Powershell で `"STR".ToBase64` のように打つと Base64 エンコードできるようにする

PowerShell では、 .NET の資産を簡単に呼び出すことができます。

つまり、 Base64 エンコードのような煩雑な処理も、 base64.exe のような外部依存なしに行えるということです。

しかし、 .NET を呼び出す記述は冗長になりがちで、対話シェルで使うにはあまりに面倒です。

そこで、 PowerShell のメンバを動的に追加する機能を使うことで、 "STR".ToBase64 のように短い記述で処理を呼び出せるようにします。

TL;DR

どうやって追加するか

PowerShell の機能の一つに Extended Type System (ETS) というものがあります。

これは、型に対してメンバーを動的に追加したりするための仕組みで、「タイプドリブン」な PowerShell で型名を気にせずスクリプトを書けるのはこの仕組みのおかげです。

今回は、 ETS の「拡張型データ」に ToBase64 プロパティを追加することで、 "STR".ToBase64 のような記述を実現します。

「分かった」気になれる言い方をするなら、 PowerShell の豊富な機能の一つを使って StringToBase64 プロパティを追加するという言い方になるのでしょう。

ちなみに、 ETS では PSObject という神クラスが重要な役割を果たしているらしいです。詳しい説明は マイクロソフトの Extended Type System に関するドキュメントにあるので興味がある方は読んでみてください。

実際にメソッドを追加する

では、 String 型に対して ToBase64 プロパティを追加してみましょう。

まずは、追加する ToBase64 の実装を考えます。とりあえず、以下のような単純な実装にしてみました。

function ToBase64([String] $str)
{
	// [System.Text.Encoding]::Unicode を使っているため、 UTF-16 の文字列としてエンコードされるので注意
	[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($str))
}

次に、 Types.ps1xml を書きます。ファイル名は何でも構いませんが、拡張子は ps1xml でなくてはなりません。

Types.ps1xml のリファレンスは Types.ps1xml にありますので、必要に応じて読んでください。

<?xml version="1.0" encoding="utf-8" ?>
<Types>
	<Type>
		<!-- System.String 型に対してデータを追加する -->
		<Name>System.String</Name>
		<Members>
			<!-- `"foo".ToBase64` のように呼び出したいので、「 Script の返り値が値となるようなプロパティ」を定義する -->
			<ScriptProperty>
				<!-- プロパティ名は `ToBase64` にする -->
				<Name>ToBase64String</Name>
				<!-- 実行するスクリプトを定義する。引数としてインスタンス自身、つまり $this を渡す -->
				<GetScriptBlock>
					[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($this))
				</GetScriptBlock>
			</ScriptProperty>
		</Members>
	</Type>
</Types>

最後に、 PowerShell で Types.ps1xml を読み込みます。

PS C:\> Update-Typedata -PrependPath .\Types.ps1xml
PS C:\> "example".ToBase64
ZQB4AGEAbQBwAGwAZQA=
PS C:\> # ToBase64 が呼び出せた!

Update-TypeData で読み込んだデータは、シェルが終了すると消えてしまいます。シェル起動時に自動的に読み込まれるようにするには、 ProfilesUpdate-TypeData を呼び出すとよいでしょう。

なお、この節で説明した内容は about_Types.ps1xml - PowerShell | Microsoft Docs の Example: Adding an Age member to FileInfo objects の節とほぼ同じですので、分からないところなどあればそちらを読んでください。