Process.start: how to get the output?
Asked Answered
N

10

404

I would like to run an external command line program from my Mono/.NET app. For example, I would like to run mencoder. Is it possible:

  1. To get the command line shell output, and write it on my text box?
  2. To get the numerical value to show a progress bar with time elapsed?
Nakada answered 27/11, 2010 at 13:42 Comment(0)
D
572

When you create your Process object set StartInfo appropriately:

var proc = new Process 
{
    StartInfo = new ProcessStartInfo
    {
        FileName = "program.exe",
        Arguments = "command line arguments to your executable",
        UseShellExecute = false,
        RedirectStandardOutput = true,
        CreateNoWindow = true
    }
};

then start the process and read from it:

proc.Start();
while (!proc.StandardOutput.EndOfStream)
{
    string line = proc.StandardOutput.ReadLine();
    // do something with line
}

You can use int.Parse() or int.TryParse() to convert the strings to numeric values. You may have to do some string manipulation first if there are invalid numeric characters in the strings you read.

Dished answered 27/11, 2010 at 13:57 Comment(6)
I was wondering how you could deal with StandardError ?. BTW I really like this code snippet ! nice and clean.Dissatisfaction
Thanks,But I think I was not clear: Should I add another loop to do so ?Dissatisfaction
@Dissatisfaction - I see. You can create one loop that terminates when both streams reach EOF. That can get a little complicated because one stream will inevitably hit EOF first and you don't want to read from it anymore. You could also use two loops in two different threads.Dished
is it more robust to read until the process itself terminates, rather than waiting for end of streams?Toffey
@Toffey - I don't think so. When the process terminates, its streams will automatically be closed. Also, a process may close its streams long before it terminates.Dished
I am trying to use this code on Ffmpeg, any help, stuck in find out the processes work is completed.Tritheism
B
385

You can process your output synchronously or asynchronously.

1. Synchronous example

static void runCommand()
{
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR"; // Note the /c command (*)
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    process.Start();
    //* Read the output (or the error)
    string output = process.StandardOutput.ReadToEnd();
    Console.WriteLine(output);
    string err = process.StandardError.ReadToEnd();
    Console.WriteLine(err);
    process.WaitForExit();
}

Note that it's better to process both output and errors: they must be handled separately.

(*) For some commands (here StartInfo.Arguments) you must add the /c directive, otherwise the process freezes in the WaitForExit().

2. Asynchronous example

static void runCommand() 
{
    //* Create your Process
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    //* Set your output and error (asynchronous) handlers
    process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
    process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);
    //* Start process and handlers
    process.Start();
    process.BeginOutputReadLine();
    process.BeginErrorReadLine();
    process.WaitForExit();
}

static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
{
    //* Do your stuff with the output (write to console/log/StringBuilder)
    Console.WriteLine(outLine.Data);
}

If you don't need to do complicate operations with the output, you can bypass the OutputHandler method, just adding the handlers directly inline:

//* Set your output and error (asynchronous) handlers
process.OutputDataReceived += (s, e) => Console.WriteLine(e.Data);
process.ErrorDataReceived += (s, e) => Console.WriteLine(e.Data);
Bradleigh answered 20/4, 2015 at 16:31 Comment(9)
gotta love async! I was able to use this code (with a little transcribing) in VB.netHendecahedron
note 'string output = process.StandardOutput.ReadToEnd();' may produce a large string if there is many lines of output; the async example, and the answer from Ferruccio both process the output line by line.Bautzen
Note: your first (synchronous) approach is not correct! You should NOT read both StandardOutput and StandardError synchronously! it will cause dead-locks. atleast one of them must be async.Sunsunbaked
Process.WaitForExit() is thread blocking, thus synchronous. Not the point of the answer, but I thought I might add this. Add process.EnableRaisingEvents = true and make use of the Exited event to be fully asynchronous.Deckhand
Is it not possible to directly redirect? I use all colorization of the sass output?Consulate
You are missing a dispose.Eubanks
@Sunsunbaked Can you explain this? I wouldn't know why that should be the case. To T30: The above async code isn't safe if you need to take the full output when the process exits. Those events keep coming in after the process has exited and the program has moved on. There's no indication whether the data is complete. If in doubt, the output data will always be incomplete! Seen this here. I'm not sure if async is a good solution in this case.Espy
Probably worth mentioning that the Standard Error of the child process ought to go to the Standard Error of this application (e.g. via using (var standardErrorStreamWriter = new StreamWriter(Console.OpenStandardError())) standardErrorStreamWriter.Write(e.Data);). Console.WriteLine writes to the Standard Output instead.Heinrich
This is fantastic. Similar to @RichardBarker, I too was able to use this with some transcribing into VB.Net, and this works EXACTLY how I needed it to! Adding Event Handlers for each OutputDataReceived and ErrorDataReceived and appending the data to Public StringBuilders (using them for multiple shell commands) has allowed me to hook the StdOut/StdErr data and process it to provide feedback to my users! WONDERFUL STUFF!Velocipede
L
28

