最近作ってる PowerShell 関係のライブラリについて

はじめに

本記事は PowerShell Advent Claendar 2018 の一日目です。
今年は立てるのが遅かったためか、まだスッカスカです。
皆さんの寄稿をお待ちしております!

qiita.com

本編

初日なんでライトに行きましょう。
最近こんなのを書いてたよ、というご紹介です。
C#PowerShell モジュールを書くために、こんなのあったら便利だよね、というやつです。

リポジトリはこちら。
github.com

src フォルダの中に 3 つのプロジェクトがあります。
それぞれさらっとご紹介。

Aerie.PowerShell

github.com

雑多なもの置き場です。

CmdletExtensions

こんな風に、パラメーターが指定されたかどうかを調べるやつです。

if (this.HasParameter(x => x.Foo)) { ... }

if (this.HasParameter(nameof(this.Bar)) { ... }

これと同じやつです。
tech.blog.aerie.jp

ValidateMethodAttribute

ValidateScriptAttribute .NET 版です。
C# では Attribute の引数に ScriptBlock を与えられないので、代わりにクラスの型とメソッド名を与えるやつを作りました。

[Parameter]
[ValidateMethod(typeof(ParameterValidator), nameof(ParameterValidator.ValidateParameter)]
public string Foo { get; set; }

Aerie.PowerShell.Async

github.com

PowerShell でも非同期処理/並列処理を使いたいよね、という時に使うライブラリです。
似たようなものは沢山あるのですが、個人的にこだわったのは、AsyncCmdlet 的なクラスからの継承を強制しないことです。
継承してもいいよという人には、利便性のために AsyncCmdlet / AsyncPSCmdlet も提供していますが、本質的には IAsyncCmdlet というインターフェイスを実装すればいいようになっています。
また、async/await だけでなく、Task.Run などのワーカースレッドを用いた並列処理でも使えるように、WriteObjectAsync 等のメソッドも用意しています。

継承を強制しない代わりに、実装に一定のお作法が生じてしまうため、そのお作法に従っているかどうかをチェックする Roslyn アナライザーも作りかけてはいるのですが、進んでいません。

Aerie.PowerShell.DynamicParameter

github.com

動的パラメーターを使いやすくしようという動機で開発しているライブラリです。
3 つの中では一番気合が入ってます。

動的パラメーターというのは、まぁそのまんまなんですが、例えば「パラメーター X の値がほげほげであるときにのみ、パラメーター Y が使用可能」といったように、条件付きで使用可能になるパラメーターのことです。
動的パラメーター自体は PowerShell が持っている仕組みなのですが、スクリプトでこれを(補助ライブラリ無しで)使おうと思うと、「もう二度と使いたくない」というような面倒くさい思いをします。サンプルは載せませんw
ただ、.NET であれば、リフレクションがありますので、通常の静的パラメーターとほぼ遜色のない使用感に出来るのではないか…ということで作ってみたものです。

たとえば、こんな風に使うことができます。

// .NET で動的パラメーターをサポートするには IDynamicParameters インターフェイスを実装する必要がある
object IDynamicParameters.GetDynamicParameters()
{
    this.EnableDynamicParameter(nameof(this.Foo));

    var proxy = this.GetParameterProxy();
    return proxy;
}

// ParameterAttribute の代わりに DynamicParameterAttribute をつける
[DynamicParameter]
public string Foo { get; set; }

動的パラメーターの概要

動的パラメーターをサポートするコマンドを .NET で書くためには、IDynamicParameters インターフェイスを実装する必要があります。
GetDynamicParameters メソッドは、RuntimeDefinedParameterDictionary オブジェクトか、カスタム オブジェクトを返します。

RuntimeDefinedParameterDictionary オブジェクトを生成して返す場合、パラメーター1個単位で使用/不使用を切り替えるなど、非常に柔軟な制御ができます。反面、すごく面倒くさいです。

カスタム オブジェクトというのは、RuntimeDefinedParameterDictionary でないユーザー定義クラスのオブジェクトのことです。
これは、メインのクラスから一部のパラメーターの定義を切り出したようなものです。
たとえばこんな感じ。

public class HogeCommandDynamicParameters
{
    [Parameter]
    public string Foo { get; set; }

    [Parameter]
    public int Bar { get; set; }
}

こういったクラスのインスタンスを GetDynamicParameters メソッドから返せば、パラメーターとして Foo と Bar が使えるようになります。
こちらの利点は、定義と利用が簡単であること。欠点は、RuntimeDefinedParameterDictionary ほどには柔軟な制御ができないということです。
それでも、パラメーター セットと同じくらいの表現力はあり、パラメーター セットよりは使いやすいので、どんどん使っていくといいと思います。

開発の動機とか

RuntimeDefinedParameterDictionary と同程度の制御の柔軟さと、カスタム オブジェクトと同程度の使いやすさを兼ね備えたものが欲しかったんです。
また、パラメーター セットが、ちょっと複雑なことをしようと思うと、途端に組み合わせ爆発を起こして使い物にならなくなるため、パラメーター セットの代替が欲しかったという動機もあります。

要は、カスタム オブジェクトの型を、メンバー単位で動的に定義できればいいわけなので、IL Emit を使ってコネコネしています。

また、静的パラメーターの使用感に近づけるというコンセプトも重視しています。
RuntimeDefinedParameterDictionary の場合、パラメーター値がセットされたタイミングを捉えることができません(通常の静的パラメーターであれば、プロパティの set アクセッサ―が呼ばれます)。
上記のサンプル コードで GetParameterProxy という名前になっているのも、値の Get/Set を Foo プロパティにリダイレクトするプロキシとして機能するオブジェクトだからです。
まぁ、これは大した意味のないこだわりではあります。パラメーター値の検証は Validator でやるべきでしょうからね。

愚痴

だいたいねえ、GetDynamicParameters が ExpandoObject とかを解釈してくれりゃーよかったんですよ。まったく。

終わりに

とりあえず、既にある持ちネタを出しただけという感じのエントリーでしたw
今後も(気が向いたら)開発を継続していきたいと思っています。
テストを書いたり、ビルドをちゃんとして nuget.org に出したりしたいですね。

が、こういった下回りのライブラリもよいのですが、これを使った具体的なアプリケーションのネタがないというのが頭の痛いところです…。
使ってみないと設計の良し悪しが分からないという点もありますので、何かネタをひねり出したいところです。