开发者

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

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 ha开发者_运维百科ndler 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.


To do autoflushing of printfs / stdouts

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

This saved my ass...


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!


Well.... you could kick it old-school. Output can be redirected to the input of another program using old-school DOS commands (foo.exe | bar.exe). Write a program that reads from standard in, and you'll get it every time the stream flushes.

Edit

You could also redirect the ouput to a named pipe and read from that. That would also be "as it happens".


Well, I tried a helper class that I know works: http://csharptest.net/browse/src/Library/Processes/ProcessRunner.cs

ProcessRunner runner = new ProcessRunner("iisreset.exe");
runner.OutputReceived += OutputDataReceived;
runner.Start("/RESTART", "/STATUS");

However, this still doesn't solve the problem with this specific executable. It seems that iisreset was written in such a way that this is not possible. Even running the following from the command line:

iisreset.exe /RESTART /STATUS > temp.txt

Still nothing is written to the text file 'temp.txt' until after all services have been restarted.

As for your example code, I would recommend reading a post I wrote some time ago: How to use System.Diagnostics.Process correctly. Specifically you are not reading the std::err stream or redirecting and closing the std::in stream. This can cause very undesirable results in your program. You can look at the example wrapper class linked above for how to do it with the output events, or if you want to directly read the streams you need to use two of your own threads.

    static void Main()
    {
        ProcessStartInfo psi = new ProcessStartInfo(@"C:\Windows\system32\iisreset.exe", "/RESTART /STATUS");
        psi.CreateNoWindow = true;
        psi.UseShellExecute = false;
        psi.RedirectStandardError = true;
        psi.RedirectStandardOutput = true;
        psi.RedirectStandardInput = true;

        ManualResetEvent output_complete = new ManualResetEvent(false);
        ManualResetEvent error_complete = new ManualResetEvent(false);

        Process p = Process.Start(psi);
        new ReadOutput(p.StandardOutput, output_complete);
        new ReadOutput(p.StandardError, error_complete);
        p.StandardInput.Close();

        p.WaitForExit();
        output_complete.WaitOne();
        error_complete.WaitOne();
    }

    private class ReadOutput
    {
        private StreamReader _reader;
        private ManualResetEvent _complete;

        public ReadOutput(StreamReader reader, ManualResetEvent complete)
        {
            _reader = reader;
            _complete = complete;
            Thread t = new Thread(new ThreadStart(ReadAll));
            t.Start();
        }

        void ReadAll()
        {
            int ch;
            while(-1 != (ch = _reader.Read()))
            {
                Console.Write((char) ch);
            }
            _complete.Set();
        }
    }

I wrote this just to see if anything was coming through. Still got nothing until the end, so I think your just SOL on getting asynchronous output from iisreset.


I've had that problem and had to solve it when my logs where too long to read in a single readtoend.

This is what I've done to solve it. It's been doing Ok so far.

            myProcess.StartInfo.FileName = path;
            myProcess.StartInfo.Arguments = args;
            myProcess.StartInfo.UseShellExecute = false;
            myProcess.StartInfo.ErrorDialog = false;
            myProcess.StartInfo.CreateNoWindow = true;
            myProcess.StartInfo.RedirectStandardError = true;
            myProcess.StartInfo.RedirectStandardInput = (stdIn != null);
            myProcess.StartInfo.RedirectStandardOutput = true;
            myProcess.Start();

            int index;
            OpenLogFile(myLog);                      //LOGGGGGGGGGGGGG


            if (myProcess.StartInfo.RedirectStandardInput)
            {
                StreamWriter sw = myProcess.StandardInput;
                sw.Write(stdIn + Convert.ToChar(26));
            }

            StreamReader sr = myProcess.StandardOutput;
            /*stdOut = new ArrayLi
            */
            while (!sr.EndOfStream)
            {                                           //LOGGGGGGGGGGGGG
                Log(sr.ReadLine(), true);
            }

Here's OpenLogFile

    private void OpenLogFile(string fileName)
    {
            if (file == StreamWriter.Null)
            {
                file = new StreamWriter(fileName, true);
                file.AutoFlush = true;
            }
    }

Of course that Log is a function that does something elsewhere. But the solution to you question lies here:

        while (!sr.EndOfStream)
        {                                           //LOGGGGGGGGGGGGG
            Log(sr.ReadLine(), true);
        }

while stream reader is still reading, you can be writing it down as the log comes out.


For my specific situation, the solution is what Mr Moses suggested in a comment above, i.e. run iisreset /stop followed by iisreset /start.

I need a proper answer, rather than a comment, in order to mark it as my "accepted answer", so this answer is more of administrativa than a new contribution. The cred should go to Mr Moses.. :-)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