PowerShellにて外部プロセスによる大量の標準出力を出力順に取得したい


PowerShell
5.0にて`System.Diagnostics.Process`の`OutputDataReceived`イベントを使って標準出力をリダイレクトすると、出力順が乱れて本来の標準出力と異なる結果が返ってきてしまうことがあります。

プロセスに対して`StandardOutput.ReadToEnd`メソッドを使用すれば出力順は乱れませんが、標準出力のバイト数が大きいと[処理が固ま](http://www.atmarkit.co.jp/fdotnet/dotnettips/805pipeasync/pipeasync.html)ってしまいます。

リダイレクトを使って標準出力を出力順に受け取ったり、`StandardOutput.ReadToEnd`のように同期のとれた方法で安全に標準出力結果を取り出すには、どうすれば良いのでしょうか。

なおPowerShellから`cmd /C "ping
/?"`のように`cmd.exe`を経由で外部exeを実行することで上記の要件は強引に達成できるように見えますが、よりPowerShellらしい解法があればご教示願います。

サンプルコード:

[code]

    #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
    
[/code]

実行結果の抜粋例(出力結果の行が正しい結果と前後しています):

[code]

    -----
                [-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 します。
    
[/code]




シェルとして普通に持つリダイレクト機能ではダメなのでしょうか？

[code]

    $output = ping /?
    $output | Select-Object -First 10
    
[/code]

で

[code]

    Usage: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]
                [-r count] [-s count] [[-j host-list] | [-k host-list]]
                [-w timeout] [-R] [-S srcaddr] [-c compartment] [-p]
                [-4] [-6] target_name
    
    Options:
        -t             Ping the specified host until stopped.
                       To see statistics and continue - type Control-Break;
                       To stop - type Control-C.
    
[/code]

が得られました。




シェルとして普通に持つリダイレクト機能ではダメなのでしょうか？

[code]

    $output = ping /?
    $output | Select-Object -First 10
    
[/code]

で

[code]

    Usage: ping [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]
                [-r count] [-s count] [[-j host-list] | [-k host-list]]
                [-w timeout] [-R] [-S srcaddr] [-c compartment] [-p]
                [-4] [-6] target_name
    
    Options:
        -t             Ping the specified host until stopped.
                       To see statistics and continue - type Control-Break;
                       To stop - type Control-C.
    
[/code]

が得られました。

