param( [Parameter(Mandatory = $true)] [string]$GoodFile, [Parameter(Mandatory = $true)] [string]$BadFile, [int]$ContextBytes = 32, [int]$MaxDifferences = 200 ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Format-Byte { param([byte]$Value) if ($Value -eq 0x0D) { return "CR" } if ($Value -eq 0x0A) { return "LF" } if ($Value -eq 0x09) { return "TAB" } if ($Value -eq 0x12) { return "DC2" } if ($Value -eq 0x14) { return "DC4" } if ($Value -eq 0x20) { return "SPACE" } if ($Value -ge 32 -and $Value -le 126) { return [char]$Value } return "." } function Format-Bytes { param( [byte[]]$Bytes, [int]$Start, [int]$Count ) if ($Bytes.Length -eq 0) { return "" } $safeStart = [Math]::Max(0, $Start) $safeEnd = [Math]::Min($Bytes.Length - 1, $safeStart + $Count - 1) if ($safeStart -gt $safeEnd) { return "" } $hex = New-Object System.Collections.Generic.List[string] $txt = New-Object System.Collections.Generic.List[string] for ($i = $safeStart; $i -le $safeEnd; $i++) { $hex.Add($Bytes[$i].ToString("X2")) $txt.Add((Format-Byte $Bytes[$i])) } return ("HEX: " + ($hex -join " ") + [Environment]::NewLine + "TXT: " + ($txt -join " ")) } $goodPath = (Resolve-Path -LiteralPath $GoodFile).Path $badPath = (Resolve-Path -LiteralPath $BadFile).Path $good = [System.IO.File]::ReadAllBytes($goodPath) $bad = [System.IO.File]::ReadAllBytes($badPath) Write-Host "GOOD: $goodPath" Write-Host "BAD : $badPath" Write-Host "GOOD length: $($good.Length) bytes" Write-Host "BAD length: $($bad.Length) bytes" Write-Host "" $limit = [Math]::Min($good.Length, $bad.Length) $diffCount = 0 for ($i = 0; $i -lt $limit; $i++) { if ($good[$i] -ne $bad[$i]) { $diffCount++ if ($diffCount -le $MaxDifferences) { Write-Host ("Difference #{0} at byte {1} / 0x{1:X}" -f $diffCount, $i) Write-Host (" GOOD: 0x{0} [{1}]" -f $good[$i].ToString("X2"), (Format-Byte $good[$i])) Write-Host (" BAD : 0x{0} [{1}]" -f $bad[$i].ToString("X2"), (Format-Byte $bad[$i])) Write-Host " GOOD context:" Write-Host (Format-Bytes $good ($i - $ContextBytes) (($ContextBytes * 2) + 1)) Write-Host " BAD context:" Write-Host (Format-Bytes $bad ($i - $ContextBytes) (($ContextBytes * 2) + 1)) Write-Host "" } } } if ($good.Length -ne $bad.Length) { Write-Host "Files have different length." if ($good.Length -gt $bad.Length) { Write-Host "GOOD has extra bytes from offset $($bad.Length) / 0x$($bad.Length.ToString("X")):" Write-Host (Format-Bytes $good $bad.Length ([Math]::Min(256, $good.Length - $bad.Length))) } else { Write-Host "BAD has extra bytes from offset $($good.Length) / 0x$($good.Length.ToString("X")):" Write-Host (Format-Bytes $bad $good.Length ([Math]::Min(256, $bad.Length - $good.Length))) } Write-Host "" } Write-Host "Total differing byte positions in common length: $diffCount" $badControls = @{} for ($i = 0; $i -lt $bad.Length; $i++) { $b = $bad[$i] $allowed = $b -eq 0x09 -or $b -eq 0x0A -or $b -eq 0x0D -or ($b -ge 0x20 -and $b -le 0x7E) if (-not $allowed) { $key = "0x" + $b.ToString("X2") + " " + (Format-Byte $b) if (-not $badControls.ContainsKey($key)) { $badControls[$key] = New-Object System.Collections.Generic.List[int] } if ($badControls[$key].Count -lt 20) { $badControls[$key].Add($i) } } } Write-Host "" Write-Host "Suspicious bytes in BAD file, excluding TAB/CR/LF/ASCII printable:" if ($badControls.Count -eq 0) { Write-Host " none" } else { foreach ($entry in $badControls.GetEnumerator() | Sort-Object Name) { Write-Host (" {0}: offsets {1}" -f $entry.Key, (($entry.Value | ForEach-Object { "$_ / 0x$($_.ToString("X"))" }) -join ", ")) } }