對於大量的檔案版本比對, 或是2個相似目錄的內容比對, 以往都是用像是WinMerge之類的工具, 比對後可以列出完整的差異清單, 甚至連版本、日期等資訊都能一次顯示出來。但對於某些場合, 卻不適合用這類第三方的工具, 因此想出以PowerShell來實做比對的做法。
不過原來希望這個指令是愈短愈好, 這樣甚至在無法由外部存入ps1檔案時, 也能直接在電腦上從頭編寫。目前實做出來的還無法達到這項簡短的需求, 但至少是一項嘗試。
首先先設定幾個變數, 由於是比對, 所以一定有2個來源。這裡還指定了某些不做比對的副檔類型, 是不做比對的。
# 比對目錄:來源1,來源2
$sourPath='C:\ThisIsSourceOne'
$destPath='C:\ThisIsSourceTwo'
# 輸出檔案:來源1,來源2, 結果
$sourOutputFile='c:\temp\source.csv'
$destOutputFile='c:\temp\destination.csv'
$resultFile='c:\temp\compare-result.csv'
# 排除檔案種類
$excludeFile = @("*.log","*.zip","*.lnk","*.bak","*.jpg","*.bmp","*.gif")
本來是想用PowerShell內建的Compare-Object來做, 但它輸出的樣式呈現效果不好, 所以我又在網路上找到一個實做類似SQL Join效果的script, 可以參考這裡和github上的說明。這裡先載入這個主要的Join-Object.ps1
function Get-ScriptDirectory {
if ($psise) {Split-Path $psise.CurrentFile.FullPath}
else {$global:PSScriptRoot}
}
$scriptPath = Get-ScriptDirectory
$scriptPath = Join-Path $scriptPath Join-Object.ps1
# 引用外部Join處理script
. $scriptPath
再來是把要比對目錄內的檔案清單產出來, 產出時會略過以上設定要排除的副檔類型。這裡還包含檔案的Hash值, 到時才能比對差異。
# 產出目錄檔案清單及Hash
Get-ChildItem $sourPath -Recurse -Exclude $excludeFile | Get-FileHash | Select Path,Hash | `
export-csv $sourOutputFile -Delimiter "," -NoTypeInformation -Encoding UTF8
Get-ChildItem $destPath -Recurse -Exclude $excludeFile | Get-FileHash | Select Path,Hash | `
Export-Csv $destOutputFile -Delimiter "," -NoTypeInformation -Encoding UTF8
以上產出的檔案清單是包含檔案名稱和完整路徑的, 由於比對一定是從2個不同位置的來源, 所以完整路徑絕對不同, 如果直接比對一定會是不相同, 所以這裡要把上層路徑去除掉, 只留底層和其子層目錄, 如此才能正確比對。
# 格式化->清理上層目錄路徑資訊
(gc -path $sourOutputFile -raw) -replace $sourPath.Replace('\','\\'), '' | Out-File $sourOutputFile
(gc -path $destOutputFile -raw) -replace $destPath.Replace('\','\\'), '' | Out-File $destOutputFile
再來就是套用上面提到的Join-Object來做比對和輸出結果。畢竟這是模仿SQL Join的做法, 還做不到百分百的功能, 只有基本的比對邏輯, 但用在檔案比對上, 應該算是足夠了。
Join-Object提供-Left及-Right分別指定比對兩邊的來源。再來是-LeftJoinProperty和-RightJoinProperty來指定比對的欄位, 相當於SQL Join中的ON, 不過這裡似乎只能設定左右各一個欄位, 無法像SQL一樣指定多個欄位。而-Type則是如同指定SQL Join中的Inner/Left Outer/Right Outer/Full Join的屬性, 然後是-LeftProperties和-RightProperties是指輸出的欄位。最後, 這裡還可指定右邊來源的輸出欄位的前/後綴詞, 這樣輸出後才能方便看出是呈現哪一邊的來源欄位, 像我這裡指定的是用前綴 r_ 來表示。
$L=Import-Csv -Path $sourOutputFile -Encoding UTF8
$R=Import-Csv -Path $destOutputFile -Encoding UTF8
# 比對並輸出結果, True=同, False=不同
Join-Object -Left $L -Right $R `
-LeftJoinProperty Path -RightJoinProperty Path `
-Type AllInBoth -Prefix r_ `
-LeftProperties Path, Hash -RightProperties Hash, Path | `
Select-Object Path,r_Path,
@{ N = "Compare";
e = {
if($_.Hash -eq $_.r_Hash)
{"同"}
Else
{"相異"}
}
} | `
Sort-Object Path,r_Path | `
Export-Csv $resultFile -Delimiter "," -NoTypeInformation -Encoding UTF8
最後一提, 這裡我都是用csv以逗點區隔的格式來存放比對的檔案清單和比對結果, 尤其是後者, 因為在試驗過程中, 本來用Format-Table之類的方式輸出, 但發現這功能會無法指定固定欄寬, 因為檔案比對時可能會有檔名長短及子目錄層數無法精確計算的狀況, 導致輸出格式會很亂, 最後改用csv格式, 以Excel來進行檢視才能避免這種檢視上的問題。