PowerShellの罠CmdletといえばStart-Processですね。アレ罠できらいです。
代わりに何を使うかというと、System.Diagnostic.Processクラスです。C#で書くのと同じように挙動が想定通りなので安心です。
http://msdn.microsoft.com/ja-jp/library/system.diagnostics.process%28v=vs.110%29.aspx
今回は、外部プロセスをPowerShellで扱うお話です。
StandardOutput だとパイプがつまることがある
PowerShellでProcessクラスを使う例は比較的多く、.StandardOutput()メソッドを使った標準出力を使っているのをよく見ます。
が、良く知られてる突然のプロセス停止問題があります。この記事でも例がかかれています。
http://www.atmarkit.co.jp/fdotnet/dotnettips/805pipeasync/pipeasync.html
どうするかというと、記事と同様にProcess.BeginOutputReadLine メソッドを使います。
http://msdn.microsoft.com/ja-jp/library/system.diagnostics.process.beginoutputreadline.aspx
コード
外部プロセスをPowerShellで実行するコードです。
https://github.com/guitarrapc/PowerShellUtil/blob/master/Invoke-Process/Invoke-Process.ps1
PowerShell でイベント購読
http://msdn.microsoft.com/ja-jp/library/system.diagnostics.process.beginoutputreadline.aspx
BeginOutputReadLine は StandardOutput ストリームで非同期読み取り操作を開始します。 このメソッドは、ストリーム出力に指定されたイベント ハンドラーを有効にした後、直ちに呼び出し元に制御を返します。これにより、呼び出し元は、ストリーム出力がイベント ハンドラーにリダイレクトされている間に他の作業を実行できます。
OutputDataReceivedイベントを購読すればいいわけです。
PowerShellのイベント購読めんどくさいんですよねー。
流れ
UseShellExecute=falseを設定RedirectStandardOutput=trueを設定OutputDataReceivedイベントのイベントハンドラーを追加。イベントハンドラーは、System.Diagnostics.DataReceivedEventHandlerデリゲートとシグネチャを一致させる
めんどくさいイベント処理部分だけみましょう。
コード
Register-ObjectEvent Cmdlet
イベント購読に使うのが、Register-ObjectEventCmdletです。
これを使うことで、指定したイベントの購読が可能になります。
今回の場合は、OutputDataReceivedイベントが対象ですね。
イベントに対して実行する内容
-Actionに実行するスクリプトブロックを渡します。
イベントで発生した標準出力をStringBuilderに追記します。ただし、イベント購読は現在のスコープとは違うため生成したStringBuilderインスタンスを渡すのが面倒ですね。
そこでイベント登録時にローカル変数を渡すのに使うのが、$Event.MessageDataです。今回の場合、$Event.MessageDataで、StringBuilderインスタンスを渡しました。
これにより、Action中のスクリプトブロックで同インスタンスが利用されます。Actionで追記されてますね。
くれぐれも$global:を使ってグローバルスコープを汚染しないように。
例
例えば、PowerShellプロセスでhogeと標準出力するだけの処理を見てみましょう。
$ Invoke-Process -Arguments "'hoge'" StandardOutput : hoge ErrorOutput : ExitCode : 0
出力できましたね。あとは、お好きにプロパティ指定で。
まとめ
ラムダ式くだされ~