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.