Alright, for anyone who wants both Errors and Outputs read, but gets deadlocks with any of the solutions, provided in other answers (like me), here is a solution that I built after reading MSDN explanation for StandardOutput property.

Answer is based on T30's code:

static void runCommand()
{
    //* Create your Process
    Process process = new Process();
    process.StartInfo.FileName = "cmd.exe";
    process.StartInfo.Arguments = "/c DIR";
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardOutput = true;
    process.StartInfo.RedirectStandardError = true;
    //* Set ONLY ONE handler here.
    process.ErrorDataReceived += new DataReceivedEventHandler(ErrorOutputHandler);
    //* Start process
    process.Start();
    //* Read one element asynchronously
    process.BeginErrorReadLine();
    //* Read the other one synchronously
    string output = process.StandardOutput.ReadToEnd();
    Console.WriteLine(output);
    process.WaitForExit();
}

static void ErrorOutputHandler(object sendingProcess, DataReceivedEventArgs outLine) 
{
    //* Do your stuff with the output (write to console/log/StringBuilder)
    Console.WriteLine(outLine.Data);
}
Leggat answered 10/5, 2016 at 12:15 Comment(6)
Thanks for adding this. Can I ask what command you were using?Bradleigh
I am developing an app in c# that is designed to launch a mysqldump.exe, show the user every single message the app generates, wait for it to finish and then perform some more tasks. I can't understand what kind of command you are talking about? This entire question is about launching a process from c#.Leggat
if you use two separate handlers you will not get deadlocksCosenza
also in you example, you read the process.StandardOutput only once... right after you start it, but one would want to read it continuously while the process is running, no?Cosenza
@Curbman, I think T30 was asking "what command" because you are firing the process called "cmd.exe" .Pitchford
Strongly related: ProcessStartInfo hanging on “WaitForExit”? Why?Hildegard
D
10

The standard .NET way of doing this is to read from the Process' StandardOutput stream. There is an example in the linked MSDN docs. Similar, you can read from StandardError, and write to StandardInput.

Desta answered 27/11, 2010 at 13:46 Comment(0)
W
6
  1. It is possible to get the command line shell output of a process as described here : http://www.c-sharpcorner.com/UploadFile/edwinlima/SystemDiagnosticProcess12052005035444AM/SystemDiagnosticProcess.aspx

  2. This depends on mencoder. If it ouputs this status on the command line then yes :)

Whereunto answered 27/11, 2010 at 13:48 Comment(0)
D
6

you can use shared memory for the 2 processes to communicate through, check out MemoryMappedFile

you'll mainly create a memory mapped file mmf in the parent process using "using" statement then create the second process till it terminates and let it write the result to the mmf using BinaryWriter, then read the result from the mmf using the parent process, you can also pass the mmf name using command line arguments or hard code it.

make sure when using the mapped file in the parent process that you make the child process write the result to the mapped file before the mapped file is released in the parent process

Example: parent process

    private static void Main(string[] args)
    {
        using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("memfile", 128))
        {
            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryWriter writer = new BinaryWriter(stream);
                writer.Write(512);
            }

            Console.WriteLine("Starting the child process");
            // Command line args are separated by a space
            Process p = Process.Start("ChildProcess.exe", "memfile");

            Console.WriteLine("Waiting child to die");

            p.WaitForExit();
            Console.WriteLine("Child died");

            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryReader reader = new BinaryReader(stream);
                Console.WriteLine("Result:" + reader.ReadInt32());
            }
        }
        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

Child process

    private static void Main(string[] args)
    {
        Console.WriteLine("Child process started");
        string mmfName = args[0];

        using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(mmfName))
        {
            int readValue;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream())
            {
                BinaryReader reader = new BinaryReader(stream);
                Console.WriteLine("child reading: " + (readValue = reader.ReadInt32()));
            }
            using (MemoryMappedViewStream input = mmf.CreateViewStream())
            {
                BinaryWriter writer = new BinaryWriter(input);
                writer.Write(readValue * 2);
            }
        }

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey();
    }

to use this sample, you'll need to create a solution with 2 projects inside, then you take the build result of the child process from %childDir%/bin/debug and copy it to %parentDirectory%/bin/debug then run the parent project

childDir and parentDirectory are the folder names of your projects on the pc good luck :)

Distrustful answered 29/4, 2016 at 17:49 Comment(0)
E
3

You can log process output using below code:

