How to add a Graphical User Interface to your PowerShell Functions using the .Net SystemWindowsForm class
In this example. we will walk through taking a standard office task--in this case, checking to see if a system responds to a ping--and build a simple GUI around the function using System.Windows.Forms class available to us in PowerShell from .Net.
Starting simple
To begin, we will create a simple ping testing tool:
$ComputerName = read-host "Enter Computer Name to test:"</code>if (Test-Connection $ComputerName -quiet -Count 2){Write-Host -ForegroundColor Green "Computer $ComputerName has network connection"</code>}Else{Write-Host -ForegroundColor Red "Computer $ComputerName does not have network connection"}
And to test it:
http://foxdeploy.files.wordpress.com/2013/10/pingtool_01.png?w=705Cool, it works!
Now that we know our base code is working, we will create the outline of the GUI we want.
First and foremost, in order to have access to the Systems.Windows.Forms .NET elements we need to draw our GUI, we have to load the assemblies. This is done with the following two lines, added to the top of our script.
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
Void is a .NET parameter that tells the method here to do the operation but be quiet about it. If we did not include this param, we would see noisey output in the console as the Assemblies are loaded.
Next, we will define the basic form onto which our application will be built, calling it $form, and then setting properties for its title (via the .Text property), size and position on screen.
#begin to draw forms$Form = New-Object System.Windows.Forms.Form$Form.Text = "Computer Pinging Tool"$Form.Size = New-Object System.Drawing.Size(300,150)$Form.StartPosition = "CenterScreen"
One of the things we are doing here with the System.Windows.Forms object is to modify its KeyPreview property to $True and then add listeners for certain key presses to have our form respond to them. If we enable KeyPreview, your form itself will intecept a key press and can do things with it before the control that is selected gets the press. So instead of the user having to click the X button or click enter, we can tell the form to do something when the user hits Escape or Enter instead.
With that in mind, lets add a hook into both the Enter and Escape keys to make them function.
$Form.KeyPreview = $True$Form.Add_KeyDown({if ($_.KeyCode -eq "Enter"){$x=$ListBox.SelectedItem;$Form.Close()}})$Form.Add_KeyDown({if ($_.KeyCode -eq "Escape"){$Form.Close()}})
And now, lets comment block out the Ping section of the script (we’ll also use #region and #endregion to allow us to collapse away that block for the time being) and add the following lines to the bottom to display our form.
#Show form$Form.Topmost = $True$Form.Add_Shown({$Form.Activate()})[void] $Form.ShowDialog()
If you’ve been following along from home, you should have something similar to this.
http://foxdeploy.files.wordpress.com/2013/10/pingtool_02.png?w=705&h=470
And now lets give it a try!
http://foxdeploy.files.wordpress.com/2013/10/pingtool_03.png?w=705&h=408
Ah, the sweet taste of progress. We now have a form and some code which works. Lets add a box where a user can specify the computer name to test, and then a button to start the test.
Drawing the forms
We’ll be using the Systems.Windows.Forms.Label (Abbreviated as simply Forms.whatever from now on), Forms. TextBox and Forms.Button controls to add the rest of our app for now. While I’m here, if you’d like a listing of all of the available other .net controls you can make use of, check out this link: http://msdn.microsoft.com/en-us/library/system.windows.forms.aspx
First, lets add a label (a field of uneditable text), in which we will describe what this tool will do. To start, instantiate a new System.Windows.Forms object of type .label, and then we’ll set the location, size and text properties. Finally, we’ll add the control to our form using the .Add() method, as you’ll see below. Add the following text above the commented out Actual Code region.
$label = New-Object System.Windows.Forms.Label$label.Location = New-Object System.Drawing.Size(5,5)$label.Size = New-Object System.Drawing.Size(240,30)$label.Text = "Type any computer name to test if it is on the network and can respond to ping"$Form.Controls.Add($label)
Some notes about this. the Location property here is given in (x,y) with distances being pixels away from the upper left hand corner of the form. Using this method of building a GUI from scratch, it is not uncommon to spend some time fiddling with the sizing by tweaking values and executing, back and forth.
http://foxdeploy.files.wordpress.com/2013/10/pingtool_04.png?w=705&h=383Note the #regions used to make editing our code a bit cleaner
Lets save our script and see what happens!
http://foxdeploy.files.wordpress.com/2013/10/pingtool_05.png?w=705
Alright, next, to throw a textbox on there and add a button to begin the ping test, add the following lines in the ‘#region begin to draw forms’.
$textbox = New-Object System.Windows.Forms.TextBox$textbox.Location = New-Object System.Drawing.Size(5,40)$textbox.Size = New-Object System.Drawing.Size(120,20)#$textbox.Text = "Select source PC:"$Form.Controls.Add($textbox) $OKButton = New-Object System.Windows.Forms.Button$OKButton.Location = New-Object System.Drawing.Size(140,38)$OKButton.Size = New-Object System.Drawing.Size(75,23)$OKButton.Text = "OK"$OKButton.Add_Click($ping_computer_click)$Form.Controls.Add($OKButton) $result_label = New-Object System.Windows.Forms.label$result_label.Location = New-Object System.Drawing.Size(5,65)$result_label.Size = New-Object System.Drawing.Size(240,30)$result_label.Text = "Results will be listed here"$Form.Controls.Add($result_label)
One thing I want to draw attention to is the $OKButton.Add_Click; specifying this property will associate the contents of the $ping_computer_click variable (currently empty) as a function to execute when the button is clicked.
http://foxdeploy.files.wordpress.com/2013/10/pingtool_06.png?w=705Seeing the UI come together is such a satisfying feeling
Tying it all together
Now to move our earlier code for the pinging function into the space of the $ping_computer_click variable, and uncomment it, and while we’re at it, lets change the value of $ComputerName to $textbox.Text. You should have something similar to the following:
http://foxdeploy.files.wordpress.com/2013/10/ep2_pingtool_03.png?w=585&h=323
Alright, and now if we test it (let’s use ‘localhost’ to guarantee the test completes) we should see…
http://foxdeploy.files.wordpress.com/2013/10/ep2_pingtool_04.png?w=585&h=185The test completes and we get console output of what happened. Nice!
Now let’s add a status bar to the tool, so the user will have feedback when the button is pressed.
$statusBar1 = New-Object System.Windows.Forms.StatusBar
$statusBar1.Name = "statusBar1"
$statusBar1.Text = "Ready..."
$form.Controls.Add($statusBar1)
Then, under the $ping_computer_click section, add this line to the top and the other to bottom:
Top | $statusBar1.Text = “Testing…” |
Bottom | $statusBar1.Text = “Testing Complete” |
We’ll also update the logic from the earlier pinging test to have it update the values of the Results label with a message for whether or not the process completed, and also spruce things up with a little color.
$ping_computer_click =
{
#region Actual Code
$statusBar1.Text = "Testing..."
$ComputerName = $textbox.Text
if (Test-Connection $ComputerName -quiet -Count 2){
Write-Host -ForegroundColor Green "Computer $ComputerName has network connection"
$result_label.ForeColor= "Green"
$result_label.Text = "System Successfully Pinged"
}
Else{
Write-Host -ForegroundColor Red "Computer $ComputerName does not have network connection"
$result_label.ForeColor= "Red"
$result_label.Text = "System is NOT Pingable"
}
$statusBar1.Text = "Testing Complete"
#endregion
}
The end result
http://foxdeploy.files.wordpress.com/2013/10/ep2_pingtool_05.png?w=585&h=165
Some final notes:
• If you don’t want the user to be able to resize your beautiful tool, then simply set $form.MaximumSize and $form.MinimumSize equal to $Form.Size.
• In order to have the enter button map to clicking the Okay button, we need to move and alter the following lines to the bottom of the #drawforms region:
$Form.Add_KeyDown({if ($_.KeyCode -eq "Enter"){& $ping_computer_click}})
$Form.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$Form.Close()}})
The Ampersand (&) has a special meaning in PowerShell. It means Invoke, or Execute. If we didn’t use this, PS would attempt to display the content of the variable instead.
I hope you’ve enjoyed this demonstration/walkthrough.
The full code
For your convenience, the full code is provided here.
01.#region Boring beginning stuff
02.[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
03.[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
04.#endregion
05.
06.#region begin to draw forms
07.$Form = New-Object System.Windows.Forms.Form
08.$Form.Text = "Computer Pinging Tool"
09.$Form.Size = New-Object System.Drawing.Size(300,170)
10.$Form.StartPosition = "CenterScreen"
11.$Form.KeyPreview = $True
12.$Form.MaximumSize = $Form.Size
13.$Form.MinimumSize = $Form.Size
14.
15.$label = New-Object System.Windows.Forms.label
16.$label.Location = New-Object System.Drawing.Size(5,5)
17.$label.Size = New-Object System.Drawing.Size(240,30)
18.$label.Text = "Type any computer name to test if it is on the network and can respond to ping"
19.$Form.Controls.Add($label)
20.$textbox = New-Object System.Windows.Forms.TextBox
21.$textbox.Location = New-Object System.Drawing.Size(5,40)
22.$textbox.Size = New-Object System.Drawing.Size(120,20)
23.#$textbox.Text = "Select source PC:"
24.$Form.Controls.Add($textbox)
25.
26.$ping_computer_click =
27.{
28.#region Actual Code
29.
30.$statusBar1.Text = "Testing..."
31.$ComputerName = $textbox.Text
32.
33.if (Test-Connection $ComputerName -quiet -Count 2){
34.Write-Host -ForegroundColor Green "Computer $ComputerName has network connection"
35.$result_label.ForeColor= "Green"
36.$result_label.Text = "System Successfully Pinged"
37.}
38.Else{
39.Write-Host -ForegroundColor Red "Computer $ComputerName does not have network connection"
40.$result_label.ForeColor= "Red"
41.$result_label.Text = "System is NOT Pingable"
42.}
43.
44.$statusBar1.Text = "Testing Complete"
45.#endregion
46.}
47.
48.$OKButton = New-Object System.Windows.Forms.Button
49.$OKButton.Location = New-Object System.Drawing.Size(140,38)
50.$OKButton.Size = New-Object System.Drawing.Size(75,23)
51.$OKButton.Text = "OK"
52.$OKButton.Add_Click($ping_computer_click)
53.$Form.Controls.Add($OKButton)
54.
55.$result_label = New-Object System.Windows.Forms.label
56.$result_label.Location = New-Object System.Drawing.Size(5,65)
57.$result_label.Size = New-Object System.Drawing.Size(240,30)
58.$result_label.Text = "Results will be listed here"
59.$Form.Controls.Add($result_label)
60.
61.$statusBar1 = New-Object System.Windows.Forms.StatusBar
62.$statusBar1.Name = "statusBar1"
63.$statusBar1.Text = "Ready..."
64.$form.Controls.Add($statusBar1)
65.
66.$Form.Add_KeyDown({if ($_.KeyCode -eq "Enter"){& $ping_computer_click}})
67.$Form.Add_KeyDown({if ($_.KeyCode -eq "Escape")
68.{$Form.Close()}})
69.#endregion begin to draw forms
70.
71.#Show form
72.$Form.Topmost = $True
73.$Form.Add_Shown({$Form.Activate()})
74.[void] $Form.ShowDialog()