Как создать процесс и записать его 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(); 

Появится, чтобы нормально работать.

Ответ 1

Здесь код, который я проверил для работы. Я использую его для нереста MSBuild и слушаю его вывод:

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

Ответ 2

Я просто пробовал это, и следующее работало для меня:

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

Ответ 3

Похоже, что две из ваших строк вышли из строя. Вы запускаете этот процесс перед настройкой обработчика события для захвата вывода. Возможно, процесс просто заканчивается до добавления обработчика событий.

Переключите строки так.

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

Ответ 4

Мне нужно было захватить как 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 в одну и ту же строку, но вы можете оставить ее отдельно, если это необходимо. Он использует события, поэтому он должен обрабатывать их по мере их поступления (я считаю). Я успешно запустил это приложение и скоро буду тестировать его.

Ответ 5

Вот вам полный и простой код. Это хорошо срабатывало, когда я использовал его.

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

Обратите внимание, что это только фиксирует стандартный вывод; он не фиксирует стандартную ошибку. Если вы хотите оба, используйте эту технику для каждого потока.

Ответ 6

Вам нужно вызвать p.Start() для фактического запуска процесса после установки StartInfo. Как бы то ни было, ваша функция, вероятно, висит на вызове WaitForExit(), потому что процесс никогда не запускался.

Ответ 7

Перенаправление потока является асинхронным и потенциально может продолжаться после завершения процесса. Умар упоминает об отмене после завершения процесса process.CancelOutputRead(). Однако это имеет потенциал потери данных.

Это работает надежно для меня:

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

Я не пробовал этот подход, но мне нравится предложение Sly:

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

Ответ 8

Здесь метод, который я использую для запуска процесса и получает его вывод и ошибки:

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

Например:

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

Ответ 9

Ответ от Judah не работал у меня (или не был полным), поскольку приложение выходило после первого BeginOutputReadLine();

Это работает для меня как полный фрагмент, читающий постоянный вывод пинга:

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