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.
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.