Search code examples
c#.netprocessprogressredirectstandardoutput

Redirecting StandardOutput when it updates lines instead of making new ones


I am launching rsync from inside my app. Right now it works fine, it opens a console window, prompts me for my password, and shows file progress as the files copy.

public static int SyncFolder(string sourceFolder, string destFolder)
{

    Process rsync = new Process();
    rsync.StartInfo.FileName = Path.Combine(RsyncPath, "rsync.exe");
    rsync.StartInfo.UseShellExecute = true;
    rsync.StartInfo.Arguments = String.Join(" ", new[] { "-rltzh --progress --chmod=a=rw,Da+x", FileUtils.EncodeParameterArgument(sourceFolder), FileUtils.EncodeParameterArgument(destFolder) });
    rsync.Start();
    rsync.WaitForExit();

    return rsync.ExitCode;
}

The problem is I don't want a separate console window to be opened. I would like to display the text progress inside a control of some type and respond to any prompts (like entering the password) from inside my program itself.

public int SyncFolder(string sourceFolder, string destFolder)
{

    Process rsync = new Process();
    rsync.StartInfo.FileName = Path.Combine(RsyncPath, "rsync.exe");
    rsync.StartInfo.UseShellExecute = false;
    rsync.StartInfo.RedirectStandardInput = true;
    rsync.StartInfo.RedirectStandardOutput = true;
    rsync.StartInfo.RedirectStandardError = true;
    rsync.StartInfo.Arguments = String.Join(" ", new[] { "-rltzh --progress --chmod=a=rw,Da+x", FileUtils.EncodeParameterArgument(sourceFolder), FileUtils.EncodeParameterArgument(destFolder) });

    rsync.ErrorDataReceived += rsync_ErrorDataReceived;
    rsync.OutputDataReceived += rsync_OutputDataReceived;

    rsync.Start();
    BindToUiConrol(rsync.StandardInput);

    rsync.WaitForExit();

    return rsync.ExitCode;
}

private void rsync_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
    //Magic!
}

private void rsync_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
    //Even more Magic!
}

private void BindToUiConrol(StreamWriter standardInput)
{
   if(this.InvokeRequired)
   {
       this.BeginInvoke(new Action<StreamWriter>(BindToUiConrol), standardInput);
       return;
   }

   //Hook up events here so a single line text box dumps it's text in when you hit enter.
}

And this is where I am stuck. If I did not have the %'s that kept updating on the same line I would just have it keep dumping new lines as they come in, but how do I handle the line being re-used with a new value put in to it?

My dream solution would be to just have kind of a console window embedded inside the form itself, similar to what HyperTerminal did in older versions of windows, but I have no idea how do do that in .NET.


Here is a example program to show the issue

public class Program
{

    private static void Main(string[] args)
    {
        Console.Write("1");
        Console.WriteLine();

        Console.Write("2");
        Console.CursorLeft = 0;
        Console.Write("3");
        Console.WriteLine();

        Console.Write("4");
        Console.WriteLine();
    }
}

How do I write a Forms application that will display

1
3
4

in a control after it runs the process.


Solution

  • This is not a trivial problem to solve in general. It has to do with the fact that Windows/Console applications don't really write to Standard Out and Standard Error, they write to the "Console". There are various hacks and things that make Standard Out and Standard Error work ok when you just care about all of the text and all of the error text at once, but nothing works well interactively, because the way things are buffered.

    Follow this discussion for more information:

    Writing a console wrapper in C#?