tech.guitarrapc.cóm

Technical updates

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

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

https://gist.github.com/96d73c5ac0224406d0d0

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デリゲートとシグネチャを一致させる

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

コード

https://gist.github.com/guitarrapc/f61acf5500a28b4b2d7f

Register-ObjectEvent Cmdlet

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

http://technet.microsoft.com/en-us/library/hh849929.aspx

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

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

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

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

イベントで発生した標準出力をStringBuilderに追記します。ただし、イベント購読は現在のスコープとは違うため生成したStringBuilderインスタンスを渡すのが面倒ですね。 そこでイベント登録時にローカル変数を渡すのに使うのが、$Event.MessageDataです。今回の場合、$Event.MessageDataで、StringBuilderインスタンスを渡しました。 これにより、Action中のスクリプトブロックで同インスタンスが利用されます。Actionで追記されてますね。

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

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

$ Invoke-Process -Arguments "'hoge'"

StandardOutput : hoge
ErrorOutput    :
ExitCode       : 0

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

まとめ

ラムダ式くだされ~