からめもぶろぐ。

SharePoint が得意なフレンズなんだね!すごーい!

Binary Module (C#) でいい感じに Write-Verbose したい

この記事は「PowerShell Advent Calendar 2018」の参加記事です。

qiita.com

ちなみに投稿が遅れたのではなく 16 日が埋まっていなかったので穴埋めしています。ここ重要!

Write-Verbose について

PowerShell では Verbose パラメーターを付けることでトレース情報を出力させることができます。モジュールの内部では Write-Verbose を呼び出すことで出力内容を制御します。Script Module の場合、PowerShell のスクリプトからコマンドレットを呼び出すだけなので、特に何も考える必要はないのですが、Binary Module の場合は勝手が違います。なぜなら、Cmdlet.WriteVerbose メソッドは static ではないからです。単純なコマンドレットであれば、ProcessRecord メソッドで全部書けば済むかもしれません。ある程度の規模になってくるとそうもいきません。かといって Cmdlet のインスタンスを持ち回すのも美しいとは思えません。

Trace.WriteLine があるじゃないか

C# には Trace.WriteLine メソッドがあります。こいつを使えばいけるのでは…と思いましたが、標準では Trace.WriteLine をホストに流し込んでくれるような仕組みにはなっていないようです。*1 仕方がないので TraceLister を自作します。

CmdletTraceListener.cs

public class CmdletTraceListener : TraceListener
{
    private readonly Cmdlet cmdlet;

    public CmdletTraceListener(Cmdlet cmdlet)
    {
        if (cmdlet == null)
        {
            throw new ArgumentNullException(nameof(cmdlet));
        }
        this.cmdlet = cmdlet;
    }

    public override void Write(string message)
    {
        this.cmdlet.WriteVerbose(message);
    }

    public override void WriteLine(string message)
    {
        this.cmdlet.WriteVerbose(message);
    }
}

TraceableCmdlet.cs

public abstract class TraceableCmdlet : PSCmdlet
{
    private readonly TraceListener traceListener;

    protected TraceableCmdlet()
    {
        this.traceListener = new CmdletTraceListener(this);
    }

    protected override void BeginProcessing()
    {
        Trace.Listeners.Add(this.traceListener);
        base.BeginProcessing();
    }

    protected override void EndProcessing()
    {
        base.EndProcessing();
        Trace.Listeners.Remove(this.traceListener);
    }
}

こうしてあげることで、どこからでも Trace.WriteLine を呼び出すだけで (Verbose パラメーターを指定していれば) ホストにメッセージが出力されますし、疎結合になるのでテストも容易になるのではないかと思います。

(2018/12/20 追記)

Twitter でご指摘いただきました。ありがとうございます。

確かに例外が発生すると EndProcessing が呼ばれないので、TraceListener が解除されずにメモリ リークになってしまっているようです。IDisposable を実装してあげるのがよいようですね。

tech.blog.aerie.jp

*1:Console.WriteLine は Write-Host と同様に動作します。