Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture output of process synchronously (i.e. "when it happens")

Tags:

c#

.net

I am trying to start a process and capture the output, have come a far way, but am not quite at the solution I'd want.

Specifically, I am trying to reset the IIS on my development machine from a small utility application that I am writing. I have come to the conclusion, by experimenting, that the safe way to do this is by running iisreset.exe in a child process.

If you run iisreset.exe on a command prompt, you get feedback during the process. Running iisreset takes several seconds, and several lines of feedback is generated, with pauses in between.

I'd like to capture this feedback and present it in my Windows Forms application (in a ListBox), and I have succeeded with that. My remaining concern is that I dont get it until the child process finishes. I'd like to get the output from the child process, line by line, immediately when the lines are created.

I have tried to do my homework, reading/testing things from e.g. these:

  • How to spawn a process and capture its STDOUT in .NET?
  • Capturing console output from a .NET application (C#)
  • http://www.aspcode.net/ProcessStart-and-redirect-standard-output.aspx

and several more with similar content. Most (all?) get the output asynchronously (e.g. with Process.ReadToEnd()). I want the output synchonously, which acording to the MSDN documentation involves establishing an event handler etc and I've tried that. It works, but the event handler does not get called until the process exits. I get the output from iisreset.exe, but not until it has finished.

To rule out the possibility that this has something to do with iisreset.exe in particular, I wrote a small console application that generates some output, pausing in between:

namespace OutputGenerator
{
    class Program
    {
        static void Main(string[] args)
        {
            System.Console.WriteLine("OutputGenerator starting and pausing for 10 seconds..");
            System.Threading.Thread.Sleep(10000);
            System.Console.WriteLine("Pausing for another 10 seconds..");
            System.Threading.Thread.Sleep(10000);
            System.Console.WriteLine("Exiting!");
        }
    }
}

Testing with this it turns out that I get captured data diretly when I want. So, to some extent it seems that the way iisreset.exe outputs the data come into play here.

Here is the code of the program (a Windows Forms application) that does the capture:

using System;
using System.Windows.Forms;
using System.Diagnostics; 

namespace OutputCapturer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnRun_Click(object sender, EventArgs e)
        {
            // Running this will show all output after the process has exited
            //String path = @"C:\Windows\system32\iisreset.exe";

            // Running this will show all output "when it happens"
            String path = @"C:\OutputGenerator.exe";

            var p = new Process();
            p.StartInfo.FileName = path;
            p.StartInfo.UseShellExecute = false;  // ShellExecute = true not allowed when output is redirected..
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.CreateNoWindow = true;
            p.OutputDataReceived += OutputDataReceived;
            p.Start();
            p.BeginOutputReadLine();
        }

        private delegate void OutputDataToTextboxDelegate(String s);

        void OutputDataToTextbox(String s)
        {
            tbxOutput.Text += s + Environment.NewLine;
            tbxOutput.Refresh();
        }

        private void OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (e.Data != null && e.Data.ToString() != "")
            {
                // Must run the update of the textbox in the same thread that created it..
                tbxOutput.Invoke(
                    new OutputDataToTextboxDelegate(OutputDataToTextbox), 
                    DateTime.Now.ToString() + ": " + e.Data.ToString()
                );
            }
        }
    }
}

Thinking it was an EOL-encoding problem (the output of iisreset.exe apearing as one line to my app)), I ran a debug session. Nope. The event handler for StandardOutput gets called several times (one time for each output line from iisreset.exe), buth these calls come in one burst after the process exits.

I would LOVE if I could get the output from iisreset.exe "when it happens" so that I can show it as a progress indication.

I've seen one other thread with the same/similar problem, Asynchronous capture from a process output not working properly , but w/o a solution.

I'm sort of stumped.

like image 253
johanekdahl Avatar asked Jul 06 '11 14:07

johanekdahl


2 Answers

To do autoflushing of printfs / stdouts

C equivalent of autoflush (flush stdout after each write)?

This saved my ass...

like image 146
Mich Avatar answered Nov 04 '22 00:11

Mich


It seems that sixlettervariables is correct, and that this has something to do with iisreset.exe isn't flushing it's buffers for each line. (I still wonder what makes it work on a plain command line - i.e. what does cmd.exe do?)

Anyhow.. I tried what apacay suggested, and wrote this:

private void btnRun_Click(object sender, EventArgs e)
{
    // Running this will show the output after the process has finished
    //String path = @"C:\Windows\system32\iisreset.exe";

    // Running this will show all output "when it happens"
    String path = @"C:\OutputGenerator.exe";

    var p = new Process();
    p.StartInfo.FileName = path;
    p.StartInfo.UseShellExecute = false;  // ShellExecute = true not allowed when output is redirected..
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.CreateNoWindow = true;
    p.Start();

    StreamReader sr = p.StandardOutput;
    while (!sr.EndOfStream)
    {
        String s = sr.ReadLine();
        if (s != "")
        {
            tbxOutput.Text += DateTime.Now.ToString() + ": " + s + Environment.NewLine;
        }
        tbxOutput.Refresh();
    }
}

Notice that I am timestamping when I get each line. For my OutputGenerator I get this:

2011-07-06 17:49:11: OutputGenerator starting and pausing for 10 seconds..
2011-07-06 17:49:21: Pausing for another 10 seconds..
2011-07-06 17:49:31: Exiting!

And for iisreset.exe I get this:

 2011-07-06 17:57:11: Attempting stop... 
 2011-07-06 17:57:11: Internet services successfully stopped
 2011-07-06 17:57:11: Attempting start... 
 2011-07-06 17:57:11: Internet services successfully restarted

Running iisreset.exe on the command line, those lines come with pauses in between, over a span of perhaps 10 seconds.

The case seems more or less closed now. Not that I am all that satisfied, but I'm at roads end it seems. I'll reluctantly live with it..

To summarise: In the general case, it is quite possible to capture output synchronously with when it is generated. This thread presents code for two ways to do that - by establishing an event handler, and by "polling" the stream. In my specific case there is something with how iisreset.exe generates output that prevents this.

Thanks to those who participated and contributed!

like image 44
johanekdahl Avatar answered Nov 04 '22 02:11

johanekdahl