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-ObjectEvent
Cmdletです。
これを使うことで、指定したイベントの購読が可能になります。
今回の場合は、OutputDataReceivedイベントが対象ですね。
イベントに対して実行する内容
-Action
に実行するスクリプトブロックを渡します。
イベントで発生した標準出力をStringBuilder に追記していきたいと思います。ただし、イベント購読は現在のスコープとは違うため生成したStringBuilderインスタンスを渡すのが面倒ですね。
そこでイベント登録時にローカル変数を渡すのに使うのが、$Event.MessageData
です。今回の場合、-MessageData $stdSb
で、StringBuilderインスタンスを渡しました。
これにより、Action
中のスクリプトブロックで同インスタンスが利用されます。AppendLine
で追記されてますね。
くれぐれも$global:
を使ってグローバルスコープを汚染しないように。
例
例えば、PowerShell プロセスでhogeと標準出力するだけの処理を見てみましょう。
Invoke-Process -Arguments "'hoge'"
結果です。
StandardOutput : hoge ErrorOutput : ExitCode : 0
出力できましたね。あとは、お好きにプロパティ指定で。
まとめ
ラムダ式くだされ~