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.


 

WPF-Host-for-PowerShell.zip

Comments (3)

  1. David Lively says:

    THANK YOU. I've been having trouble capturing io from console apps launched from within a hosted powershell instance. After two days of searching, I found your example which does exactly what I need.

  2. DotNetInterop says:

    Glad it helped.

  3. swpark says:

    Thank you so much for posting this. This is what I'm trying to implement as David. In the meantime, I'm not familiar with WPF so I changed Windows1 to PowerShellCaller like below in order to get output result,

    public class PowerShell

       {

           delegate void MyAppendCallback(string s);

           /// <summary>

           /// shell

           /// </summary>

           Shell shell;

           StringBuilder textBuffer = new StringBuilder();

           MyAppendCallback AppendDelegate = null;

           /// <summary>

           /// Constructor

           /// </summary>

           public PowerShell()

           {

               AppendDelegate = new MyAppendCallback(MyAppend);

               shell = new PowerShellHelper(new Shell.OutputReceived(StdoutReceived), new Shell.OutputReceived(StderrReceived));

           }

           private void MyAppend(string s)

           {

               AppendDelegate(s);

               this.textBuffer.Append(s);

           }

           void StdoutReceived(string t)

           {

               MyAppend(t);

           }

           void StderrReceived(string t)

           {

               MyAppend(t);

           }

           /// <summary>

           /// Execute powershell command

           /// </summary>

           /// <param name="cmd"></param>

           public void Execute(string cmd)

           {

               shell.Input.WriteLine(cmd);

               shell.Input.Flush();

           }

           /// <summary>

           /// Return output after powershell command execution

           /// </summary>

           /// <returns></returns>

           public string Output()

           {

               return this.textBuffer.ToString();

           }

           /// <summary>

           /// Terminate Powrshell Execution.

           /// </summary>

           public void Exit()

           {

               shell.Stop();

           }

       }

    However, when I call Output() to collect the buffer, it doesn't look like it returns the output correctly. I think I have a problem while converting from Windows1 to PowerShellCaller. Could you tell me how to convert below for getting output correctly from PowerShellCaller?

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

           {

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

               {

                   this.Dispatcher.BeginInvoke

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

                   return;

               }

               lock (this.Scroller1)

               {

                   if (s != "")

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

                   this.Scroller1.ScrollToBottom();

               }

           }

    Thank you.