PowerShellにて外部プロセスによる大量の標準出力を出力順に取得したい
PowerShell 5.0にてSystem.Diagnostics.Process
のOutputDataReceived
イベントを使って標準出力をリダイレクトすると、出力順が乱れて本来の標準出力と異なる結果が返ってきてしまうことがあります。
プロセスに対してStandardOutput.ReadToEnd
メソッドを使用すれば出力順は乱れませんが、標準出力のバイト数が大きいと処理が固まってしまいます。
リダイレクトを使って標準出力を出力順に受け取ったり、StandardOutput.ReadToEnd
のように同期のとれた方法で安全に標準出力結果を取り出すには、どうすれば良いのでしょうか。
なおPowerShellからcmd /C "ping /?"
のようにcmd.exe
を経由で外部exeを実行することで上記の要件は強引に達成できるように見えますが、よりPowerShellらしい解法があればご教示願います。
サンプルコード:
#pingのヘルプを呼び出し、標準出力を返すだけのサンプルコード
function Invoke-Ping {
$info = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$info.CreateNoWindow = $true
$info.UseShellExecute = $false
$info.RedirectStandardOutput = $true
$info.FileName = 'ping'
$info.Arguments = @('/?')
$process = New-Object -TypeName System.Diagnostics.Process
$process.StartInfo = $info
$outBuilder = New-Object -TypeName System.Text.StringBuilder
#非同期に標準出力を受け取る
$outReceived = {
if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
$Event.MessageData.AppendLine($EventArgs.Data)
}
}
$outEvent = Register-ObjectEvent -InputObject $process `
-Action $outReceived -EventName 'OutputDataReceived' `
-MessageData $outBuilder
[void]$process.Start()
$process.BeginOutputReadLine()
[void]$process.WaitForExit()
Unregister-Event -SourceIdentifier $outEvent.Name
$outBuilder.ToString()
}
$set = New-Object 'System.Collections.Generic.HashSet[string]'
1..10 | % {
#標準出力の戻り値がずれる
($outValue = Invoke-Ping) > $null
$set.Add($outValue) > $null
}
$set -join "-----`r`n"
"HashSet.Count = {0}" -f $set.Count
実行結果の抜粋例(出力結果の行が正しい結果と前後しています):
-----
[-r ホップ数] [-s ホップ数] [[-j ホスト一覧] | [-k ホスト一覧]]
使用法: ping [-t] [-a] [-n 要求数] [-l サイズ] [-f] [-i TTL] [-v TOS]
[-w タイムアウト] [-R] [-S ソースアドレス] [-c コンパートメント]
オプション:
[-p] [-4] [-6] ターゲット名
統計を表示して続行するには、Ctrl+Break を押してください。
停止するには、Ctrl+C を押してください。
-a アドレスをホスト名に解決します。
-t 中断されるまで、指定されたホストを Ping します。