Udostępnij za pośrednictwem


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

  • Anonymous
    August 06, 2010
    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.

  • Anonymous
    September 08, 2010
    Glad it helped.

  • Anonymous
    February 09, 2012
    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.