tech.guitarrapc.cóm

Technical updates

PowerShell で System.Diagnostic.Process にて BeginOutputReadLine() を使う

PowerShell の罠Cmdlet といえば Start-Process ですね。

ほんとアレ罠。きらい。大っ嫌いです。

で、代わりに何を使うかっていうと、System.Diagnostic.Process クラスです。ふつーに C# で書くのと同じように挙動が想定通りなので安心です。

今回は、この 外部プロセスを PowerShell で扱うお話。

目次

StandardOutput だとパイプがつまることがある

PowerShell で Processクラスを使う例は比較的多く、.StandardOutput() メソッドを使った標準出力を使っているのをよく見ます。

が、良く知られてる問題がありますよね?はい、突然のプロセス停止!です。

この記事でも例がかかれています。

では実際にどうするかというと、記事と同様にProcess.BeginOutputReadLine メソッドを使います。

コード

外部プロセスを PowerShell で実行するコードです。

PowerShell でイベント購読

BeginOutputReadLine は StandardOutput ストリームで非同期読み取り操作を開始します。 このメソッドは、ストリーム出力に指定されたイベント ハンドラーを有効にした後、直ちに呼び出し元に制御を返します。これにより、呼び出し元は、ストリーム出力がイベント ハンドラーにリダイレクトされている間に他の作業を実行できます。

OutputDataReceived イベントを購読すればいいわけです。

PowerShell のイベント購読めんどくさいんですよねー。

流れ
  • UseShellExecute に false を設定します
  • RedirectStandardOutput に true を設定します
  • OutputDataReceived イベントのイベント ハンドラーを追加します。 イベント ハンドラーは、System.Diagnostics.DataReceivedEventHandler デリゲート シグネチャと一致する必要があります

めんどくさいイベント処理部分だけみましょう。

コード

Register-ObjectEvent Cmdlet

イベント購読に使うのが、Register-ObjectEventCmdletです。

これを使うことで、指定したイベントの購読が可能になります。

今回の場合は、OutputDataReceivedイベントが対象ですね。

イベントに対して実行する内容

-Actionに実行するスクリプトブロックを渡します。

イベントで発生した標準出力をStringBuilder に追記していきたいと思います。ただし、イベント購読は現在のスコープとは違うため生成したStringBuilderインスタンスを渡すのが面倒ですね。

そこでイベント登録時にローカル変数を渡すのに使うのが、$Event.MessageDataです。今回の場合、-MessageData $stdSb で、StringBuilderインスタンスを渡しました。

これにより、Action中のスクリプトブロックで同インスタンスが利用されます。AppendLineで追記されてますね。

くれぐれも$global:を使ってグローバルスコープを汚染しないように。

例えば、PowerShell プロセスでhogeと標準出力するだけの処理を見てみましょう。

Invoke-Process -Arguments "'hoge'"

結果です。

StandardOutput : hoge
ErrorOutput    : 
ExitCode       : 0

出力できましたね。あとは、お好きにプロパティ指定で。

まとめ

ラムダ式くだされ~