ProcessStartInfo pinfo = new ProcessStartInfo(item);
pinfo.CreateNoWindow = false;
pinfo.UseShellExecute = true;
pinfo.RedirectStandardOutput = true;
pinfo.RedirectStandardInput = true;
pinfo.RedirectStandardError = true;
pinfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
var p = Process.Start(pinfo);
p.WaitForExit();
Process process = Process.Start(new ProcessStartInfo((item + '>' + item + ".txt"))
{
    UseShellExecute = false,
    RedirectStandardOutput = true
});
process.WaitForExit();
string output = process.StandardOutput.ReadToEnd();
if (process.ExitCode != 0) { 
}
Exophthalmos answered 11/1, 2019 at 11:46 Comment(0)
N
3

I was running into the infamous deadlock problem when calling Process.StandardOutput.ReadLine and Process.StandardOutput.ReadToEnd.

My goal/use case is simple. Start a process and redirect it's output so I can capture that output and log it to the console via .NET Core's ILogger<T> and also append the redirected output to a file log.

Here's my solution using the built in async event handlers Process.OutputDataReceived and Process.ErrorDataReceived.

var p = new Process
{
    StartInfo = new ProcessStartInfo(
        command.FileName, command.Arguments
    )
    {
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        UseShellExecute = false,
    }
};


// Asynchronously pushes StdOut and StdErr lines to a thread safe FIFO queue
var logQueue = new ConcurrentQueue<string>();
p.OutputDataReceived += (sender, args) => logQueue.Enqueue(args.Data);
p.ErrorDataReceived += (sender, args) => logQueue.Enqueue(args.Data);

// Start the process and begin streaming StdOut/StdErr
p.Start();
p.BeginOutputReadLine();
p.BeginErrorReadLine();

// Loop until the process has exited or the CancellationToken is triggered
do
{
    var lines = new List<string>();
    while (logQueue.TryDequeue(out var log))
    {
        lines.Add(log);
        _logger.LogInformation(log)
    }
    File.AppendAllLines(_logFilePath, lines);

    // Asynchronously sleep for some time
    try
    {
        Task.Delay(5000, stoppingToken).Wait(stoppingToken);
    }
    catch(OperationCanceledException) {}

} while (!p.HasExited && !stoppingToken.IsCancellationRequested);
Nobody answered 2/6, 2021 at 17:28 Comment(0)
M
2

How to launch a process (such as a bat file, perl script, console program) and have its standard output displayed on a windows form:

processCaller = new ProcessCaller(this);
//processCaller.FileName = @"..\..\hello.bat";
processCaller.FileName = @"commandline.exe";
processCaller.Arguments = "";
processCaller.StdErrReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.StdOutReceived += new DataReceivedHandler(writeStreamInfo);
processCaller.Completed += new EventHandler(processCompletedOrCanceled);
processCaller.Cancelled += new EventHandler(processCompletedOrCanceled);
// processCaller.Failed += no event handler for this one, yet.

this.richTextBox1.Text = "Started function.  Please stand by.." + Environment.NewLine;

// the following function starts a process and returns immediately,
// thus allowing the form to stay responsive.
processCaller.Start();    

You can find ProcessCaller on this link: Launching a process and displaying its standard output

Mckibben answered 22/7, 2018 at 22:34 Comment(0)
A
1

The solution that worked for me in win and linux is the folling

// GET api/values
        [HttpGet("cifrado/{xml}")]
        public ActionResult<IEnumerable<string>> Cifrado(String xml)
        {
            String nombreXML = DateTime.Now.ToString("ddMMyyyyhhmmss").ToString();
            String archivo = "/app/files/"+nombreXML + ".XML";
            String comando = " --armor --recipient [email protected]  --encrypt " + archivo;
            try{
                System.IO.File.WriteAllText(archivo, xml);                
                //String comando = "C:\\GnuPG\\bin\\gpg.exe --recipient [email protected] --armor --encrypt C:\\Users\\Administrador\\Documents\\pruebas\\nuevo.xml ";
                ProcessStartInfo startInfo = new ProcessStartInfo() {FileName = "/usr/bin/gpg",  Arguments = comando }; 
                Process proc = new Process() { StartInfo = startInfo, };
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardError = true;
                proc.Start();
                proc.WaitForExit();
                Console.WriteLine(proc.StandardOutput.ReadToEnd());
                return new string[] { "Archivo encriptado", archivo + " - "+ comando};
            }catch (Exception exception){
                return new string[] { archivo, "exception: "+exception.ToString() + " - "+ comando };
            }
        }
Aleris answered 24/9, 2019 at 17:29 Comment(1)
exceptions caught by general catch(Exception) must be re-thrown, otherwise it swallows the exception which can be awaited by "upper" code. In the given example the debugger will not stop on exception if it happened inside try blockRosy

© 2022 - 2024 — McMap. All rights reserved.