2つのフォルダから差分をパワーシェルだけで炙り出す
Winmargeでも確かに十分だが好き勝手にcsvでもやれるしカスタムをしたいためだけ
笑笑
.ps1 という拡張子で以下を保存すればそのままダブルクリックすれば、
ダイアログ表示されるので使えます。
比較するフォルダを入力して、ENTER keyで実行可能
宜しくお願い致します。
param(
[string]$Folder1,
[string]$Folder2,
[string]$OutputCsv,
[int]$TimestampToleranceSeconds = 2,
[switch]$DiffOnly,
[switch]$ExcludeDirectories,
[int]$FlushInterval = 200,
[switch]$UseDialogs
)
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
function Select-FolderPath {
param(
[string]$Description,
[string]$SelectedPath
)
$dialog = New-Object System.Windows.Forms.FolderBrowserDialog
$dialog.Description = $Description
$dialog.ShowNewFolderButton = $false
if (-not [string]::IsNullOrWhiteSpace($SelectedPath) -and (Test-Path -LiteralPath $SelectedPath)) {
$dialog.SelectedPath = $SelectedPath
}
$result = $dialog.ShowDialog()
if ($result -ne [System.Windows.Forms.DialogResult]::OK) {
return $null
}
return $dialog.SelectedPath
}
function Select-OutputCsvPath {
param(
[string]$InitialPath
)
$dialog = New-Object System.Windows.Forms.SaveFileDialog
$dialog.Title = '比較結果CSVの保存先を指定'
$dialog.Filter = 'CSVファイル (*.csv)|*.csv|すべてのファイル (*.*)|*.*'
$dialog.DefaultExt = 'csv'
$dialog.AddExtension = $true
$dialog.OverwritePrompt = $true
$dialog.FileName = 'FolderCompare_{0}.csv' -f (Get-Date -Format 'yyyyMMdd_HHmmss')
if (-not [string]::IsNullOrWhiteSpace($InitialPath)) {
try {
$dir = Split-Path -Path $InitialPath -Parent
$leaf = Split-Path -Path $InitialPath -Leaf
if (-not [string]::IsNullOrWhiteSpace($dir) -and (Test-Path -LiteralPath $dir)) {
$dialog.InitialDirectory = $dir
}
if (-not [string]::IsNullOrWhiteSpace($leaf)) {
$dialog.FileName = $leaf
}
}
catch {
}
}
else {
try {
$dialog.InitialDirectory = (Get-Location).Path
}
catch {
}
}
$result = $dialog.ShowDialog()
if ($result -ne [System.Windows.Forms.DialogResult]::OK) {
return $null
}
return $dialog.FileName
}
function Show-CompareSetupForm {
param(
[string]$Folder1,
[string]$Folder2,
[string]$OutputCsv
)
$form = New-Object System.Windows.Forms.Form
$form.Text = 'フォルダ比較設定'
$form.StartPosition = 'CenterScreen'
$form.Size = New-Object System.Drawing.Size(860, 250)
$form.MinimumSize = New-Object System.Drawing.Size(860, 250)
$form.MaximizeBox = $false
$form.MinimizeBox = $false
$form.TopMost = $true
$labelInfo = New-Object System.Windows.Forms.Label
$labelInfo.Location = New-Object System.Drawing.Point(20, 15)
$labelInfo.Size = New-Object System.Drawing.Size(800, 20)
$labelInfo.Text = '直接入力または参照ボタンで指定して、OKで次へ進みます。'
$form.Controls.Add($labelInfo)
$label1 = New-Object System.Windows.Forms.Label
$label1.Location = New-Object System.Drawing.Point(20, 50)
$label1.Size = New-Object System.Drawing.Size(140, 20)
$label1.Text = 'Folder1'
$form.Controls.Add($label1)
$textFolder1 = New-Object System.Windows.Forms.TextBox
$textFolder1.Location = New-Object System.Drawing.Point(20, 72)
$textFolder1.Size = New-Object System.Drawing.Size(700, 25)
$textFolder1.Text = $Folder1
$form.Controls.Add($textFolder1)
$buttonBrowse1 = New-Object System.Windows.Forms.Button
$buttonBrowse1.Location = New-Object System.Drawing.Point(730, 70)
$buttonBrowse1.Size = New-Object System.Drawing.Size(90, 28)
$buttonBrowse1.Text = '参照...'
$buttonBrowse1.Add_Click({
$selected = Select-FolderPath -Description '比較元のフォルダ (Folder1) を選択' -SelectedPath $textFolder1.Text
if ($null -ne $selected) {
$textFolder1.Text = $selected
}
})
$form.Controls.Add($buttonBrowse1)
$label2 = New-Object System.Windows.Forms.Label
$label2.Location = New-Object System.Drawing.Point(20, 105)
$label2.Size = New-Object System.Drawing.Size(140, 20)
$label2.Text = 'Folder2'
$form.Controls.Add($label2)
$textFolder2 = New-Object System.Windows.Forms.TextBox
$textFolder2.Location = New-Object System.Drawing.Point(20, 127)
$textFolder2.Size = New-Object System.Drawing.Size(700, 25)
$textFolder2.Text = $Folder2
$form.Controls.Add($textFolder2)
$buttonBrowse2 = New-Object System.Windows.Forms.Button
$buttonBrowse2.Location = New-Object System.Drawing.Point(730, 125)
$buttonBrowse2.Size = New-Object System.Drawing.Size(90, 28)
$buttonBrowse2.Text = '参照...'
$buttonBrowse2.Add_Click({
$selected = Select-FolderPath -Description '比較先のフォルダ (Folder2) を選択' -SelectedPath $textFolder2.Text
if ($null -ne $selected) {
$textFolder2.Text = $selected
}
})
$form.Controls.Add($buttonBrowse2)
$label3 = New-Object System.Windows.Forms.Label
$label3.Location = New-Object System.Drawing.Point(20, 160)
$label3.Size = New-Object System.Drawing.Size(160, 20)
$label3.Text = '出力CSV'
$form.Controls.Add($label3)
$textOutput = New-Object System.Windows.Forms.TextBox
$textOutput.Location = New-Object System.Drawing.Point(20, 182)
$textOutput.Size = New-Object System.Drawing.Size(700, 25)
$textOutput.Text = $OutputCsv
$form.Controls.Add($textOutput)
$buttonBrowseOutput = New-Object System.Windows.Forms.Button
$buttonBrowseOutput.Location = New-Object System.Drawing.Point(730, 180)
$buttonBrowseOutput.Size = New-Object System.Drawing.Size(90, 28)
$buttonBrowseOutput.Text = '参照...'
$buttonBrowseOutput.Add_Click({
$selected = Select-OutputCsvPath -InitialPath $textOutput.Text
if ($null -ne $selected) {
$textOutput.Text = $selected
}
})
$form.Controls.Add($buttonBrowseOutput)
$buttonOk = New-Object System.Windows.Forms.Button
$buttonOk.Location = New-Object System.Drawing.Point(620, 215)
$buttonOk.Size = New-Object System.Drawing.Size(95, 30)
$buttonOk.Text = 'OK'
$buttonOk.Add_Click({
$folder1Value = $textFolder1.Text.Trim()
$folder2Value = $textFolder2.Text.Trim()
$outputValue = $textOutput.Text.Trim()
if ([string]::IsNullOrWhiteSpace($folder1Value)) {
[System.Windows.Forms.MessageBox]::Show('Folder1を入力または選択してください。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textFolder1.Focus()
return
}
if (-not (Test-Path -LiteralPath $folder1Value)) {
[System.Windows.Forms.MessageBox]::Show('Folder1が存在しません。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textFolder1.Focus()
return
}
if (-not (Get-Item -LiteralPath $folder1Value -Force).PSIsContainer) {
[System.Windows.Forms.MessageBox]::Show('Folder1にはフォルダを指定してください。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textFolder1.Focus()
return
}
if ([string]::IsNullOrWhiteSpace($folder2Value)) {
[System.Windows.Forms.MessageBox]::Show('Folder2を入力または選択してください。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textFolder2.Focus()
return
}
if (-not (Test-Path -LiteralPath $folder2Value)) {
[System.Windows.Forms.MessageBox]::Show('Folder2が存在しません。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textFolder2.Focus()
return
}
if (-not (Get-Item -LiteralPath $folder2Value -Force).PSIsContainer) {
[System.Windows.Forms.MessageBox]::Show('Folder2にはフォルダを指定してください。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textFolder2.Focus()
return
}
if ([string]::IsNullOrWhiteSpace($outputValue)) {
[System.Windows.Forms.MessageBox]::Show('出力CSVを入力または選択してください。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textOutput.Focus()
return
}
try {
$parentDir = Split-Path -Path $outputValue -Parent
if (-not [string]::IsNullOrWhiteSpace($parentDir) -and -not (Test-Path -LiteralPath $parentDir)) {
[System.Windows.Forms.MessageBox]::Show('出力先フォルダが存在しません。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textOutput.Focus()
return
}
}
catch {
[System.Windows.Forms.MessageBox]::Show('出力CSVのパス形式が不正です。', '入力確認', [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
$textOutput.Focus()
return
}
$form.Tag = [pscustomobject]@{
Folder1 = $folder1Value
Folder2 = $folder2Value
OutputCsv = $outputValue
}
$form.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.Close()
})
$form.Controls.Add($buttonOk)
$buttonCancel = New-Object System.Windows.Forms.Button
$buttonCancel.Location = New-Object System.Drawing.Point(725, 215)
$buttonCancel.Size = New-Object System.Drawing.Size(95, 30)
$buttonCancel.Text = 'キャンセル'
$buttonCancel.Add_Click({
$form.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.Close()
})
$form.Controls.Add($buttonCancel)
$form.AcceptButton = $buttonOk
$form.CancelButton = $buttonCancel
$result = $form.ShowDialog()
if ($result -ne [System.Windows.Forms.DialogResult]::OK) {
return $null
}
return $form.Tag
}
function Confirm-CompareSettings {
param(
[string]$Folder1,
[string]$Folder2,
[string]$OutputCsv,
[int]$TimestampToleranceSeconds,
[bool]$DiffOnly,
[bool]$ExcludeDirectories,
[int]$FlushInterval
)
$diffOnlyText = 'いいえ'
if ($DiffOnly) {
$diffOnlyText = 'はい'
}
$excludeDirectoriesText = 'いいえ'
if ($ExcludeDirectories) {
$excludeDirectoriesText = 'はい'
}
$message = @"
以下の設定で比較を開始します。
Folder1:
$Folder1
Folder2:
$Folder2
出力CSV:
$OutputCsv
更新日時許容差(秒): $TimestampToleranceSeconds
差分のみ出力: $diffOnlyText
フォルダ行を除外: $excludeDirectoriesText
途中保存間隔(件): $FlushInterval
はい : この設定で実行
いいえ : 入力フォームへ戻る
キャンセル : 実行せず終了
"@
return [System.Windows.Forms.MessageBox]::Show(
$message,
'比較開始の確認',
[System.Windows.Forms.MessageBoxButtons]::YesNoCancel,
[System.Windows.Forms.MessageBoxIcon]::Question,
[System.Windows.Forms.MessageBoxDefaultButton]::Button1
)
}
if ([string]::IsNullOrWhiteSpace($OutputCsv)) {
$OutputCsv = Join-Path -Path (Get-Location) -ChildPath ('FolderCompare_{0}.csv' -f (Get-Date -Format 'yyyyMMdd_HHmmss'))
}
if ($UseDialogs -or [string]::IsNullOrWhiteSpace($Folder1) -or [string]::IsNullOrWhiteSpace($Folder2) -or [string]::IsNullOrWhiteSpace($OutputCsv)) {
while ($true) {
$selection = Show-CompareSetupForm -Folder1 $Folder1 -Folder2 $Folder2 -OutputCsv $OutputCsv
if ($null -eq $selection) {
Write-Warning '入力フォームがキャンセルされました。処理を終了します。'
return
}
$Folder1 = $selection.Folder1
$Folder2 = $selection.Folder2
$OutputCsv = $selection.OutputCsv
$confirmResult = Confirm-CompareSettings `
-Folder1 $Folder1 `
-Folder2 $Folder2 `
-OutputCsv $OutputCsv `
-TimestampToleranceSeconds $TimestampToleranceSeconds `
-DiffOnly ([bool]$DiffOnly) `
-ExcludeDirectories ([bool]$ExcludeDirectories) `
-FlushInterval $FlushInterval
if ($confirmResult -eq [System.Windows.Forms.DialogResult]::Yes) {
break
}
if ($confirmResult -eq [System.Windows.Forms.DialogResult]::Cancel) {
Write-Warning '実行はキャンセルされました。'
return
}
}
}
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$script:CsvBuffer = New-Object System.Collections.Generic.List[object]
$script:Summary = @{}
$script:TotalRowsWritten = 0
$script:CompletedSuccessfully = $false
$script:CsvColumns = @(
'判定区分',
'相対パス',
'Folder1フルパス',
'Folder2フルパス',
'種別1',
'種別2',
'サイズ1',
'サイズ2',
'サイズ差',
'作成日時1',
'作成日時2',
'更新日時1',
'更新日時2',
'更新日時差秒',
'差分理由',
'エラー内容'
)
$script:PartialOutputCsv = $OutputCsv + '.partial'
function Resolve-NormalizedPath {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
if (-not (Test-Path -LiteralPath $Path)) {
throw "パスが存在しません: $Path"
}
$item = Get-Item -LiteralPath $Path -Force
return $item.FullName.TrimEnd('\\')
}
function Get-RelativePathSafe {
param(
[Parameter(Mandatory = $true)]
[string]$RootPath,
[Parameter(Mandatory = $true)]
[string]$FullPath
)
if ($FullPath -eq $RootPath) {
return ''
}
$rootUri = New-Object System.Uri(($RootPath.TrimEnd('\\') + '\\'))
$fullUri = New-Object System.Uri($FullPath)
$relativeUri = $rootUri.MakeRelativeUri($fullUri)
$relativePath = [System.Uri]::UnescapeDataString($relativeUri.ToString())
return ($relativePath -replace '/', '\\')
}
function Format-DateTimeValue {
param(
[object]$DateValue
)
if ($null -eq $DateValue) {
return ''
}
return ([datetime]$DateValue).ToString('yyyy-MM-dd HH:mm:ss')
}
function Initialize-OutputCsv {
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
$dir = Split-Path -Path $Path -Parent
if (-not [string]::IsNullOrWhiteSpace($dir) -and -not (Test-Path -LiteralPath $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
$headerLine = ($script:CsvColumns -join ',')
Set-Content -LiteralPath $Path -Value $headerLine -Encoding UTF8
}
function Flush-CsvBuffer {
if ($script:CsvBuffer.Count -eq 0) {
return
}
$script:CsvBuffer |
Export-Csv -LiteralPath $script:PartialOutputCsv -NoTypeInformation -Encoding UTF8 -Append
$script:TotalRowsWritten += $script:CsvBuffer.Count
$script:CsvBuffer.Clear()
}
function Add-OutputRow {
param(
[Parameter(Mandatory = $true)]
[psobject]$Row
)
$script:CsvBuffer.Add($Row)
$category = [string]$Row.判定区分
if ($script:Summary.ContainsKey($category)) {
$script:Summary[$category]++
}
else {
$script:Summary[$category] = 1
}
if ($script:CsvBuffer.Count -ge $FlushInterval) {
Flush-CsvBuffer
}
}
function Get-FileSystemIndex {
param(
[Parameter(Mandatory = $true)]
[string]$RootPath,
[Parameter(Mandatory = $true)]
[string]$SideLabel,
[bool]$IncludeDirectories = $true,
[int]$ProgressId = 1
)
$index = @{}
$errors = New-Object System.Collections.Generic.List[object]
$stack = New-Object System.Collections.Stack
$stack.Push($RootPath)
$discoveredFileCount = 0
$processedEntryCount = 0
$lastProgressTick = [Environment]::TickCount
while ($stack.Count -gt 0) {
$currentDir = [string]$stack.Pop()
try {
$children = [System.IO.Directory]::GetFileSystemEntries($currentDir)
}
catch {
$relativeForError = ''
if ($currentDir -ne $RootPath) {
$relativeForError = Get-RelativePathSafe -RootPath $RootPath -FullPath $currentDir
}
$errors.Add([pscustomobject]@{
SideLabel = $SideLabel
RelativePath = $relativeForError
FullPath = $currentDir
ErrorMessage = $_.Exception.Message
})
continue
}
foreach ($child in $children) {
$relativePath = ''
try {
$processedEntryCount++
$relativePath = Get-RelativePathSafe -RootPath $RootPath -FullPath $child
$attributes = [System.IO.File]::GetAttributes($child)
$isDirectory = (($attributes -band [System.IO.FileAttributes]::Directory) -ne 0)
if ($isDirectory) {
$dirInfo = New-Object System.IO.DirectoryInfo($child)
if ($IncludeDirectories) {
$index[$relativePath] = [pscustomobject]@{
RelativePath = $relativePath
FullPath = $dirInfo.FullName
ItemType = 'Directory'
Size = $null
CreationTime = $dirInfo.CreationTime
LastWriteTime = $dirInfo.LastWriteTime
Attributes = [string]$dirInfo.Attributes
}
}
$stack.Push($child)
}
else {
$discoveredFileCount++
$fileInfo = New-Object System.IO.FileInfo($child)
$index[$relativePath] = [pscustomobject]@{
RelativePath = $relativePath
FullPath = $fileInfo.FullName
ItemType = 'File'
Size = [int64]$fileInfo.Length
CreationTime = $fileInfo.CreationTime
LastWriteTime = $fileInfo.LastWriteTime
Attributes = [string]$fileInfo.Attributes
}
}
$nowTick = [Environment]::TickCount
if ((($processedEntryCount % 200) -eq 0) -or (($nowTick - $lastProgressTick) -ge 300)) {
Write-Progress `
-Id $ProgressId `
-Activity "$SideLabel をスキャン中" `
-Status ("発見ファイル数: {0:N0}" -f $discoveredFileCount) `
-CurrentOperation $relativePath
$lastProgressTick = $nowTick
}
}
catch {
$errors.Add([pscustomobject]@{
SideLabel = $SideLabel
RelativePath = $relativePath
FullPath = $child
ErrorMessage = $_.Exception.Message
})
}
}
}
Write-Progress `
-Id $ProgressId `
-Activity "$SideLabel をスキャン中" `
-Status ("完了 発見ファイル数: {0:N0}" -f $discoveredFileCount) `
-Completed
return [pscustomobject]@{
RootPath = $RootPath
Index = $index
Errors = $errors
}
}
function New-CompareRow {
param(
[string]$Category,
[string]$RelativePath,
[string]$Folder1FullPath,
[string]$Folder2FullPath,
[string]$Type1,
[string]$Type2,
[object]$Size1,
[object]$Size2,
[object]$CreationTime1,
[object]$CreationTime2,
[object]$LastWriteTime1,
[object]$LastWriteTime2,
[string]$Reason,
[string]$ErrorMessage
)
$sizeDiff = $null
if ($null -ne $Size1 -and $null -ne $Size2) {
$sizeDiff = [int64]$Size2 - [int64]$Size1
}
$timeDiffSeconds = $null
if ($null -ne $LastWriteTime1 -and $null -ne $LastWriteTime2) {
$timeDiffSeconds = [math]::Round((New-TimeSpan -Start ([datetime]$LastWriteTime1) -End ([datetime]$LastWriteTime2)).TotalSeconds, 0)
}
return [pscustomobject][ordered]@{
判定区分 = $Category
相対パス = $RelativePath
Folder1フルパス = $Folder1FullPath
Folder2フルパス = $Folder2FullPath
種別1 = $Type1
種別2 = $Type2
サイズ1 = $Size1
サイズ2 = $Size2
サイズ差 = $sizeDiff
作成日時1 = Format-DateTimeValue -DateValue $CreationTime1
作成日時2 = Format-DateTimeValue -DateValue $CreationTime2
更新日時1 = Format-DateTimeValue -DateValue $LastWriteTime1
更新日時2 = Format-DateTimeValue -DateValue $LastWriteTime2
更新日時差秒 = $timeDiffSeconds
差分理由 = $Reason
エラー内容 = $ErrorMessage
}
}
function Finalize-SortedOutput {
if (-not (Test-Path -LiteralPath $script:PartialOutputCsv)) {
return
}
if ($script:TotalRowsWritten -le 0) {
$headerLine = ($script:CsvColumns -join ',')
Set-Content -LiteralPath $OutputCsv -Value $headerLine -Encoding UTF8
return
}
Import-Csv -LiteralPath $script:PartialOutputCsv |
Sort-Object 判定区分, 相対パス |
Export-Csv -LiteralPath $OutputCsv -NoTypeInformation -Encoding UTF8
}
try {
$root1 = Resolve-NormalizedPath -Path $Folder1
$root2 = Resolve-NormalizedPath -Path $Folder2
$includeDirectories = -not $ExcludeDirectories
Initialize-OutputCsv -Path $script:PartialOutputCsv
$scan1 = Get-FileSystemIndex -RootPath $root1 -SideLabel 'Folder1' -IncludeDirectories $includeDirectories -ProgressId 1
$scan2 = Get-FileSystemIndex -RootPath $root2 -SideLabel 'Folder2' -IncludeDirectories $includeDirectories -ProgressId 2
$allKeys = @($scan1.Index.Keys) + @($scan2.Index.Keys) | Sort-Object -Unique
$totalCompareCount = $allKeys.Count
$compareCounter = 0
$lastCompareTick = [Environment]::TickCount
foreach ($key in $allKeys) {
$compareCounter++
$nowTick = [Environment]::TickCount
if ((($compareCounter % 500) -eq 0) -or (($nowTick - $lastCompareTick) -ge 300)) {
$percent = 0
if ($totalCompareCount -gt 0) {
$percent = [int](($compareCounter / $totalCompareCount) * 100)
}
Write-Progress `
-Id 3 `
-Activity "差分比較中" `
-Status ("比較済み: {0:N0} / {1:N0}" -f $compareCounter, $totalCompareCount) `
-CurrentOperation $key `
-PercentComplete $percent
$lastCompareTick = $nowTick
}
$item1 = $null
$item2 = $null
if ($scan1.Index.ContainsKey($key)) {
$item1 = $scan1.Index[$key]
}
if ($scan2.Index.ContainsKey($key)) {
$item2 = $scan2.Index[$key]
}
$category = ''
$reason = ''
if ($null -ne $item1 -and $null -eq $item2) {
$category = 'Folder1のみ存在'
$reason = 'Folder1にのみ存在'
}
elseif ($null -eq $item1 -and $null -ne $item2) {
$category = 'Folder2のみ存在'
$reason = 'Folder2にのみ存在'
}
else {
if ($item1.ItemType -ne $item2.ItemType) {
$category = '種別不一致'
$reason = '同一相対パスでファイル/フォルダが不一致'
}
elseif ($item1.ItemType -eq 'Directory') {
$category = '両方あり同一候補'
$reason = '両方に同名フォルダあり'
}
else {
$size1 = [int64]$item1.Size
$size2 = [int64]$item2.Size
$sizeDiff = $size2 - $size1
$timeDiffSeconds = [math]::Round((New-TimeSpan -Start $item1.LastWriteTime -End $item2.LastWriteTime).TotalSeconds, 0)
$sameSize = ($size1 -eq $size2)
$sameTime = ([math]::Abs($timeDiffSeconds) -le $TimestampToleranceSeconds)
if ($sameSize -and $sameTime) {
$category = '両方あり同一候補'
$reason = 'サイズ一致かつ更新日時差が許容範囲内'
}
else {
if (-not $sameTime) {
if ($timeDiffSeconds -gt 0) {
$category = 'Folder2が新しい候補'
if ($sizeDiff -gt 0) {
$reason = '更新日時・サイズともにFolder2側が新しい/大きい'
}
elseif ($sizeDiff -lt 0) {
$reason = '更新日時はFolder2が新しいが、サイズはFolder1の方が大きい'
}
else {
$reason = '更新日時はFolder2が新しい'
}
}
else {
$category = 'Folder1が新しい候補'
if ($sizeDiff -lt 0) {
$reason = '更新日時・サイズともにFolder1側が新しい/大きい'
}
elseif ($sizeDiff -gt 0) {
$reason = '更新日時はFolder1が新しいが、サイズはFolder2の方が大きい'
}
else {
$reason = '更新日時はFolder1が新しい'
}
}
}
else {
if ($sizeDiff -gt 0) {
$category = 'Folder2が新しい候補'
$reason = '更新日時差は許容範囲内だが、サイズはFolder2の方が大きい'
}
elseif ($sizeDiff -lt 0) {
$category = 'Folder1が新しい候補'
$reason = '更新日時差は許容範囲内だが、サイズはFolder1の方が大きい'
}
else {
$category = '両方あり同一候補'
$reason = 'サイズ一致・更新日時差は許容範囲内'
}
}
}
}
}
if ($DiffOnly -and $category -eq '両方あり同一候補') {
continue
}
$folder1FullPath = ''
$folder2FullPath = ''
$type1 = 'Missing'
$type2 = 'Missing'
$size1Val = $null
$size2Val = $null
$creation1 = $null
$creation2 = $null
$write1 = $null
$write2 = $null
if ($null -ne $item1) {
$folder1FullPath = $item1.FullPath
$type1 = $item1.ItemType
$size1Val = $item1.Size
$creation1 = $item1.CreationTime
$write1 = $item1.LastWriteTime
}
if ($null -ne $item2) {
$folder2FullPath = $item2.FullPath
$type2 = $item2.ItemType
$size2Val = $item2.Size
$creation2 = $item2.CreationTime
$write2 = $item2.LastWriteTime
}
$row = New-CompareRow `
-Category $category `
-RelativePath $key `
-Folder1FullPath $folder1FullPath `
-Folder2FullPath $folder2FullPath `
-Type1 $type1 `
-Type2 $type2 `
-Size1 $size1Val `
-Size2 $size2Val `
-CreationTime1 $creation1 `
-CreationTime2 $creation2 `
-LastWriteTime1 $write1 `
-LastWriteTime2 $write2 `
-Reason $reason `
-ErrorMessage ''
Add-OutputRow -Row $row
}
Write-Progress -Id 3 -Activity "差分比較中" -Completed
foreach ($err in $scan1.Errors) {
$row = New-CompareRow `
-Category '比較不能' `
-RelativePath $err.RelativePath `
-Folder1FullPath $err.FullPath `
-Folder2FullPath '' `
-Type1 'Error' `
-Type2 '' `
-Size1 $null `
-Size2 $null `
-CreationTime1 $null `
-CreationTime2 $null `
-LastWriteTime1 $null `
-LastWriteTime2 $null `
-Reason 'Folder1側の読み取りに失敗' `
-ErrorMessage $err.ErrorMessage
Add-OutputRow -Row $row
}
foreach ($err in $scan2.Errors) {
$row = New-CompareRow `
-Category '比較不能' `
-RelativePath $err.RelativePath `
-Folder1FullPath '' `
-Folder2FullPath $err.FullPath `
-Type1 '' `
-Type2 'Error' `
-Size1 $null `
-Size2 $null `
-CreationTime1 $null `
-CreationTime2 $null `
-LastWriteTime1 $null `
-LastWriteTime2 $null `
-Reason 'Folder2側の読み取りに失敗' `
-ErrorMessage $err.ErrorMessage
Add-OutputRow -Row $row
}
Flush-CsvBuffer
Finalize-SortedOutput
$script:CompletedSuccessfully = $true
Write-Host ""
Write-Host "並び替え済みCSV出力完了: $OutputCsv" -ForegroundColor Green
Write-Host ("CSV行数: {0:N0}" -f $script:TotalRowsWritten) -ForegroundColor Green
Write-Host ("途中保存CSV: {0}" -f $script:PartialOutputCsv) -ForegroundColor DarkGray
Write-Host ""
Write-Host "件数サマリ:" -ForegroundColor Cyan
$script:Summary.GetEnumerator() |
Sort-Object Name |
ForEach-Object {
Write-Host (" {0} : {1:N0}" -f $_.Name, $_.Value)
}
}
catch {
try {
Flush-CsvBuffer
}
catch {
}
Write-Error $_.Exception.Message
throw
}
finally {
try {
Flush-CsvBuffer
}
catch {
}
Write-Progress -Id 1 -Activity "Folder1 をスキャン中" -Completed
Write-Progress -Id 2 -Activity "Folder2 をスキャン中" -Completed
Write-Progress -Id 3 -Activity "差分比較中" -Completed
if (-not $script:CompletedSuccessfully) {
Write-Warning "処理は完走していません。途中までの結果は partial CSV に残っている可能性があります: $script:PartialOutputCsv"
}
}
