Search code examples
c#asynchronousredirectstandardoutput

Process output redirection on a single console line


I am writing a C# application that can launch third party command line executables and redirect their standard output to a RichTextBox.

I am able to redirect the output alright by using the process.OutputDataReceived event. I have a queue that I push lines to, and then I use a timer to periodically dump all the queued lines into the RichTextBox.

This works perfectly for some third party programs.

The problem lies with programs that write to a single line of the console multiple times. eg: 5% -> 10% -> 25% etc... (constantly rewriting the same spot) This is done, I suppose, by not putting a line feed character, but rather simply returning to the beginning of the line with a carriage return and writing over the previous characters.

What happens is that the OutputDataReceived does not seem to have any way of indicating if a new line should be applied or not so my RichTextBox output simply has multiple updates on new lines. eg: 5% 10% 25% etc...

Is there any way to tell if the standard output is going on a new line or the same line?


Solution

  • If I understand correctly, you are currently seeing progress updates reported to your RichTextBox control as individual lines, while you would like to emulate the behavior of the console window. I.e. to overwrite the previously output line with the new one, rather than keeping the previous line and adding a new one. With that in mind…

    The .NET types, including things like TextReader and the Process class's handling of output, consider a newline to be any of \r, \n, or \r\n. So even a process that is simply using a \r to overwrite the current line would still be interpreted by OutputDataReceived as starting a new line. Given how consistent this behavior is across .NET, your only real option is to avoid using any built-in line-based I/O mechanisms.

    Fortunately, you can get asynchronous reads from the process directly via StandardOutput, instead of having to handle the line-based OutputDataReceived event. For example:

    async Task ConsumeOutput(TextReader reader, Action<string> callback)
    {
        char[] buffer = new char[256];
        int cch;
    
        while ((cch = await reader.ReadAsync(buffer, 0, buffer.Length)) > 0)
        {
            callback(new string(buffer, 0, cch));
        }
    }
    

    You might call it like this:

    // don't "await", but do keep "task" so that at some later time, you can
    // check the result, "await" it, whatever, to determine the outcome.
    Task task = ConsumeOutput(process.StandardOutput, s =>
        {
            richTextBox1.AppendText(s);
        });
    

    Of course, without any modification the above will just do what you are already getting. But reading the output this way will not do any interpretation of the line-break characters. Instead, they will be present in the string s that is passed to the anonymous method handling each read callback. So you can scan that string yourself, looking for lone carriage returns that indicate that you are starting a new line, but should delete the previous line before adding the new text. Be sure to add (or at least skip) all of the text up to the carriage return first, and then delete the last line, before adding the new text.