11 April 2008

WPF invokes Powershell.exe as an inferior shell

I wrote this up as an example of how to run powershell.exe as an inferior shell from within a WPF app.

It is not a "Powershell host" in the normal sense of the word, with a RunSpace and a RunSpaceFactory and so on.  Instead, this example uses System.Diagnostics.Process to start powershell.exe as a process, and redirect stdin, stdout, and stderr.  The result of those things is displayed in a WPF TextBlock.

The key bit is the Shell class, which I show here.  It includes async i/o for the output. 

    1 using System;

    2 using System.Collections.Generic;

    3 using System.Linq;

    4 using System.Text;

    5 

    6 namespace WPF_Host_for_PowerShell

    7 {

    8     class Shell

    9     {

   10         public delegate void OutputReceived(String output);

   11 

   12         private System.Diagnostics.Process _p;

   13         private System.AsyncCallback _rc = null;

   14         private System.IO.StreamWriter _sw;

   15 

   16         public System.IO.StreamWriter Input { get { return _sw; } }

   17         public OutputReceived StdoutOutputReceived { get; set; }

   18         public OutputReceived StderrOutputReceived { get; set; }

   19 

   20         public Shell(String exe, string args, OutputReceived callback1, OutputReceived callback2)

   21         {

   22             _rc = new System.AsyncCallback(ReadCompleted);

   23             StdoutOutputReceived = callback1;

   24             StderrOutputReceived = callback2;

   25             Launch(exe, args);

   26         }

   27 

   28 

   29         public class StreamState

   30         {

   31             public System.IO.Stream Stream;

   32             public byte[] Buffer;

   33             public OutputReceived Callback;

   34 

   35             public const int DefaultBufferSize = 2048;

   36 

   37             public StreamState(System.IO.Stream stream, OutputReceived callback)

   38             {

   39                 Buffer = new byte[DefaultBufferSize];

   40                 Stream = stream;

   41                 Callback = callback;

   42             }

   43         }

   44 

   45 

   46         private void ReadCompleted(System.IAsyncResult asyncResult)

   47         {

   48             StreamState state = (StreamState)asyncResult.AsyncState;

   49             int BytesRead = state.Stream.EndRead(asyncResult);

   50             if (BytesRead > 0)

   51             {

   52                 if (state.Callback != null)

   53                     state.Callback(System.Text.Encoding.ASCII.GetString(state.Buffer, 0, BytesRead));

   54 

   55                 System.Threading.Thread.Sleep(20);

   56                 // repeat:

   57                 state.Stream.BeginRead(state.Buffer,

   58                     0,

   59                     state.Buffer.Length,

   60                     _rc,

   61                     state);

   62             }

   63 

   64         }

   65 

   66 

   67 

   68         private void KickoffAsyncReading(System.IO.Stream stream, OutputReceived callback)

   69         {

   70             // initialize the state for this async read loop:

   71             StreamState state = new StreamState(stream, callback);

   72             System.Threading.Thread.Sleep(20);

   73             stream.BeginRead(state.Buffer,  // where to put the results

   74                 0,                          // offset

   75                 state.Buffer.Length,        // how many bytes (BUFFER_SIZE)

   76                 _rc,                        // ReadCompleted call back delegate

   77                 state);                    // local state object

   78         }

   79 

   80 

   81         private void Launch(string shellexe, string args)

   82         {

   83             _p = new System.Diagnostics.Process();

   84             _p.StartInfo.FileName = shellexe;

   85             _p.StartInfo.Arguments = args;

   86             _p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;

   87             _p.StartInfo.CreateNoWindow = true;

   88 

   89             _p.StartInfo.RedirectStandardOutput = true;

   90             _p.StartInfo.RedirectStandardError = true;

   91             _p.StartInfo.RedirectStandardInput = true;

   92             _p.StartInfo.UseShellExecute = false// required for redirect

   93 

   94             _p.Start();

   95 

   96             _sw = _p.StandardInput;

   97             KickoffAsyncReading(_p.StandardOutput.BaseStream, StdoutOutputReceived);

   98             KickoffAsyncReading(_p.StandardError.BaseStream, StderrOutputReceived);

   99         }

  100 

  101 

  102         public void Stop()

  103         {

  104             try

  105             {

  106                 _p.Kill();

  107             }

  108             catch { }

  109         }

  110     }

  111 }

