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

前端 未结 6 1398
灰色年华
灰色年华 2020-12-18 18:50

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

相关标签:
6条回答
  • 2020-12-18 19:21

    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 讨论(0)
  • 2020-12-18 19:29

    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!

    0 讨论(0)
  • 2020-12-18 19:29

    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.

    0 讨论(0)
  • 2020-12-18 19:38

    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.

    0 讨论(0)
  • 2020-12-18 19:41

    To do autoflushing of printfs / stdouts

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

    This saved my ass...

    0 讨论(0)
  • 2020-12-18 19:41

    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".

    0 讨论(0)
提交回复
热议问题