Friday, October 28, 2011

Capturing Console output without interfering with it

Recently I've had to tackle the following problem: given an existing .NET console application, send the commandline output to an email address when the application is finished. Sounded reasonably simple, right? And in the end it was.

Options...

The key to making this work is finding out that the .NET implementation of System.Console has access to the normal stdin, stdout and stderr console streams wrapped in a TextWriter.

If you then find out that Console has methods like SetOut()for changing the TextWriters that are used for this, you have way of capturing text that the existing console application writes out using Console.Writeline() and related methods so you can email it.

...that leave existing output as it

But wait, functionality for the application should remain the same, so this output should stilll remain visible on the console too. Here design patterns come to our aid, in this case the Decorator pattern. The class we need is the following:

using System;
using System.IO;
using System.Text;

namespace peSHIr
{
  public class StringBufferPassthroughWriter : TextWriter
  {
     private TextWriter wrapped;
     private StringBuilder buffer;

     public StringBufferPassthroughWriter(TextWriter wrap)
     {
        wrapped = wrap;
        buffer = new StringBuilder();
     }

     public override Encoding Encoding { get { return wrapped.Encoding; } }
     public override void Write(string value) { wrapped.Write(value); buffer.Append(value); }
     public override void WriteLine(string value) { wrapped.WriteLine(value); buffer.AppendLine(value); }
     public override void WriteLine() { wrapped.WriteLine(); buffer.AppendLine(); }

     public string Buffer { get { return buffer.ToString(); } }
  }
}

What does the above class do? Well, not very much. All it does is decorate another TextWriter. That is: it derives from TextWriter, taking another TextWriter in its constructor which it stores, and then it simply passes through all method calls that write elementary pieces of text to the stored TextWriter. This makes sure it does not interfere with whatever that other TextWriter would normally be doing.

And it does one extra thing: keep an instance of a StringBuilder as well, and also fill this from the same overridden methods. Finally, our new TextWriter decorator supplies a read-only property to get at the stored string in the StringBuilder buffer.

How to use this

It should now probably be fairly obvious now how to actually "insert" this decorator in our existing console application to enable it to send an email with its complete output without interfering with that same output. I'll show a simple "before" and "after" comparison of the Main() method for the application.

Before

using System;

static int Main(string[] args)
{
  // Application code, calling Console.WriteLine() etc.
  return 0;
}

After

using System;
using peSHIr;

// Actual EmailSuperUser() method omitted as
// exercise for the reader... ;-)

static int Main(string[] args)
{
  using (var output = new StringBufferPassthroughWriter(Console.Out))
  {
    Console.SetOut(output);
    // Application code, calling Console.WriteLine() etc.
    EmailSuperUser(output.Buffer);
  }
  return 0;
}

And that is basically all. Hope you enjoyed this little code sample.

(For more information on how to write the EmailSuperUser method, see this Scott Guthrie blogpost and/or the System.Net.Mail documentation.)

2 comments:

  1. This is exactly what I needed, thank you. Linked back to this page in my source. Cheers.

    ReplyDelete