Search code examples
c#processautoresetevent

Why is OutputDataReceived not being raised for prompt, preventing AutoResetEvent from being signaled?


I'm making an app that will spawn up a process that has a command-line interpreter. I need to supply commands to this CLI from another machine. Now, I have to detect when the command is finished, so I'm checking for when the CLI's prompt appears in the standard output of the process I'm spawning. Here's a snippet of code:

private string StartProcess(string input)
{
try
{
    StringBuilder output = new StringBuilder();
    StringBuilder error = new StringBuilder();

    AutoResetEvent commandFinished = new AutoResetEvent(false);

    ProcessStartInfo startInfo = new ProcessStartInfo()
    {
        FileName = "myprocess.exe",
        Arguments = "",
        UseShellExecute = false,
        CreateNoWindow = true,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        RedirectStandardInput = true,
        UserName = System.Environment.UserName
    };

    Process myProcess = new Process()
    {
        StartInfo = startInfo,
        EnableRaisingEvents = true
    };

    myProcess.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
    {
        if (e.Data != null)
        {
            string prompt = "user >";

            if (e.Data.Substring(e.Data.Length - prompt.Length).Equals(prompt))
            {
                Console.WriteLine("Received prompt! Sending CommandFinished signal!");
                commandFinished.Set();
                Console.WriteLine("CommandFinished signal set!");
            }
            else
            {
                output.AppendLine(e.Data);
            }
        }
        else
        {
            // Data finished
            Console.WriteLine("StdOut data finished! Sending CommandFinished signal!");
            commandFinished.Set();
            Console.WriteLine("CommandFinished signal set!");
        }
    });


    myProcess.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
    {
        if (e.Data != null)
        {
            Console.WriteLine("Error Data received: " + e.Data.ToString());
            error.AppendLine(e.Data);
        }
    });

    myProcess.Start();
    myProcess.BeginOutputReadLine();
    myProcess.BeginErrorReadLine();

    Console.WriteLine("Executing input command: " + input);
    myProcess.StandardInput.WriteLine(input);

    Console.WriteLine("Waiting for input command to complete...");

    commandFinished.WaitOne();

    Console.WriteLine("Command complete!");

    return output.ToString();
}
catch (Exception ex)
{
    Console.WriteLine("EXCEPTION: " + ex.ToString());
    throw ex;
}
}

Now, the code is hanging on the call to WaitOne(). I'm confused as to why - I don't detect the CLI prompt in the output, and I never get any WriteLines telling me the prompt was received in the OutputDataReceived event, or that the Data received was null. I.e. the OutputDataReceived event isn't being raised when the previous command has completed and the prompt is displayed.

The input command I'm supplying does take a while, but it does finish. Am I using AutoResetEvent wrong here?


Solution

  • Without a good, minimal, complete code example it's impossible to know for sure what might be wrong with the code. But I can see one obvious explanation:

    The OutputDataReceived event is raised for each line of output that is received. That is, a string terminated by a newline character.

    Without specifics about your external process, I can't say for sure. But most CLI type scenarios involve the display of a prompt without a newline character. I.e. the prompt is written to the console and user input is expected to be echoed to the console immediately following the prompt, rather than on the next line.

    If that's the case for your scenario, then almost certainly you are failing to detect the prompt because the event won't be raised for the prompt alone. After the final line of output from the previous command's operation, the next time the event will be raised is after the command has been sent to the process via standard input. This is obviously too late to be useful in knowing when to send that command. :)

    To get this to work, you will have to read input from the process via one of the other mechanisms available that are not line-based.