2

I receive console output in my application. I use code from here (the accepted solution). But I never get null in my OutputDataReceived. Instead, I have String.Empty at the end of the output. Would it be correct to use String.NullOrEmpty instead of just comparing to null?

static void Main(string[] args)
        {
            var command = @"wmic cpu get loadpercentage";
            using (Process process = new Process())
            {
                process.StartInfo.FileName = "cmd.exe";
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.CreateNoWindow = true;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardInput = true;
                process.StartInfo.RedirectStandardError = true;

                StringBuilder output = new StringBuilder();
                StringBuilder error = new StringBuilder();

                using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false))
                using (AutoResetEvent errorWaitHandle = new AutoResetEvent(false))
                {
                    process.OutputDataReceived += (sndr, a) =>
                    {
                        if (a.Data == null)
                        {
                            outputWaitHandle.Set();
                        }
                        else
                        {
                            output.AppendLine(a.Data);
                        }
                    };
                    process.ErrorDataReceived += (sndr, a) =>
                    {
                        if (a.Data == null)
                        {
                            errorWaitHandle.Set();
                        }
                        else
                        {
                            error.AppendLine(a.Data);
                        }
                    };

                    process.Start();
                    process.BeginOutputReadLine();    
                    outputWaitHandle.WaitOne();

                    string path = "Test.txt";
                    using (StreamWriter sw = File.Exists(path) ? File.AppendText(path) : File.CreateText(path))
                    {
                        sw.WriteLine(String.Format("{0}, {1}", DateTime.Now, output));

                    }

                }
            }
        }

Update: It seems that it won't work multiline output. The question is why there's no null in a.Data

Community
  • 1
  • 1
amplifier
  • 1,793
  • 1
  • 21
  • 55
  • You should always get a `null` at the end. The reason you're not receiving it is that you don't wait for the process to terminate. You need a `process.WaitForExit()` somewhere after the `process.Start()` (particularly in the case of the race where the process exits before `BeginOutputReadLine` is called). – Cameron Jul 13 '15 at 17:58
  • @Cameron but my process (cmd.exe) never exits. I want to type multiple commands using process.StandardInput.WriteLine and receive the corresponding output – amplifier Jul 13 '15 at 18:02
  • 1
    the code you posted doesn't use the command variable after setting it, your new process might be doing nothing – stackuser83 Jul 13 '15 at 18:04
  • 2
    If the process never exits, then its stdout will stay open (by default, anyway) and you'll never receive null. What exactly are you trying to accomplish with these auto-reset events? – Cameron Jul 13 '15 at 18:07
  • @stackuser83 sorry, forgot to remove command. Here what I try to achieve is only to get the string "Microsoft windows [Version 6.1.7601 ]" etc – amplifier Jul 13 '15 at 18:09
  • @cameron I want to open cmd.exe with Process and "emulate" the console interaction with the user (by redirecting input and ouput). Say, in the code I can call process.StandardInput.WriteLine("ping localhost") and I want to receive the output as command ends. Than I can type other command – amplifier Jul 13 '15 at 18:12
  • 2
    @amplifier No is the answer to question as asked - as Cameron pointed out CMD does not stop in your code (and it looks like exactly what you want implementing some sort of remote CMD / telnet) - so it will never have "end of output". Additionally there is no "end of command execution" marker of any kind, so you really can't see if "ping" or any other command finished or waiting for user input or opened its own sub-prompt like "ftp". – Alexei Levenkov Jul 13 '15 at 18:18
  • if that is all you need, try https://msdn.microsoft.com/en-us/library/system.environment.osversion%28v=vs.110%29.aspx which provides both a descriptive string as well as data typed attribute variables indicating the host OS – stackuser83 Jul 13 '15 at 18:22
  • @stackuser83 I took "Microsoft windows [Version 6.1.7601 ]" only as example – amplifier Jul 13 '15 at 18:31

1 Answers1

2

The standard output stream is only terminated with the process itself is terminated. Note that here, "process" means the process running cmd.exe. That process can start other processes, but they have their own standard output stream, and while those process's stdout will be terminated when they themselves terminate, none of that affects the parent cmd.exe process's stdout.

If you want to run a process that runs various other processes by emulating user input to cmd.exe, then you will also have to emulate the user interaction that would recognize and respond to the final command being executed.

Imagine yourself in a real user's place and think about how they deal with the situation. When they execute a command in a command-prompt window, does that window close when the command has completed? No. Do they receive any sort of indication that the command has completed? Sort of. First, they are presumably expected the output of the command to conform to some specific format, which often will include some indicator that the command has completed. Second, the command-prompt window will display a new prompt (i.e. the "prompt" in the phrase "command-prompt").

Note that a malicious command could figure out what the current prompt looks like and fake it. But that's unusual and presumably you have enough control over the commands you're issuing to avoid that. So one approach is to simply process the output as it occurs and detect the new prompt that appears when the command has completed.

If that doesn't seem reliable enough, then you will have to handle each command individually, interpreting the output from the command and recognizing when that command has reached the end of its output solely from the content in that output.

Finally note that you can in fact use the Process class to execute the commands themselves. You don't need cmd.exe to do that. And if you execute each command as a separate process yourself, then you do wind up getting notification of the termination of the process via the end-of-stream of the stdout for each process.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136