¿Cómo generar un proceso y capturar su STDOUT en .NET?

Necesito generar un proceso secundario que sea una aplicación de consola, y capturar su salida.

Escribí el siguiente código para un método:

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; 

Sin embargo, esto no devuelve nada. No creo que el evento OutputDataReceived se devuelva, o el WaitForExit() puede estar bloqueando el hilo por lo que nunca se devolverá.

¿Algún consejo?

EDITAR: Parece que estaba intentando demasiado con la callback. Obra:

 return p.StandardOutput.ReadToEnd(); 

Parece que funciona bien.

Aquí está el código que he verificado que funciona. Lo uso para generar MSBuild y escuchar su salida:

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

Simplemente probé esto y lo siguiente funcionó para mí:

 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 = ""; processStartInfo.FileName = ""; 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(); 

Parece que dos de tus líneas están fuera de servicio. Comienza el proceso antes de configurar un controlador de eventos para capturar el resultado. Es posible que el proceso esté terminando antes de que se agregue el controlador de eventos.

Cambia las líneas así.

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

Necesitaba capturar tanto stdout como stderr y tener un tiempo de espera si el proceso no finalizaba cuando se esperaba. Se me ocurrió esto:

 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(); } 

Estoy canalizando el stdout y el stderr en la misma cuerda, pero podrías mantenerlo separado si es necesario. Utiliza eventos, por lo que debe manejarlos tal como vienen (creo). Lo he ejecutado con éxito, y lo probaré en breve.

Aquí hay un código completo y simple para hacer esto. Esto funcionó bien cuando lo usé.

 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(); 

Tenga en cuenta que esto solo captura la salida estándar; no captura el error estándar. Si desea ambas, use esta técnica para cada flujo.

Debe llamar a p.Start () para ejecutar realmente el proceso después de configurar StartInfo. Tal como está, es probable que su función esté pendiente de la llamada WaitForExit () porque el proceso nunca se inició realmente.

El redireccionamiento de la transmisión es asincrónico y continuará potencialmente después de que el proceso haya finalizado. Umar lo menciona para cancelar después del proceso de finalización del process.CancelOutputRead() . Sin embargo, eso tiene potencial de pérdida de datos.

Esto funciona de manera confiable para mí:

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

No probé este enfoque, pero me gusta la sugerencia de Sly:

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

Este es un método que uso para ejecutar un proceso y obtiene su resultado y errores:

 public static string ShellExecute(this string path, string command, TextWriter writer, params string[] arguments) { using (var process = Process.Start(new ProcessStartInfo { WorkingDirectory = path, FileName = command, Arguments = string.Join(" ", arguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true })) { using (process.StandardOutput) { writer.WriteLine(process.StandardOutput.ReadToEnd()); } using (process.StandardError) { writer.WriteLine(process.StandardError.ReadToEnd()); } } return path; } 

Por ejemplo :

 @"E:\Temp\MyWorkingDirectory".ShellExecute(@"C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\svcutil.exe", Console.Out); 

La respuesta de Judah no funcionó para mí (o no está completa) ya que la aplicación estaba saliendo después de la primera BeginOutputReadLine();

Esto funciona para mí como un fragmento completo, leyendo la salida constante de un 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();