PowerShell 7で外部コマンドのUTF-8出力が文字化けする場合の対処法

# 概要

外部コマンドがUTF-8で日本語（などの非ASCII文字）を出力する場合、それをPowerShell変数として受けると文字化けすることがある。その際は、一時的に`[Console]::OutputEncoding`を変更する。以下のようになる。

```powershell
[Console]::OutputEncoding=[Text.Encoding]::UTF8
$result = 外部コマンド
[Console]::OutputEncoding=[Console]::InputEncoding
```

1liner化するならこんな感じ？

```powershell
$result = Invoke-Command { [Console]::OutputEncoding=[Text.Encoding]::UTF8; 外部コマンド; [Console]::OutputEncoding=[Console]::InputEncoding }
```

そのまま後続処理にパイプ渡しもできる。例えば出力がJSON形式なら、次のようにパースまでつなげられる。

```powershell
$result = Invoke-Command { [Console]::OutputEncoding=[Text.Encoding]::UTF8; 外部コマンド; [Console]::OutputEncoding=[Console]::InputEncoding } | ConvertFrom-JSON
```

ちなみに筆者は外部コマンドとして、具体的には[1password CLI](https://developer.1password.com/docs/cli/)の`op item list`のJSON形式出力を`ConvertFrom-JSON`につなげたかった。でも文字化けが差し障ったのでこれらを調査して、最終的にこんな感じにした。

```powershell
$result = Invoke-Command { [Console]::OutputEncoding=[Text.Encoding]::UTF8; op item list --format=json; [Console]::OutputEncoding=[Console]::InputEncoding } | ConvertFrom-JSON
```

# 詳細

文字化け対策の基本的な考え方は以下より。本記事タイトルも以下をちょっともじっただけ。

> PowerShellで、Unicode出力を正しく受け取るためには、コンソールの出力エンコードをUnicodeにする。これで、PowerShellは外部コマンドの出力がUnicodeエンコードであることを理解し、変換しなくなる。そのためには、**一時的にコンソールの出力エンコードをUnicodeにして、コマンドの実行後に元に戻す**。
> （[ASCII.jp：PowerShellで外部コマンドの出力が文字化けする場合の対処法 (1/2)](https://ascii.jp/elem/000/004/140/4140455/)）

示されているコードは以下で、上述のものより丁寧。

```
$TempMyOutputEncode=[System.Console]::OutputEncoding
[System.Console]::OutputEncoding=[System.Text.Encoding]::Unicode
$x=wsl.exe -l
[System.Console]::OutputEncoding=$TempMyOutputEncode
```

ここから変更と二つの手抜きをしているのだけど、変更点はUnicodeではなくUTF-8を受け取るため、文字コード指定を変えている。以下「[Encoding クラス (System.Text) | Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.text.encoding?view=net-7.0#properties)」からの抜粋にあるように、`Unicode`はUTF-16を指す。

|プロパティ|概要|
|---|---|
|Unicode|リトル エンディアン バイト順を使用する UTF-16 形式のエンコーディングを取得します。|
|UTF8|UTF-8 形式のエンコーディングを取得します。|

手抜きとして第一に `[System.Console]` や `[System.Text.Encoding]` の部分は、 `System.` を省略できる。

> 型がシステム名前空間のルートにない場合は、オブジェクト型の完全な名前を指定します。 "System" は省略できます。
> （[型演算子について - PowerShell | Microsoft Learn](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_type_operators?view=powershell-7.4#long-description)）

次に、`[System.Console]::OutputEncoding`は意図的に変えていなければ、`[System.Console]::InputEncoding`のはず。この着想は以下からの類推。`$Output`と`[System.Console]::OutputEncoding`は別物なので、この説明があるから正しいというものではないけど、とりあえずうまくいっている。

> ほとんどのシナリオでは、 $OutputEncoding の値は [Console]::InputEncodingの値に合わせる必要があります。
> （[基本設定変数について - PowerShell | Microsoft Learn](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.4#outputencoding)）

2番目の手抜きは、意図的に変更している可能性もあるので、しない方が確実ではある。

# 参考

* [ASCII.jp：PowerShellで外部コマンドの出力が文字化けする場合の対処法 (1/2)](https://ascii.jp/elem/000/004/140/4140455/)
* [型演算子について - PowerShell | Microsoft Learn](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_type_operators?view=powershell-7.4)
* [基本設定変数について - PowerShell | Microsoft Learn](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.4)

記事内では言及しなかったが、以下のページも理解のための参考にした。

* [文字エンコードについて - PowerShell | Microsoft Learn](https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-7.4)
* [クラシック コマンドと PowerShell の間のエンコード設定 - is BUG Ready ?](https://ladybug.hatenadiary.org/entry/20111203/p1)