Search code examples
c#http-redirectconsoleconsole-output

Redirecting command prompt process output to WPF TextBox


I am attempting to redirect a process running a command prompt to output to a WPF textbox. I found some very helpful code in a previous question here that got me most of the way. The redirect works, but only up until the end of the first line. For instance. If I direct the process to run cmd.exe /c ipconfig, only "Windows IP Configuration" is added to the textbox and not the full output of the command. The classes that deal with redirection are identical to the response in the previous question:

public class ConsoleInputReadEventArgs : EventArgs
{
    public ConsoleInputReadEventArgs(string input)
    {
        this.Input = input;
    }

    public string Input { get; private set; }
}

public interface IConsoleAutomator
{
    StreamWriter StandardInput { get; }

    event EventHandler<ConsoleInputReadEventArgs> StandardInputRead;
}

public abstract class ConsoleAutomatorBase : IConsoleAutomator
{
    protected readonly StringBuilder inputAccumulator = new StringBuilder();

    protected readonly byte[] buffer = new byte[1024];

    protected volatile bool stopAutomation;

    public StreamWriter StandardInput { get; protected set; }

    protected StreamReader StandardOutput { get; set; }

    protected StreamReader StandardError { get; set; }

    public event EventHandler<ConsoleInputReadEventArgs> StandardInputRead;

    protected void BeginReadAsync()
    {
        if (!this.stopAutomation)
        {
            this.StandardOutput.BaseStream.BeginRead(this.buffer, 0, this.buffer.Length, this.ReadHappened, null);
        }
    }

    protected virtual void OnAutomationStopped()
    {
        this.stopAutomation = true;
        this.StandardOutput.DiscardBufferedData();
    }

    private void ReadHappened(IAsyncResult asyncResult)
    {
        var bytesRead = this.StandardOutput.BaseStream.EndRead(asyncResult);
        if (bytesRead == 0)
        {
            this.OnAutomationStopped();
            return;
        }

        var input = this.StandardOutput.CurrentEncoding.GetString(this.buffer, 0, bytesRead);
        this.inputAccumulator.Append(input);

        if (bytesRead < this.buffer.Length)
        {
            this.OnInputRead(this.inputAccumulator.ToString());
        }

        this.BeginReadAsync();
    }

    private void OnInputRead(string input)
    {
        var handler = this.StandardInputRead;
        if (handler == null)
        {
            return;
        }

        handler(this, new ConsoleInputReadEventArgs(input));
        this.inputAccumulator.Clear();
    }
}

public class ConsoleAutomator : ConsoleAutomatorBase, IConsoleAutomator
{
    public ConsoleAutomator(StreamWriter standardInput, StreamReader standardOutput)
    {
        this.StandardInput = standardInput;
        this.StandardOutput = standardOutput;
    }

    public void StartAutomate()
    {
        this.stopAutomation = false;
        this.BeginReadAsync();
    }

    public void StopAutomation()
    {
        this.OnAutomationStopped();
    }
}

I am creating my process and using an event handler to direct the output to the textbox in my GUI here:

private void runCMD(string command)
    {
        var processStartInfo = new ProcessStartInfo
        {
            FileName = "cmd.exe",
            Verb = "runas",
            Arguments = "/C " + command,
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            UseShellExecute = false
        };

        var process = Process.Start(processStartInfo);
        var automator = new ConsoleAutomator(process.StandardInput, process.StandardOutput);

        automator.StandardInputRead += AutomatorStandardInputRead;
        automator.StartAutomate();

        process.WaitForExit();
        automator.StandardInputRead -= AutomatorStandardInputRead;
        process.Close();
    }

    private void AutomatorStandardInputRead(object sender, ConsoleInputReadEventArgs e)
    {
        this.Dispatcher.Invoke(() => {
            consoleWindow.Text += e.Input.ToString();
        });
    }

Can someone explain what I am missing? Thank you in advance.


Solution

  • I have an off-beat suggestion, but I have used it for something similar.

    In the System.Diagnostics namespace, there is a class called Trace.

    Trace is for diagnostics, but its great at message relay.

    you write messages to the buffer like this:

    System.Diagnostics.Trace.TraceInformation($"Something happened at {DateTime.Now}");
    

    to subscribe or "listen" to the stream of messages, you can:

    using System.Diagnostics;
    // ## IMPORTANT - set 'AutoFlush' or you will have to call Flush() manually at some interval
    Trace.AutoFlush = true;
    // log to console.
    Trace.Listeners.Add(new ConsoleWriterTraceListener(true));
    // log to file.
    Trace.Listeners.Add(new TextWriterTraceListener(@"c:\temp\applog.txt"));
    // write something custom by inheriting from the base class.
    Trace.Listeners.Add(new YourCustomTraceListener());
    

    The beauty of this is the async message relay stuff is all handled by the framework, you just need to send and receive messages.

    NOTE: You can also manage the set of listeners in the config file:

    <system.diagnostics>
        <trace>
            <listeners autoflush="true" >
               <add type="System.Diagnostics.TextWriteTraceListener" initialData="c:\temp\applog.txt" name="default" />
            </listeners>
        </trace>
    </system.diagnostics>