To use it, it looks something like this:

    1 using System;

    2 using System.Collections.Generic;

    3 using System.Linq;

    4 using System.Text;

    5 using System.Windows;

    6 using System.Windows.Controls;

    7 using System.Windows.Data;

    8 using System.Windows.Documents;

    9 using System.Windows.Input;

   10 using System.Windows.Media;

   11 using System.Windows.Media.Imaging;

   12 using System.Windows.Navigation;

   13 using System.Windows.Shapes;

   14 

   15 namespace WPF_Host_for_PowerShell

   16 {

   17     /// <summary>

   18     /// Interaction logic for Window1.xaml

   19     /// </summary>

   20     public partial class Window1 : Window

   21     {

   22         delegate void MyAppendCallback(string s, System.Windows.Media.Brush color);

   23 

   24         Shell shell;

   25         TextBlock currentTextBlock= null;

   26         MyAppendCallback AppendDelegate = null;

   27 

   28         public Window1()

   29         {

   30             InitializeComponent();

   31 

   32             AppendDelegate= new MyAppendCallback(MyAppend);

   33         }

   34 

   35         private void MyAppend(string s, System.Windows.Media.Brush color)

   36         {

   37             if (this.Scroller1.Dispatcher.Thread != System.Threading.Thread.CurrentThread)

   38             {

   39                 this.Dispatcher.BeginInvoke

   40                     (System.Windows.Threading.DispatcherPriority.Normal, AppendDelegate, s, new object[] { color });

   41                 return;

   42             }

   43 

   44             lock (this.Scroller1)

   45             {

   46                 if (s != "")

   47                     this.currentTextBlock.Inlines.Add(new Run() { Text = s, Foreground = color });

   48 

   49                 this.Scroller1.ScrollToBottom();

   50             }

   51         }

   52 

   53         void StdoutReceived(string t)

   54         {

   55             MyAppend(t, System.Windows.Media.Brushes.BlueViolet);

   56         }

   57 

   58         void StderrReceived(string t)

   59         {

   60             MyAppend(t, System.Windows.Media.Brushes.Red);

   61         }

   62 

   63 

   64         private void Window_Loaded(object sender, RoutedEventArgs e)

   65         {

   66             // creating the shell starts it up

   67             shell = new Powershell(

   68                 new Shell.OutputReceived(StdoutReceived),

   69                 new Shell.OutputReceived(StderrReceived));

   70 

   71             InitCommand();

   72 

   73             shell.Input.WriteLine("prompt");

   74             shell.Input.Flush();

   75         }

   76 

   77         private void InitCommand()

   78         {

   79             // add a new textblock for each command

   80             currentTextBlock = new TextBlock();

   81             currentTextBlock.FontFamily = new FontFamily("Consolas");

   82             currentTextBlock.Margin = new Thickness(0d);

   83             //currentTextBlock.Foreground = color;

   84             this.OutputPanel.Children.Add(currentTextBlock);

   85         }

   86 

   87         private void Button_Click(object sender, RoutedEventArgs e)

   88         {

   89             InitCommand();

   90 

   91             MyAppend(this.textBox1.Text, System.Windows.Media.Brushes.SteelBlue);

   92             shell.Input.WriteLine(this.textBox1.Text);

   93 

   94             shell.Input.WriteLine("prompt");

   95             shell.Input.Flush();

   96         }

   97 

   98         private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)

   99         {

  100             shell.Stop();

  101         }

  102     }

  103 }

The full source is attached. Maybe interesting and re-usable for someone who wants to embed Powershell capability into an application.

 

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

No Comments

Leave a Comment

Comment Policy: No HTML allowed. URIs and line breaks are converted automatically. Your e–mail address will not show up on any public page.

(required) 
(optional)
(required) 

  
Enter Code Here: Required
Page view tracker