PowerShellからパイプラインで外部に繋ぐための文字エンコード設定

## はじめに
PowerShellでのテキスト処理が、やはり簡単ではないことが多いと感じてきました。PowerShellから外部コマンドやwslに渡すために試したことをメモにまとめました。

## 結論
* 外部コマンドに渡す場合は`$OutputEncoding`を`[console]::OutputEncoding`に揃える
  * `$OutputEncoding = [console]::OutputEncoding`
* `wsl`に渡す場合は`$OutputEncoding`を`utf-8`にする
  * `$OutputEncoding = [System.Text.Encoding]::UTF8`

## 試したこと
### デフォルトでの動作

```
PS > ls \ | oss | sls c:

    ディレクトリ: C:\


PS > ls \ | findstr C:
    ??????: C:\
PS > ls \ | wsl grep C:
    ??????: C:\
```
7bit分しか渡されていないようです。
これは、`$PSDefaultParameterValues`を変更しても変わりませんでした。

調べると、下記の記事がヒットしました。

* 参考：[Powershell上で、パイプで渡すと文字化けする。](https://qiita.com/EmEpsilon/items/7e8f72b9c58576b4c5a5)

`$OutputEncoding`の設定が必要ということがわかりました。

### ドキュメントを良く読む
* 参考：[文字エンコードについて - PowerShell | Microsoft Docs (既定のエンコードの変更)](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-5.1#changing-the-default-encoding)

このセクションの最後の部分に以下のように書いてありました。

> 自動変数は $OutputEncoding 、外部プログラムとの通信に PowerShell が使用するエンコードに影響します。 

ということで、`$OutputEncoding`を調べるとデフォルトでは`us-ascii`になっていました。

```
PS > $OutputEncoding.WebName
us-ascii
```

`$OutputEncoding`について、ドキュメントを見てみましょう。
* 参考：[基本設定変数について - PowerShell ($OutputEncoding)](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-5.1#outputencoding)

`findstr.exe`に渡す場合は、以下のように設定する必要があると書かれています。
>PowerShell でコマンドを機能させるには、 の値を コンソールの $OutputEncoding OutputEncoding プロパティの値に設定します。これは、Windows に対して選択されたロケールに基づいて行います。


>`$OutputEncoding = [console]::OutputEncoding`

これで何が設定されるかを実際に調べてみると、

```
PS > [console]::OutputEncoding


BodyName          : iso-2022-jp
EncodingName      : 日本語 (シフト JIS)
HeaderName        : iso-2022-jp
WebName           : shift_jis
WindowsCodePage   : 932
IsBrowserDisplay  : True
IsBrowserSave     : True
IsMailNewsDisplay : True
IsMailNewsSave    : True
IsSingleByte      : False
EncoderFallback   : System.Text.InternalEncoderBestFitFallback
DecoderFallback   : System.Text.InternalDecoderBestFitFallback
IsReadOnly        : True
CodePage          : 932


```
こうなっていました。コンソールの出力と、`$OutputEncoding`とが一致していないと文字化けするということですね。
`$OutputEncoding`に何が設定出来るのかを知るために、以下のリンクを辿ってみました。
>既定値: <u>ASCIIEncoding</u>オブジェクト。

* [ASCIIEncoding](https://docs.microsoft.com/ja-jp/dotnet/api/system.text.asciiencoding?view=netframework-4.5) 

各プロパティの継承元を追ってみます。

>(継承元 <u>Encoding</u>)

* [Encoding クラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.text.encoding?view=netframework-4.5)

この中に一覧表があり、ここで「.NET Framework サポート」のチェックがあるものが設定可能ということがわかりました。

* [Encoding クラス (エンコーディングの一覧)](https://docs.microsoft.com/ja-jp/dotnet/api/system.text.encoding?view=netframework-4.5#list-of-encodings)

この表の下に、以下のメソッドを見つけました。
> GetEncoding(Int32) GetEncoding(String)

コードページかエンコードの名前を用いて文字エンコードが指定できるということですね。
クラスが`System.Text.Encoding`ということもわかったので、試してみます。

```
PS > [System.Text.Encoding]::GetEncoding(65001).webname
utf-8
PS > [System.Text.Encoding]::GetEncoding('utf-8').webname
utf-8
```

他にも指定する方法がないか、調べてみました。

```
PS > [System.Text.Encoding] | get-member -static


   TypeName: System.Text.Encoding

Name             MemberType Definition
----             ---------- ----------
Convert          Method     static byte[] Convert(System.Text.Encoding srcEncoding, System.Text.Encoding dstEncoding...
Equals           Method     static bool Equals(System.Object objA, System.Object objB)
GetEncoding      Method     static System.Text.Encoding GetEncoding(int codepage), static System.Text.Encoding GetEn...
GetEncodings     Method     static System.Text.EncodingInfo[] GetEncodings()
ReferenceEquals  Method     static bool ReferenceEquals(System.Object objA, System.Object objB)
RegisterProvider Method     static void RegisterProvider(System.Text.EncodingProvider provider)
ASCII            Property   static System.Text.Encoding ASCII {get;}
BigEndianUnicode Property   static System.Text.Encoding BigEndianUnicode {get;}
Default          Property   static System.Text.Encoding Default {get;}
Unicode          Property   static System.Text.Encoding Unicode {get;}
UTF32            Property   static System.Text.Encoding UTF32 {get;}
UTF7             Property   static System.Text.Encoding UTF7 {get;}
UTF8             Property   static System.Text.Encoding UTF8 {get;}


```

`UTF8`、`Default`、`ASCII`のプロパティが使えそうです。

```
PS > [System.Text.Encoding]::UTF8.WebName
utf-8
PS > [System.Text.Encoding]::Default.WebName
shift_jis
PS > [System.Text.Encoding]::ASCII.WebName
us-ascii
```
これで、必要な文字エンコードを直接指定することができるようになりました。

### 期待通りの動作
```
PS > $OutputEncoding = [console]::OutputEncoding
PS > ls \ | findstr C:
    ディレクトリ: C:\
PS > $OutputEncoding = [System.Text.Encoding]::UTF8
PS > ls \ | wsl grep C:
    ディレクトリ: C:\
```
`$OutputEncoding`を固定して使う場合は、`$profile`ファイルに書き込んでおくと便利です。
* 参考：[`$profile`ファイルの編集手順](https://qiita.com/e4rfx/items/243a19d7853a2a585422#%E8%B5%B7%E5%8B%95%E6%99%82%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%82%92%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%82%81%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B)

### 確認環境
* Windows 10 Home 21H2

```
PS > (gi function:ver).definition
(systeminfo | ?{$_.Contains("OS")})[0,1];($PSVersionTable | oss)[3].trim() -replace ' +',":`t`t";(wsl -l)[3].Split()[0]
PS > ver
OS 名:                  Microsoft Windows 10 Home
OS バージョン:          10.0.19044 N/A ビルド 19044
PSVersion:              5.1.19041.1320
Ubuntu-18.04
```
※ `(wsl -l)`の文字化けは調査中(上記は文字化け結果を利用したもの)

## 参考

https://qiita.com/e4rfx/items/eb8fcd7c3b1647912c0b

https://qiita.com/e4rfx/items/243a19d7853a2a585422