web-dev-qa-db-ja.com

プロセスを生成し、そのSTDOUTを.NETでキャプチャする方法は?

コンソールアプリケーションである子プロセスを生成し、その出力をキャプチャする必要があります。

メソッドについて次のコードを作成しました。

string retMessage = String.Empty;
ProcessStartInfo startInfo = new ProcessStartInfo();
Process p = new Process();

startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;

startInfo.UseShellExecute = false;
startInfo.Arguments = command;
startInfo.FileName = exec;

p.StartInfo = startInfo;
p.Start();

p.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        using (StreamReader output = p.StandardOutput)
        {
            retMessage = output.ReadToEnd();
        }
    }
);

p.WaitForExit();

return retMessage;

ただし、これは何も返しません。 OutputDataReceivedイベントがコールバックされているとは思わないか、WaitForExit()コマンドがスレッドをブロックしているため、コールバックが発生しません。

何かアドバイス?

編集:コールバックで一生懸命やりすぎていたようです。やること:

return p.StandardOutput.ReadToEnd(); 

正常に動作するようです。

132
FlySwat

これが動作することを確認したコードです。 MSBuildを生成し、その出力を聞くために使用します。

process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, args) => Console.WriteLine("received output: {0}", args.Data);
process.Start();
process.BeginOutputReadLine();
149

私はまさにこれを試しましたが、次のことがうまくいきました:

StringBuilder outputBuilder;
ProcessStartInfo processStartInfo;
Process process;

outputBuilder = new StringBuilder();

processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
processStartInfo.Arguments = "<insert command line arguments here>";
processStartInfo.FileName = "<insert tool path here>";

process = new Process();
process.StartInfo = processStartInfo;
// enable raising events because Process does not raise events by default
process.EnableRaisingEvents = true;
// attach the event handler for OutputDataReceived before starting the process
process.OutputDataReceived += new DataReceivedEventHandler
(
    delegate(object sender, DataReceivedEventArgs e)
    {
        // append the new data to the data already read-in
        outputBuilder.Append(e.Data);
    }
);
// start the process
// then begin asynchronously reading the output
// then wait for the process to exit
// then cancel asynchronously reading the output
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();

// use the output
string output = outputBuilder.ToString();
34

Stdoutとstderrの両方をキャプチャし、プロセスが予期したとおりに終了しなかった場合はタイムアウトする必要がありました。私はこれを思いつきました:

Process process = new Process();
StringBuilder outputStringBuilder = new StringBuilder();

try
{
process.StartInfo.FileName = exeFileName;
process.StartInfo.WorkingDirectory = args.ExeDirectory;
process.StartInfo.Arguments = args;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.EnableRaisingEvents = false;
process.OutputDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.ErrorDataReceived += (sender, eventArgs) => outputStringBuilder.AppendLine(eventArgs.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
var processExited = process.WaitForExit(PROCESS_TIMEOUT);

if (processExited == false) // we timed out...
{
    process.Kill();
    throw new Exception("ERROR: Process took too long to finish");
}
else if (process.ExitCode != 0)
{
    var output = outputStringBuilder.ToString();
    var prefixMessage = "";

    throw new Exception("Process exited with non-zero exit code of: " + process.ExitCode + Environment.NewLine + 
    "Output from process: " + outputStringBuilder.ToString());
}
}
finally
{                
process.Close();
}

私はstdoutとstderrを同じ文字列にパイピングしていますが、必要に応じて別々に保つこともできます。イベントを使用するため、イベントが発生したときに処理する必要があります(私は信じています)。これを正常に実行しましたが、まもなくボリュームテストを行う予定です。

20
Robb Sadler

これを行うための完全でシンプルなコードを次に示します。私はそれを使用したとき、これはうまく働きました。

var processStartInfo = new ProcessStartInfo
{
    FileName = @"C:\SomeProgram",
    Arguments = "Arguments",
    RedirectStandardOutput = true,
    UseShellExecute = false
};
var process = Process.Start(processStartInfo);
var output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

これは標準outputのみをキャプチャすることに注意してください。標準errorをキャプチャしません。両方が必要な場合は、各ストリームに この手法 を使用します。

19
Sam

2行が故障しているようです。出力をキャプチャするイベントハンドラを設定する前に、プロセスを開始します。イベントハンドラーが追加される前に、プロセスが終了している可能性があります。

そのように行を切り替えます。

p.OutputDataReceived += ...
p.Start();        
19
JaredPar

StartInfoを設定した後、実際にプロセスを実行するには、p.Start()を呼び出す必要があります。プロセスは実際には開始されなかったため、関数はおそらくWaitForExit()呼び出しでハングしています。

3
SoapBox

ストリームのリダイレクトは非同期であり、プロセスが終了した後も継続する可能性があります。 Umarは、プロセスの終了後にキャンセルすることを述べていますprocess.CancelOutputRead()。ただし、データ損失の可能性があります。

これは私にとって確実に機能しています:

process.WaitForExit(...);
...
while (process.StandardOutput.EndOfStream == false)
{
    Thread.Sleep(100);
}

私はこのアプローチを試みませんでしたが、スライからの提案が好きです:

if (process.WaitForExit(timeout))
{
    process.WaitForExit();
}
2
jws

アプリケーションが最初のBeginOutputReadLine();の後に終了していたため、ユダからの答えは私にはうまくいきませんでした(または完全ではありません)

これは完全なスニペットとして機能し、pingの一定の出力を読み取ります。

        var process = new Process();
        process.StartInfo.FileName = "ping";
        process.StartInfo.Arguments = "google.com -t";
        process.StartInfo.RedirectStandardOutput = true;
        process.StartInfo.UseShellExecute = false;
        process.OutputDataReceived += (sender, a) => Console.WriteLine(a.Data);
        process.Start();
        process.BeginOutputReadLine();
        process.WaitForExit();
0
Craig