One of the best features in PowerShell (PS) is functions. I can’t make that any clearer. Before diving into PowerShell I was a command prompt (CMD) geek who was scared to death of that horrible alien looking VBscript code – vbscript still does frighten me a little. This is why I was happy to learn that PowerShell held on to its CMD roots, and that was the driving factor in getting me to start learning PowerShell.
Being more of a scripter than a coder, though, I initially refused to work with all that object-oriented craziness and kept to my simple CMD logic. While I was pleased to see that working with variables was much easier than with CMD, I was disappointed to see such tight execution policy restrictions. I understand why, I just don’t like it. Overall I was happy enough with my simple PS ways to finally, after a decade, abandoned batch files. Then I started working on the System Information script and all that changed.
Suddenly I had to add in unfamiliar logic operations, loops and *gasp* object-oriented code. On top of that I had to make the output look aesthetically pleasing using HTML code which had to be uploaded to a SharePoint site. This meant that not only was I learning new PowerShell code and techniques, but I was also using PowerShell to dynamically generate code for a completely different programming language! Here’s the fun part, it took me a week’s worth of spare time to do it.
Enough of the chit-chat, time to get into the script itself. We’ll start with the main body of the program itself.
Made with the Online Syntax Highlighter
-
-
-
- [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
- [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
-
-
- startGUI
For those who have never programmed with functions and objects before, welcome to a new world of programming logic. I do only three things with the “code body”: document (the only thing that stuck with me from my programming classes in college), call two assemblies, and then call the first function. The rest of my code is held inside of said functions.
One thing I learned, and I’m sure all you seasoned programmers out there will laugh when I say this, is that you should always put your functions first. If you put the program body before the functions you get a nasty little error and nothing works.
Now let’s take a look at startGUI to see how I really kick things off.
Made with the Online Syntax Highlighter
- function startGUI {
-
- $cancel = $False
-
-
- $objForm = New-Object System.Windows.Forms.Form
- $objForm.Text = "System Information"
- $objForm.Size = New-Object System.Drawing.Size(310,200)
- $objForm.StartPosition = "CenterScreen"
-
-
- $objTextBox = New-Object System.Windows.Forms.TextBox
- $objTextBox.Location = New-Object System.Drawing.Size(10,80)
- $objTextBox.Size = New-Object System.Drawing.Size(260,20)
- $objForm.Controls.Add($objTextBox)
-
-
- $objForm.KeyPreview = $True
- $objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
- {$Server=$objTextBox.Text;$objForm.Close()}})
- $objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
- {$cancel = $True;$objForm.Close()}})
-
- $OKButton = New-Object System.Windows.Forms.Button
- $OKButton.Location = New-Object System.Drawing.Size(75,120)
- $OKButton.Size = New-Object System.Drawing.Size(75,23)
- $OKButton.Text = "OK"
- $OKButton.Add_Click({$Server=$objTextBox.Text;$objForm.Close()})
- $objForm.Controls.Add($OKButton)
-
- $CancelButton = New-Object System.Windows.Forms.Button
- $CancelButton.Location = New-Object System.Drawing.Size(150,120)
- $CancelButton.Size = New-Object System.Drawing.Size(75,23)
- $CancelButton.Text = "Cancel"
- $CancelButton.Add_Click({$cancel = $True;$objForm.Close()})
- $objForm.Controls.Add($CancelButton)
-
-
- $objUser = New-Object System.Windows.Forms.Label
- $objUser.Location = New-Object System.Drawing.Size(20,20)
- $objUser.Size = New-Object System.Drawing.Size(280,40)
- $objUser.Text = "Enter a hostname or IP address."
- $objForm.Controls.Add($objUser)
-
-
- $objForm.Topmost = $True
-
-
- $objForm.Add_Shown({$objForm.Activate()})
- [void] $objForm.ShowDialog()
-
- mainFunc -Server $server -Cancel $cancel
- }
When PowerShell 2.0 comes out we PowerShellers will be able to use the Windows Presentation Foundation (WPF) to make some pretty slick interfaces, until then we are stuck with Windows Forms. Not as pretty, but still functional. Incidentally, those two assemblies called in the main program body are for the GUI. I call them in the program body instead of the function itself so I only have to call them once and not each time I run the function.
You can actually run this code directly from the PowerShell command line window by copy/pasting the two assembly calls then the guts of the function, minus the last line – which calls the next function in the program.
This is a very simple UI that asks for a hostname or IP. You have two selection options, OK and Cancel, which can be triggered by either clicking the buttons or using Enter/Escape on the keyboard. The form body consists of a text box, where the user can enter information, and a label for the text box. I would love to say that I came up with this all my lonesome, but since this is slightly modified code ripped straight from one of Microsoft’s “Windows PowerShell Tip of the Week” posts I won’t. Instead I’m going impart you with a two links that I have found invaluable when creating Windows Forms from PowerShell, and then let the experts tell you how it all works.
PowerShell Tip of the Week link:
http://www.microsoft.com/technet/scriptcenter/resources/pstips/feb08/pstip0208.mspx
Details on the System.Windows.Forms namespace:
http://msdn.microsoft.com/en-us/library/system.windows.forms.aspx
The second link is especially handy when you decide to build more complex interfaces. For example, one of the latest interfaces I built is nearly 200-lines and uses labels, textboxes, checkboxes and a dynamically populated listbox. All of which I figured out by understanding the code from the PowerShell tip above and by reading about the forms namespace.
Before I close this blog post out I will impart a few bits of UI wisdom I have learned. First, document well. Well documented UI elements, and code for that matter, make it easier for you to recall and reuse code. Second, keep your code neat. You don’t have to follow my format, but the cleaner you keep your code the easier it is to recall and reuse it. Notice the pattern yet? Third, there is no miracle way to easily make a UI. Create a second script separate from the one you are building specifically for generating the UI so you can tweak it easily and quickly. It will take time and patience to get everything lined up properly. And lastly, Windows forms work differently on different systems. I hate saying this, but a form that looks perfect on my Vista laptop may look horrible on a 2003 server. Either build some test VMs or find some systems to test your UI on so you can find the best common settings for all.
Hopefully the WPF support in PowerShell 2.0 will fix a lot of the UI woes in PowerShell 1.0. I haven’t had time to dig into WPF support, but if you are interested there is a great series of articles about it starting here:
http://blogs.msdn.com/powershell/archive/2008/05/22/wpf-powershell-part-1-hello-world-welcome-to-the-week-of-wpf.aspx
In part 3 I will dive into the main function body, followed by generating the HTML in part 4. Part 3 coming soon™.
#James Kehr
Get-Member $OW | ?{$_.title -eq "System Administrator"`
-and $_.certification -contains 'MCSE 2000, MCDST, Network+, A+'}
New-Variable -name company -value 'ORCS Web, Inc.' -description ‘www.orcsweb.com | 1.888.313.9421’