Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Tutorial: How-To Convert a PowerShell Script to a GUI Forms Based Tool

Creating PowerShell Tools

Tutorial: How-To Convert a PowerShell Script to a GUI Forms Based Tool

by  markdmac  Posted    (Edited  )
Welcome to the PowerShell GUI Tools Tutorial.
[color #3465A4]By Mark D. MacLachlan[/color]
This tutorial is designed to help people with a fundamental knowledge of PowerShell scripting to be able to convert a simple script into a GUI based tool. Don't be frightened by the amount of text in this FAQ. We will take things slowly to make it easy for you to learn. Please don't forget to rate this FAQ. I strive for ratings of 10, please contact me if you find any problems or have suggestions to make the FAQ better. Thank you.

This tutorial makes the assumption that the reader has already installed the [link http://www.itninja.com/community/admin-script-editor]Admin Script Editor (ASE)[/link] from iTripoli. The company that created ASE has closed shop, but before doing so they opened up their licensing to everyone to use this product for free for home or business use. I will specifically provide steps for using this tool, however the steps are relatively the same for use with Primal Forms. itNinja, the new home of ASE will make you join their forums to get to the download link.

Before we dive into the actual coding, let’s discuss what our application will do. Initially we will create one of the world’s most useless programs that I call PushMe. We will have just a single form with one push button. Upon pushing the button the user will be presented with a pop-up that says "You pushed the button."

After successfully testing our simple form works, we will expand on it to include a text box and label to prompt for the user’s name. When they type their name and push the button the pop-up will change to "You pushed the button username."

We will continue to add code and logic to the application. Our label will change to ask for a computer name. We will query remote computers for BIOS information. We will add the ability to import content from a text file, loop through a list of servers and present the user with a list of bios information.

Step by step the reader will learn to use different form features and how they work within the PowerShell code until our useless app becomes a useable tool. I recommend that you type all code manually rather than cut and paste for two reasons. First, I composed this document in Microsoft Word which regretfully likes to change quotes from coding into fancy curved quotes that won’t execute in code (all efforts have been made to ensure the code is clean anyway). Second, and this is an important issue, ASE by default does some strange things when pasting code in. It will often add spaces where you don’t want them. Take a look at Settings, Formatting, Script Code then Launch Script Formatter for some auto format options. I uncheck everything that adds a space so I have more direct control over the look of my code.

Time to get Coding!!!

Start by launching ASE. Click the blank page icon in the upper left to create a new script. Select PowerShell, and then Blank. Replace Untitled with PushMe and click OK. You now have a blank script. Click the page with a paintbrush icon (right side, left most icon in the group of 5). This starts the forms editor.

Click Button on the left, then drag to make a button in the form. On the right is the properties window. Find Text and change it to say Push Me. Scroll down to find Name which will default to Button1. Change Button1 to btnPushMe. Click into another field for the name change to take.

Click the lightning bolt at the top of the properties. This is the actions menu. Double click next to the word Click. It will fill in an action name BtnPushmeClick.

Click a blank space on the form away from the button. Change the text from Form1 to PushMe Test. Play with setting the background color and foreground color if you like. It is best to use Web Colors instead of the System colors. System colors will change with the user's color theme causing your application to not always look consistent.

Click File, and then select Save to ASE. Close the forms designer.

Back in the ASE, expand out Event Handlers. You should see the function btnPushMeClick. Our code will go inside the curly braces { }.
Add this code:

[code PowerShell][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.MessageBox]::Show("You pushed it.")[/code]
Click File, Save.

Now look for the icon that is a page with a blue arrow in the bottom left corner. That will run the script. Run it and push your button. If you get an error about execution policies, launch PowerShell as admin and type Set-ExecutionPolicy Unrestricted. Press enter then answer yes. Note you have to set execution policy for both the x86 and x64 versions of PowerShell.
OK, now you know how to do a popup with two lines of code. You also know how to use a button. Now let’s add a text box.
Launch the form designer again. Click TextBox in the menu on the left. Drag to make a text box on the form. In properties rename the textbox to tbName. File, Save To ASE. Close the form editor.

Return to the event handlers. Inside our function and above the two lines of code we added before add the following.

[code PowerShell]$Name = $tbName.text[/code]

Note that the form field uses a PowerShell variable name format starting with a dollar sign. The .text property is the same property as was in the Form Designer. You can reference any of those properties with a .Property format.
Edit the last line of code we added to read:
[code PowerShell][System.Windows.Forms.MessageBox]::Show("You pushed it $Name")[/code]
Save it and run again. Type your name in the box. Then push the button. You should now get prompted with a personalized message.

By this time you should be starting to know your way around the ASE interfaces for both the forms designer and the code designer. So, let’s make our application actually do some real work.

Open up the forms designer and change the text for the form to "BIOS Grabber" or something similar to that. Change the text of our push button to "Execute."

From the toolbox click on Label and add a label above the text box. Change the text for the label to "Computer Name", we really don’t care what the name of the label is as we won’t be changing it at this time. Note that for some programs you might want to know the name of the label so you could programatically change the text that it displays. We won't be doing that today though.

Add a second text box under our push button. You may need to stretch your form to make it fit. Change the name of this new text box to "tbResults". Locate multiline in the properties window and set it to True. You should now be able to stretch out the text box to fill the bottom of the form. Save the form back to the ASE and close the forms editor.

Wipe out all the code you have inside our curly braces for the BtnPushMeClick event.

We are now going to switch things up and have our script do some real work by getting BIOS information for us.

Add the following code.
[code PowerShell]
#Set the $Computer variable
$Computer = $tbName.text
#Query the computer with WMI
$WMI = Get-WmiObject Win32_Bios -ComputerName $Computer
#Display our results in the results text box
$tbResults.Text = $WMI
[/code]
Save your code and execute the script. You can query your local machine using the machine name or just a period or the name localhost.
OK things should be working well, you should now see bios information in the results text box. You may have noticed however that if you want to query another computer you need to delete the name of the last machine queried. This is less than convenient so let’s fix that. Under the line $Computer = $tbName.text add a new line of code:
[code PowerShell]$tbName.Text = ""[/code]
Save the code again and test once again.

Now we are going to explore how to use a list box. Return to the forms editor. Expand the size of the form slightly and push the results text box and our push button down slightly. Insert a list box above the button. Stretch it out to fit across the form so there could be 2-3 lines of visible text. Change the name of the list box to "lbWMISelection". Locate the Items property. Click (Collection) and then the three dots to add items to the list. Add BIOS, OS and VIDEO to the collection. (one word per line please) Click OK to close the collection.

Save the form changes. We now want to have the script check for the value that is selected in our listbox. If none is selected we want to inform the user to make a selection. We will use the PowerShell Switch function to determine the selection. We will use a new variable called $WMISelection to check the selected item and then based on that selection we will set a value to our $WMI variable.
Here is what our new code will look like:
[code PowerShell]#Query the computer with WMI
$WMISelection = $lbWMISelection.SelectedItem
switch($WMISelection)
{
"BIOS" { $WMI = Get-WmiObject Win32_Bios -ComputerName $Computer | select Description}
"OS" { $WMI = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer | select Caption}
"VIDEO" { $WMI = Get-WmiObject Win32_VideoController -ComputerName $Computer | select CurrentHorizontalResolution, CurrentVerticalResolution }
default {
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.MessageBox]::Show("Please select a WMI Class")
}
}
return $Computer + $WMI + "`r`n"[/code]

Notice that our switch has a choice of "default" which is what is executed if no other choice is made. In this case we prompt the user to make a selection.

We have accounted for no selection in the WMI class to query, but nothing for the computer name if that is left blank. Let’s make the tool default to the local system if no other choice is made. We are going to modify where we set $Computer with the following:
[code PowerShell]#Set the $Computer variable
if ($tbName.text -eq "")
{ $Computer = "." }
else
{ $Computer = $tbName.text }[/code]

Our last enhancement will be to allow the user to select a text file that lists servers to query. We will then wrap all the rest of our code inside a loop and do some magic to append text to our results list.
Open the forms editor. Drag an OpenFileDialog anywhere on the form. Note that the dialog actually gets attached to the form but is shown below the form. We will leave this named OpenFileDialog1.

Add a new button to the form under our text box. Name the button btnUseList. Change its text to "Use List" and create an on Click event. Save the changes back to the ASE and close the form editor.

Expand out the Event Handlers and locate the BtnUseListClick section.
Add the following code to assign the button to display the file selection menu and assign the file name to a variable called $File. We will also put the file path in our name box. Lastly we will set a global flag that we will use as a toggle to determine we are in list mode.
[code PowerShell] $OpenFileDialog1.ShowDialog()
$File = $OpenFileDialog1.FileName
$tbName.Text = $File
$Global:ListMode = "Yes"
[/code]
We will be checking the value of that flag in different functions and event handlers, so we want it to be globally accessible.
We now need to wrap our existing BtnPushMeClick code inside some code that will check for ListMode to be true and if so take appropriate action. The easiest way for us to do that is to first make our existing code a function that will accept the computer name as an argument. We will call this function Report. Under the first curly brace inside the BtnPushMeClick event, add a new function with the code:
[code PowerShell]Function Report($Computer)
Don’t forget to add the closing curly brace at the end of the event handler section.
Next delete the following code:
#Set the $Computer variable
if ($tbName.text -eq "")
{ $Computer = "." }
else
{ $Computer = $tbName.text }
[/code]
We will add that with additional checking after our $Report function.
Here is the full text of what we are adding:
[code PowerShell]if ($Global:ListMode = "Yes")
{
$Computers = Get-Content($tbName.Text)
foreach ($Computer in $Computers)
{
Report($Computer)
}
#Turn off list mode once done processing our list
$Global:ListMode = "No"
}

if ($Global:ListMode = "No")
{
#Set the $Computer variable
if ($tbName.text -eq "")
{
$Computer = "."
}
else
{
$Computer = $tbName.text
}
Report($Computer)
}
[/code]
All of this is great but our Report Function will wipe out each server’s data with each server from the list. So we now need to accommodate for that and set our text to build an ongoing list.

We are going to delete the line:
$tbResults.Text = $WMI
What we want is the results of each supported server to be pushed down in the list. We can easily accomplish this by having our function return a value and then have that returned value appended to the $tbresults.Text value. Add the following after the switch.
[code PowerShell]return $Computer + $WMI + "`r`n"[/code]

Did you catch the inclusion of `r`n? Those are escape characters that force carriage returns in our text and make it easier to read.
One thing we have not done yet is set the default value of the ListMode value.

After our Report function we are goig to alter the code to display our results so multiple results will be displayed without overwriting. The bit of code that does that is displayed in red for you.

[code PowerShell]if ($Global:ListMode = "Yes")
{
$Computers = Get-Content($tbname.text)
foreach ($Computer in $Computers)
{
$Results = Report($Computer)
[color #CC0000]$tbResults.Text = $tbResults.Text + $Results[/color]
}
}

if ($Global:ListMode = "No")
{
#Set the $Computer variable
if ($tbName.text -eq "")
{
$Computer = "."
}
else
{
$Computer = $tbName.text
}
$Results = Report($Computer)
[color #CC0000]$tbResults.Text = $tbResults.Text + $Results[/color]
}
[/code]

Open the Forms Designer again. Click on the form. Then click the actions icon. Locate the section that says Load and double click in the value area. It should fill in with Form1Load.

Save the form back to ASE and close the editor.

Locate the Form1Load event handler and set the default value of "No" to our global ListMode.
[code PowerShell]$Global:ListMode = "No"[/code]
You may be thinking we need to ensure that our Results box needs to be cleaned up with each press of the button, and you are right. Above the Report function and inside the BtnPushMeClick event, add the following code:
[code PowerShell] #Clear our report area
$tbResults.Text = ""[/code]
Save your script and test out your work.
We bounced around a lot in our code, adding and subtracting from different sections to add more functionality. Having a set plan on what you want your code to do can eliminate such moving around, however I prefer the approach we took for the following reasons. First off, I like to be able to test my code along the way and starting with more basic elements facilitates that testing. Second, more often than not I start with a simple request or idea for a script and later decide I need to do more with it. When I design code for myself I don’t need to make it "idiot proof" because I understand what input a script will need, but regular users don’t always follow directions. Adding rules and validation to a script can enforce that need.
For those folks looking for the completed script code, here it is:
[code PowerShell]#region Script Settings
#<ScriptSettings xmlns="http://tempuri.org/ScriptSettings.xsd">
# <ScriptPackager>
# <process>powershell.exe</process>
# <arguments />
# <extractdir>%TEMP%</extractdir>
# <files />
# <usedefaulticon>true</usedefaulticon>
# <showinsystray>false</showinsystray>
# <altcreds>false</altcreds>
# <efs>true</efs>
# <ntfs>true</ntfs>
# <local>false</local>
# <abortonfail>true</abortonfail>
# <product />
# <version>1.0.0.1</version>
# <versionstring />
# <comments />
# <company />
# <includeinterpreter>false</includeinterpreter>
# <forcecomregistration>false</forcecomregistration>
# <consolemode>false</consolemode>
# <EnableChangelog>false</EnableChangelog>
# <AutoBackup>false</AutoBackup>
# <snapinforce>false</snapinforce>
# <snapinshowprogress>false</snapinshowprogress>
# <snapinautoadd>2</snapinautoadd>
# <snapinpermanentpath />
# <cpumode>1</cpumode>
# <hidepsconsole>false</hidepsconsole>
# </ScriptPackager>
#</ScriptSettings>
#endregion

#region ScriptForm Designer

#region Constructor

[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

#endregion

#region Post-Constructor Custom Code

#endregion

#region Form Creation
#Warning: It is recommended that changes inside this region be handled using the ScriptForm Designer.
#When working with the ScriptForm designer this region and any changes within may be overwritten.
#~~< Form1 >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$Form1 = New-Object System.Windows.Forms.Form
$Form1.ClientSize = New-Object System.Drawing.Size(292, 494)
$Form1.Text = "MultiTool"
#~~< btnUseList >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$btnUseList = New-Object System.Windows.Forms.Button
$btnUseList.Location = New-Object System.Drawing.Point(13, 67)
$btnUseList.Size = New-Object System.Drawing.Size(75, 23)
$btnUseList.TabIndex = 5
$btnUseList.Text = "Use List"
$btnUseList.UseVisualStyleBackColor = $true
$btnUseList.add_Click({BtnUseListClick($btnUseList)})
#~~< lbWMISelection >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$lbWMISelection = New-Object System.Windows.Forms.ListBox
$lbWMISelection.FormattingEnabled = $true
$lbWMISelection.Location = New-Object System.Drawing.Point(13, 96)
$lbWMISelection.Size = New-Object System.Drawing.Size(267, 43)
$lbWMISelection.TabIndex = 4
$lbWMISelection.Items.AddRange([System.Object[]](@("BIOS", "OS", "VIDEO")))
$lbWMISelection.SelectedIndex = -1
#~~< tbResults >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$tbResults = New-Object System.Windows.Forms.TextBox
$tbResults.Location = New-Object System.Drawing.Point(13, 188)
$tbResults.Multiline = $true
$tbResults.Size = New-Object System.Drawing.Size(267, 294)
$tbResults.TabIndex = 3
$tbResults.Text = ""
#~~< tbName >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$tbName = New-Object System.Windows.Forms.TextBox
$tbName.Location = New-Object System.Drawing.Point(13, 40)
$tbName.Size = New-Object System.Drawing.Size(267, 20)
$tbName.TabIndex = 2
$tbName.Text = ""
#~~< btnPushMe >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$btnPushMe = New-Object System.Windows.Forms.Button
$btnPushMe.Location = New-Object System.Drawing.Point(13, 145)
$btnPushMe.Size = New-Object System.Drawing.Size(75, 23)
$btnPushMe.TabIndex = 1
$btnPushMe.Text = "Execute"
$btnPushMe.UseVisualStyleBackColor = $true
$btnPushMe.add_Click({BtnPushMeClick($btnPushMe)})
#~~< Label1 >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Location = New-Object System.Drawing.Point(13, 13)
$Label1.Size = New-Object System.Drawing.Size(100, 23)
$Label1.TabIndex = 0
$Label1.Text = "Computer Name"
$Form1.Controls.Add($btnUseList)
$Form1.Controls.Add($lbWMISelection)
$Form1.Controls.Add($tbResults)
$Form1.Controls.Add($tbName)
$Form1.Controls.Add($btnPushMe)
$Form1.Controls.Add($Label1)
$Form1.add_Load({Form1Load($Form1)})
#~~< OpenFileDialog1 >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$OpenFileDialog1 = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog1.FileName = "OpenFileDialog1"
$OpenFileDialog1.ShowHelp = $true

#endregion

#region Custom Code

#endregion

#region Event Loop

function Main{
[System.Windows.Forms.Application]::EnableVisualStyles()
[System.Windows.Forms.Application]::Run($Form1)
}

#endregion

#endregion

#region Event Handlers



function BtnPushMeClick($object)
{

#Clear our report area
$tbResults.Text = ""

function Report($Computer)
{

#clear the entered name
$tbName.Text = ""
#Query the computer with WMI
$WMISelection = $lbWMISelection.SelectedItem
switch($WMISelection)
{
"BIOS" { $WMI = Get-WmiObject Win32_Bios -ComputerName $Computer | select Description }
"OS" { $WMI = Get-WmiObject Win32_OperatingSystem -ComputerName $Computer | select Caption }
"VIDEO" { $WMI = Get-WmiObject Win32_VideoController -ComputerName $Computer | select CurrentHorizontalResolution, CurrentVerticalResolution }
default {
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[System.Windows.Forms.MessageBox]::Show("Please select a WMI Class")
}
}
return $Computer + $WMI + "`r`n"
}
if ($Global:ListMode = "Yes")
{
$Computers = Get-Content($tbname.text)
foreach ($Computer in $Computers)
{
$Results = Report($Computer)
$tbResults.Text = $tbResults.Text + $Results
}
#Turn off list mode once done processing our list
$Global:ListMode = "No"
}

if ($Global:ListMode = "No")
{
#Set the $Computer variable
if ($tbName.text -eq "")
{
$Computer = "."
}
else
{
$Computer = $tbName.text
}
$Results = Report($Computer)
$tbResults.Text = $tbResults.Text + $Results
}
}

function BtnUseListClick( $object ){
$OpenFileDialog1.ShowDialog()
$File = $OpenFileDialog1.FileName
$tbName.Text = $File
$Global:ListMode = "Yes"
}

function Form1Load( $object ){
$Global:ListMode = "No"
}

Main # This call must remain below all other event functions

#endregion[/code]
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top