web-dev-qa-db-fra.com

Comment générer un processus et capturer son STDOUT dans .NET?

Je dois créer un processus enfant qui est une application console et capturer sa sortie.

J'ai écrit le code suivant pour une méthode:

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;

Cependant, cela ne retourne rien. Je ne crois pas que l'événement OutputDataReceived a été rappelé ou que la commande WaitForExit() ait bloqué le thread, de sorte qu'il ne sera jamais rappelé.

Aucun conseil?

EDIT: On dirait que j'essayais trop fort avec le rappel. Faire:

return p.StandardOutput.ReadToEnd(); 

Semble bien fonctionner.

132
FlySwat

Voici le code que j'ai vérifié pour fonctionner. Je l'utilise pour générer MSBuild et écouter sa sortie:

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

J'ai juste essayé cette chose et les choses suivantes ont fonctionné pour moi:

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

J'avais besoin de capturer stdout et stderr et de l'exclure si le processus ne se terminait pas comme prévu. Je suis venu avec ceci:

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

Je siffle le stdout et le stderr dans la même chaîne, mais vous pouvez le séparer si nécessaire. Il utilise les événements, donc il devrait les gérer comme ils viennent (je crois). J'ai exécuté cette tâche avec succès et le testerai en volume bientôt.

20
Robb Sadler

Voici un code simple et complet pour le faire. Cela a bien fonctionné quand je l'ai utilisé.

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

Notez que cela ne capture que la sortie standard ; il ne capture pas l'erreur standard . Si vous voulez les deux, utilisez cette technique pour chaque flux.

19
Sam

On dirait que deux de vos lignes sont en panne. Vous démarrez le processus avant de configurer un gestionnaire d'événements pour capturer la sortie. Il est possible que le processus se termine avant l'ajout du gestionnaire d'événements.

Changez les lignes comme ça.

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

Vous devez appeler p.Start () pour exécuter le processus après avoir défini StartInfo. Dans l'état actuel des choses, votre fonction est probablement en attente de l'appel WaitForExit () car le processus n'a jamais été démarré.

3
SoapBox

La redirection du flux est asynchrone et continuera éventuellement après la fin du processus. Umar mentionne l'annulation après la fin du processus process.CancelOutputRead(). Cependant, cela a un potentiel de perte de données.

Cela fonctionne de manière fiable pour moi:

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

Je n'ai pas essayé cette approche mais j'aime bien la suggestion de Sly:

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

La réponse de Judah n'a pas fonctionné pour moi (ou n'est pas complète) car l'application se fermait après le premier BeginOutputReadLine();

Cela fonctionne pour moi comme un extrait complet, lisant la sortie constante d'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();
0
Craig