PowerShell で diff できるコマンドを作ってみた

# TL; DR

次のようなコマンドで、動作します。

```powershell:実行例
### 次のようなfrom.txtファイルとto.txtファイルがあった場合
cat .\from.txt
# This is from text.
# There is same line.
cat .\to.txt
# This is to text.
# There is same line.

### その2ファイルの中身の差分を見る
diff.ps1 .\from.txt .\to.txt
# Line U InputObject
# ---- - -----------
#    1 < This is from text.
#    1 > This is to text.
```


# はじめに

下記記事の通り、WindowsのPowerShellにおけるdiffは、Linuxを使うときと同じイメージで使うとガッカリしてしまいます。

https://qiita.com/ktrarai/items/8bbc7566c82a3fc6d063

そこで、常にLinuxライクなdiffコマンドを実行するために、コマンドを自作します。


# コマンドの用意

以下は、コマンドの完成までの道のりです。

## 差分を表示する

次のようなコマンドで、2ファイルの中身の差分を表示できます。
なお下記コマンドは、ファイル名の差分以外は上記参考記事の内容のままです。

```powershell:from.txtとto.txtのファイルの中身の差分を表示するdiff.ps1
Compare-Object -ReferenceObject @(Get-Content -Path .\from.txt) -DifferenceObject @(Get-Content -Path .\to.txt) |
    Sort-Object -Property @{Expression = {$_.InputObject.ReadCount}; Ascending = $true} |
    Format-Table -Property @{Name = 'ReadCount'; Expression = {$_.InputObject.ReadCount}}, *
```

```powershell:実行結果
diff.ps1
# ReadCount SideIndicator InputObject
# --------- ------------- -----------
#         1            <= This is from text.
#         1            => This is to text.
```

## パラメーターを読込む

from.txtとto.txtといった固定ではなく、パラメーターとして差分を確認したいですよね。

```diff_powershell:パラメーターから読込んだファイルの中身の差分を確認するdiff.ps1
+ Param(
+   [parameter(mandatory=$true)][String]$DiffFileFrom,
+   [parameter(mandatory=$true)][String]$DiffFileTo
+ )
+
+ $DiffFileFromPath = Convert-Path $DiffFileFrom
+ $DiffFileToPath = Convert-Path $DiffFileTo
+
+ if ( $DiffFileFromPath -ne $null -and $DiffFileToPath -ne $null )
+ {
    Compare-Object -ReferenceObject @(Get-Content -Path $DiffFileFromPath) -DifferenceObject @(Get-Content -Path $DiffFileToPath) |
      Sort-Object -Property @{Expression = {$_.InputObject.ReadCount}; Ascending = $true} |
      Format-Table -Property @{Name = 'ReadCount'; Expression = {$_.InputObject.ReadCount}}, *
+ }
```

```powershell:実行結果
diff.ps1 .\from.txt .\to.txt
# ReadCount SideIndicator InputObject
# --------- ------------- -----------
#         1            <= This is from text.
#         1            => This is to text.
```

正確にはインデントを増やした `Compare-Object` コマンド行も差分といえば差分なのですが、分かりやすさのため `-w` しておきました。

https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_functions_advanced_parameters?view=powershell-5.1

https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-if?view=powershell-7.3

## 表示形式を簡略化する

表示結果のスペースが広すぎるなと感じるので、少し短くしましょう。

```diff_powershell:表示結果を簡略化したdiff.ps1
  Param(
    [parameter(mandatory=$true)][String]$DiffFileFrom,
    [parameter(mandatory=$true)][String]$DiffFileTo
  )

  $DiffFileFromPath = Convert-Path $DiffFileFrom
  $DiffFileToPath = Convert-Path $DiffFileTo

  if ( $DiffFileFromPath -ne $null -and $DiffFileToPath -ne $null )
  {
    Compare-Object -ReferenceObject @(Get-Content -Path $DiffFileFromPath) -DifferenceObject @(Get-Content -Path $DiffFileToPath) |
+     Select-Object @{
+       n='U';
+       e={ @{ '<=' = '<'; '=>' = '>' }[$_.SideIndicator] }
+     }, InputObject |
      Sort-Object -Property @{Expression = {$_.InputObject.ReadCount}; Ascending = $true} |
-     Format-Table -Property @{Name = 'ReadCount'; Expression = {$_.InputObject.ReadCount}}, *
+     Format-Table -Property @{Name = 'Line'; Expression = {$_.InputObject.ReadCount}}, *
  }
```

```powershell:実行結果
diff.ps1 .\from.txt .\to.txt
# Line U InputObject
# ---- - -----------
#    1 < This is from text.
#    1 > This is to text.
```

https://stackoverflow.com/questions/50629195/replace-arrows-for-sideindicator-in-compare-object

## 使い方をドキュメントに書く

使い方を書いて完成です。

```diff_powershell:diff.ps1
<#
.DESCRIPTION
  The Linux-like diff command.

.EXAMPLE
  PS C\:> diff.ps1 .\from.txt .\to.txt
#>

Param(
  [parameter(mandatory=$true)][String]$DiffFileFrom,
  [parameter(mandatory=$true)][String]$DiffFileTo
)

$DiffFileFromPath = Convert-Path $DiffFileFrom
$DiffFileToPath = Convert-Path $DiffFileTo

if ( $DiffFileFromPath -ne $null -and $DiffFileToPath -ne $null )
{
  Compare-Object -ReferenceObject @(Get-Content -Path $DiffFileFromPath) -DifferenceObject @(Get-Content -Path $DiffFileToPath) |
    Select-Object @{
      n='U';
      e={ @{ '<=' = '<'; '=>' = '>' }[$_.SideIndicator] }
    }, InputObject |
    Sort-Object -Property @{Expression = {$_.InputObject.ReadCount}; Ascending = $true} |
    Format-Table -Property @{Name = 'Line'; Expression = {$_.InputObject.ReadCount}}, *
}
```

https://ss64.com/ps/syntax-comments.html


# デジタル署名を実施する

PowerShellをコマンド化する際に必要なデジタル署名は、以下を参考にしてください。
これをしないと、スクリプトファイルがコマンド実行できません。

https://kondeneenen.com/powershell_digitalsignature/

ちなみに、完成途中までのスクリプトファイルも、それぞれデジタル署名が必要です。


# おわりに

デジタル署名、めっちゃ面倒ですね。