銀河鉄道

【PowerShell】2つのファイルの差分を出す|Compare-Object

サムネイル
[PowerShell]compare two filesCompare-Object 1 2

#region ファイル選択関数
function Select-MultipleFilePaths {
    param(
        [string]$Title = "複数のファイルを選択",
        [string]$Filter = "データファイル (*.txt;*.csv;*.xlsx)|*.txt;*.csv;*.xlsx|すべてのファイル (*.*)|*.*",
        # MyDocumentsからDesktopに変更
        [string]$InitialDirectory = [Environment]::GetFolderPath("Desktop") 
    )

    $dialog = New-Object System.Windows.Forms.OpenFileDialog
    $dialog.Title = $Title

    # InitialDirectoryを設定
    $dialog.InitialDirectory = $InitialDirectory
    $dialog.Filter = $Filter
    
    # 複数選択を有効化
    $dialog.Multiselect = $true
    
    if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
        return $dialog.FileNames
    }
    else {
        return @()
    }
}

function Select-FilePath {
    param(
        [string]$Title = "ファイルを選択",
        [string]$Filter = "データファイル (*.txt;*.csv;*.xlsx)|*.txt;*.csv;*.xlsx|すべてのファイル (*.*)|*.*",
        # MyDocumentsからDesktopに変更
        [string]$InitialDirectory = [Environment]::GetFolderPath("Desktop")
    )
    
    $dialog = New-Object System.Windows.Forms.OpenFileDialog
    $dialog.Title = $Title
    # InitialDirectoryを設定
    $dialog.InitialDirectory = $InitialDirectory
    $dialog.Filter = $Filter
    $dialog.Multiselect = $false
    
    if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
        return $dialog.FileName
    }
    else {
        return $null
    }
}
#endregion

#region メイン比較関数
function Show-FileDifferenceGui {
    param(
        [Parameter(Mandatory=$false)]
        [string]$Title = "ファイル差分比較ツール"
    )

    # 複数ファイルを選択
    $selectedFiles = Select-MultipleFilePaths -Title "比較候補ファイルを複数選択してください (Shift/Ctrlで複数選択)"

    if ($selectedFiles.Count -lt 2) {
        Write-Warning "比較には少なくとも2つのファイルが必要です。処理を中止します。"
        return
    }

    # Out-GridViewを利用して、選択されたファイルリストから比較対象の2ファイルを選ぶ
    Write-Host "選択されたファイル: $($selectedFiles.Count)件。この中から比較したい2つのファイルを選んでください。"
    
    $fileInfo = $selectedFiles | ForEach-Object {
        [PSCustomObject]@{
            FileName = (Split-Path $_ -Leaf)
            FullPath = $_
        }
    }
    
    # Out-GridViewで複数選択を許可
    $filesToCompare = $fileInfo | Out-GridView -Title "比較対象の2ファイルを選択 (Shift/Ctrlで選択)" -OutputMode Multiple

    if ($null -eq $filesToCompare -or $filesToCompare.Count -ne 2) {
        Write-Warning "正確に2つのファイルを選択してください。処理を中止します。"
        return
    }

    # 比較対象のファイルパスを抽出
    $file1Path = $filesToCompare[0].FullPath
    $file2Path = $filesToCompare[1].FullPath
    
    Write-Host "比較元ファイル: $($file1Path)"
    Write-Host "比較先ファイル: $($file2Path)"

    # ファイルの内容を比較
    Write-Host "ファイルを比較中... $($file1Path) と $($file2Path)"
    
    $file1Content = Get-Content $file1Path
    $file2Content = Get-Content $file2Path

    $diffResult = Compare-Object $file1Content $file2Content

    # 結果をGUIで表示(差分ありの場合)
    if ($diffResult) {
        $annotatedDiff = $diffResult | Select-Object InputObject, SideIndicator, @{
            Name = '差異方向'
            Expression = {
                switch ($_.SideIndicator) {
                    '<=' { "比較元ファイルにのみ存在 ({0})" -f (Split-Path $file1Path -Leaf) }
                    '=>' { "比較先ファイルにのみ存在 ({0})" -f (Split-Path $file2Path -Leaf) }
                    default { $_.SideIndicator }
                }
            }
        }
        
        $annotatedDiff | Out-GridView -Title "$Title - 差分結果(差異あり)"
        Write-Host "差分結果をグリッドビューに表示しました。差異方向をご確認ください。"
    }
    # 結果をメッセージボックスで表示(差分なしの場合)
    else {
        $message = "【差異なし】ファイル間に差異はありませんでした。内容が完全に一致しています。"
        [System.Windows.Forms.MessageBox]::Show($message, "$Title - 差異なし", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information) | Out-Null
        Write-Host "(差異なし) - コンソールにメッセージを出力しました。"
    }
}
#endregion

#region 実行ロジック (GUI初期化の強化)
# アセンブリがまだロードされていない場合のみロードを実行する
if (-not ([System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms"))) {
    Add-Type -AssemblyName System.Windows.Forms
}
# Visual Stylesを有効化
[System.Windows.Forms.Application]::EnableVisualStyles()

# STAモード警告のチェック(GUI表示問題の主要因であるため維持)
$isSTA = ([System.Threading.Thread]::CurrentThread.GetApartmentState() -eq [System.Threading.ApartmentState]::STA)
if (-not $isSTA) {
    Write-Warning "GUI実行環境の推奨モードではありません。ダイアログが表示されない場合は、-STAオプション付きでPowerShellを再起動してください。"
}

# 実行
Show-FileDifferenceGui
#endregion

どう比べている?

  • Line-by-line comparison|行単位比較
    • Get-Content1行=1要素 の配列。
    • Compare-Object は「左配列 vs 右配列」の集合差を計算し、片側にしか無い行を抽出する。
  • SideIndicator|向き
    • <= は「左(比較元)にのみ存在」
    • => は「右(比較先)にのみ存在」

Application|応用(よくある要件)

  • エンコーディング差を吸収:
    • Get-Content -Encoding UTF8 等で明示(混在環境だと文字化け・誤差分の温床)。
  • 空白/改行差の正規化:
    • 前処理で Trim()-replace '\r?\n','\n' などを入れて意図しない差分を減らす。
  • CSV/TSVのカラム差分:
    • 行全体ではなく、ConvertFrom-Csv → カラム選択 → Compare-Object -Property にすると構造的な差分が見える。

C. Diagram / Pseudocode|図式/擬似コード

[OpenFileDialog (multi)] --> [List of paths]
            |
            v
 [Out-GridView pick exactly 2]
            |
     (f1, f2) valid?
        | yes
        v
 Get-Content f1,f2  --> Compare-Object --> Diff?
                                | yes                | no
                                v                    v
                   annotate + Out-GridView   MessageBox "No difference"
# English: Pseudocode
# 日本語: 擬似コード
function Show-FileDifferenceGui():
	paths = Select-MultipleFilePaths()
	if paths.count < 2: warn & return
	choice = Out-GridView(paths, pick=2)
	if invalid(choice): warn & return
	lines1 = Get-Content(choice[0])
	lines2 = Get-Content(choice[1])
	diff   = Compare-Object(lines1, lines2)
	if diff not empty:
		annotate(diff) |> Out-GridView
	else:
		MessageBox("No differences")

コマンド、構文

Compare-Object

2つのオブジェクトセット(ここではGet-Contentで取得した行のコレクション)を比較し、共通しない要素や差分のある要素を検出する。

Compares two sets of objects (here, collections of lines obtained by Get-Content) and detects non-common or differing elements.

Out-GridView

パイプラインからの入力を、フィルタリング、ソート、グルーピングが可能な対話型グリッドビューウィンドウに表示する。

Displays input from the pipeline in an interactive grid view window, which allows for filtering, sorting, and grouping.

[Environment]::GetFolderPath("Desktop")

デスクトップフォルダのパスを取得する

現在のユーザーのデスクトップフォルダのフルパスを確実かつ動的に取得する.NETメソッド

.NET method that reliably and dynamically retrieves the full path of the current user’s Desktop folder, independent of Windows environment variables

OpenFileDialog.InitialDirectory

ファイル選択ダイアログが最初に開くディレクトリを設定するプロパティ。

このプロパティに「Desktop」パスを代入することで、初期表示場所を変更する

A property that sets the initial directory the file selection dialog opens to. Changing this property to the “Desktop” path modifies the initial display location

System.Windows.Forms

PowerShellでWindows標準のGUI要素(ファイルダイアログなど)を扱うためにロードする.NET Frameworkのアセンブリ。

A .NET Framework assembly loaded in PowerShell to handle standard Windows GUI elements, such as file dialogs.

Select-FilePath (カスタム関数)

System.Windows.Forms.OpenFileDialogを利用して、ユーザーに視覚的にファイルを選択させるためのラッパー関数。

A wrapper function utilizing System.Windows.Forms.OpenFileDialog to allow users to visually select a file.

OpenFileDialog.Filterプロパティ

ファイル選択ダイアログに表示されるファイルの種類を指定するプロパティ。

The property that specifies the file types displayed in the file selection dialog.

フィルター文字列の構文

表示名とファイル拡張子をパイプ記号 (|) で区切り、拡張子はセミコロン (;) で複数指定可能。

The display name and file extensions are separated by the pipe symbol (|), and multiple extensions can be specified using a semicolon (;).

embedding|埋め込み
line-by-line comparison|行単位比較
SideIndicator|差異方向
initial directory|初期ディレクトリ
visual styles|ビジュアルスタイル
single-threaded apartment (STA)|シングルスレッドアパートメント
normalize|正規化
encoding|エンコーディング
hash comparison|ハッシュ比較
structure-aware diff|構造化差分

著者

author
月うさぎ

編集後記:
この記事の内容がベストではないかもしれません。