Skip to content

Instantly share code, notes, and snippets.

@Hashbrown777
Created February 21, 2024 12:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Hashbrown777/c542b5ba760dfea92decbafb89bf012e to your computer and use it in GitHub Desktop.
Save Hashbrown777/c542b5ba760dfea92decbafb89bf012e to your computer and use it in GitHub Desktop.
#https://serverfault.com/questions/532065/how-do-i-diff-two-folders-in-windows-powershell/1041111#1041111
Function DirDiff { Param($a, $b, [switch]$Force, [switch]$Verbose)
$a,$b `
| DiffFolders -Force:$Force -Verbose:$Verbose `
| %{
$item = $_
switch -Exact ($item.event) {
'Added' { ">`t$($item.value)" }
'Deleted' { "<`t$($item.value)" }
'Changed' { "~`t$($item.value)" }
'' { "=`t$($item.value)" }
}
}
}
Function DiffFolders { Param([switch]$Force, [switch]$Verbose)
Begin {
$current = $last = $NULL
Function Output { Param($event, $value)
[PSCustomObject]@{
parent = $current
event = $event
value = (
$value | Resolve-Path -Relative -RelativeBasePath (($current,$last)[$event -eq 'Deleted'])
) + (([IO.Path]::DirectorySeparatorChar,'')[!$value.Parent])
}
}
}
Process {
$last = $current
$current = $_
$unchanged = 0
if (!$last) {
return
}
$queue = [System.Collections.Queue]::new()
$queue.Enqueue(($last,$current | %{ Get-Item $_ }))
while ($queue.Count) {
$x,$y = $queue.Dequeue()
if ($x) {
$x = @($x | Get-ChildItem | Sort-Object -Property Name)
}
else {
$x = @()
}
if ($y) {
$y = @($y | Get-ChildItem | Sort-Object -Property Name)
}
else {
$y = @()
}
$i = $j = 0
$m = $n = $NULL
while ($True) {
$m = $x[$i]
$n = $y[$j]
if (!($m -or $n)) {
break
}
if (
$m.Parent -or $n.Parent -and
$m.Name -ceq $n.Name
) {
if (!$m.Parent) {
$m = $NULL
}
elseif (!$n.Parent) {
$n = $NULL
}
else {
$queue.Enqueue(($m,$n))
++$i
++$j
continue
}
}
if ($m -and (
(!$n) -or ($m.Name -lt $n.Name)
)) {
Output 'Deleted' $m
if ($Force -and $m.Parent) {
$queue.Enqueue(($m,$NULL))
}
++$i
}
elseif ($n -and (
(!$m) -or ($n.Name -lt $m.Name)
)) {
Output 'Added' $n
if ($Force -and $n.Parent) {
$queue.Enqueue(($NULL,$n))
}
++$j
}
else {
++$i
++$j
if (
$m.Length -ne $n.Length -or
(cmp -s $m.FullName $n.FullName) -or
!$?
) {
Output 'Changed' $n
}
elseif ($Verbose) {
Output '' $n
}
else {
++$unchanged
}
}
}
}
if ($unchanged) {
[PSCustomObject]@{
parent = $current
event = 'Unchanged'
value = $unchanged
}
}
}
}
Function DiffFolders { Param([switch]$Force, [switch]$Verbose)
Begin {
$parent = $current = $last = $NULL
Function Output { Param($event, $value)
[PSCustomObject]@{
parent = $parent
event = $event
value = $value
}
}
$hash = { (Get-FileHash -Algorithm MD5 -LiteralPath $Input).Hash }
Function Differ { Param($name)
$old = $last.Item($name)
$new = $current.Item($name)
$last.Remove($name)
if ((!$old) -and (!$new)) {
return $False
}
if ($old[1] -ne $new[1]) {
return $True
}
$new[2] = Start-Job $hash -InputObject $new[0]
if (!$old[2]) {
$old[2] = $old[0] | &$hash
}
$new[2] = Receive-Job -Wait $new[2]
return $old[2] -ne $new[2]
}
}
Process {
$last = $current
$current = @{}
$unchanged = 0
$parent = $_
$item = Get-Item $parent
$relative = "^$([regex]::escape($item.FullName))"
$item `
| Get-ChildItem -Recurse -Force:$Force `
| %{
$name = $_.FullName -replace $relative,''
if ($_.Parent) {
$name += [IO.Path]::DirectorySeparatorChar
$current.Add($name, $NULL)
}
else {
$current.Add($name, ($_.FullName, $_.Length, $NULL))
}
if (!$last) {
}
elseif (!$last.ContainsKey($name)) {
Output 'Added' $name
}
elseif (Differ $name) {
Output 'Changed' $name
}
elseif ($Verbose) {
Output '' $name
}
else {
++$unchanged
}
}
if ($last) {
$last.Keys | %{ Output 'Deleted' $_ }
if ($unchanged) {
Output 'Unchanged' $unchanged
}
}
}
}
@Hashbrown777
Copy link
Author

image

@Hashbrown777
Copy link
Author

TODO create our own hashing function that streams two files at once, stopping at the first difference, and continuable later when comparing a third file to the second.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment