Hey, Scripting Guy! I know this may sound like a strange question, but I would like to create a graphical user interface for a Windows PowerShell script. I could do these kinds of things using a HTML Application (HTA) file, but I like writing Windows PowerShell scripts. If I could use some sort of functionality that would create a drop-down list that would allow a user to select a specific item from the list, it would be great. Or do I need to continue creating HTAs if I want a graphical user interface? I guess I could figure out a way to run Windows PowerShell code from inside my HTA. Let me know.
— MC
Hello MC,
Microsoft Scripting Guy Ed Wilson here. Graphical user interface (GUI) is not something I have been a big fan of in the IT pro/scripting world. I have always figured that if I needed a GUI, I would use Visual Studio and write either a VB.NET or a C# application. Visual Studio makes it so easy to do this; it hardly seems worth the effort to write HTML code by hand. With the advent of the free versions of VB.NET and Visual C#, there is no need for cost justification for the IT pro who wants to start using developer tools. The step from VBScript to VB.NET or from Windows PowerShell to C# is not a very big one. The big commitment, of course, is the time investment it takes to learn the new tools.
From having worked with thousands of IT pros in the last few years, I do know that there are some legitimate reasons for creating GUIs, and the HTA has been a faithful servant in that arena. Unfortunately, I have always found them to be an overly complex technology and a horribly documented one at that. If it were not for the few Hey, Scripting Guy! articles and the HTA Helpomatic (available from our home page), there would be almost no help for HTAs.
Here is an example of an HTA that creates a drop-down list and populates it with items from a text file. A similar HTA is discussed in the Hey, Scripting Guy! article, How Can I Load a Drop-Down List from a Text File?
DisplayListBox.hta
<head>
<!–
‘********************************************************************
‘*
‘* File: DisplayListBox.hta
‘* Author: Ed Wilson
‘* Created: 8/21/2009
‘* Version: 1.0
‘*
‘* Description: Reads a text file, and creates a list box
‘*
‘* Dependencies: You must modify path to the text file
‘* HSG-08-31-09
‘********************************************************************
–>
<title>HTA List Box</title>
<HTA:APPLICATION
ID=”ListBox”
APPLICATIONNAME=”HTAListBox”
SCROLL=”yes”
SINGLEINSTANCE=”yes”
>
</head>
<SCRIPT LANGUAGE=”VBScript”>
Sub Window_Onload
LoadListBox
End Sub
Sub LoadListBox
ForReading = 1
strNewFile = “c:FSOItemList.txt”
Set objFSO = CreateObject(“Scripting.FileSystemObject”)
Set objFile = objFSO.OpenTextFile _
(strNewFile, ForReading)
strItems = objFile.ReadAll
objFile.Close
arrItems = Split(strItems,vbNewLine)
For Each stritem in arrItems
Set objOption = Document.createElement(“OPTION”)
objOption.Text = strItem
objOption.Value = strItem
Items.Add(objOption)
Next
End Sub
</SCRIPT>
<body>
<select name=”Items”>
</select>
</body>
When the DisplayListBox.hta script is run, this GUI is displayed:
The ItemList.txt file is not anything special. It is a list of item names as seen here:
I created the ItemList.txt text file in Explorer, and was getting ready to add some content to the list. Then I realized, this is dumb, I am the Scripting Guy … I know how to write scripts. Therefore, I created the contents of the item list text file by typing the command seen here at a Windows PowerShell prompt.
PS C:> 1..20 | ForEach-Object { Add-Content C:fsoItemList.txt “Item$_” }
The Get-ListBoxFunction.ps1 script contains most of the code in a function called Get-ListBox. I could have called the function New-ListBox and thought about it pretty hard. However, when I looked at Windows PowerShell cmdlets that used the new verb, it did not seem to match up too well, so in the end I decided to call it Get-ListBox. Function names should conform to the Verb-Noun pattern in Windows PowerShell 2.0, or they will generate warning messages when placed into modules. It is a good idea to get in the habit of thinking about proper function names now.
The first thing you must do is load the assembly that contains the various graphical classes we want to use. To do this you use the LoadWithPartialName static method from the System.Reflection.Assembly class. In Windows PowerShell 2.0, this is easier because you can use the Add-Type cmdlet to do this for you. Because you do not want to be distracted with the confirmation message from calling the method, you pipe the results to the Out-Null cmdlet. This is seen here:
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) |
out-null
You next need to create an instance of a Windows form. The Form class resides in the System.Windows.Forms namespace and is documented on MSDN. Because the System is the root namespace and is prepended automatically to the classes, you can leave it off when you create the class. You then assign a title for the Windows form of “ListBox Control” by assigning a value for the text property. This is seen here:
$WinForm = new-object Windows.Forms.Form
$WinForm.text = “ListBox Control”
The size of the form is specified as an instance of a Drawing.Size structure. You need to create an instance of the Drawing.Size structure, and specify the height and the width of the form. The Drawing.Size structure is documented on MSDN. One way to get the Drawing.Size dimensions is to draw the form in Visual Studio and look at the code behind the picture. You can also run the code and experiment with the sizes until you obtain a size that works for your particular application:
$WinForm.Size = new-object Drawing.Size(200,125)
Once you have a form and a size for the form, you need to create a list box. The list box is an instance of the ListBox class that is found in the Windows.Forms namespace. It is documented on MSDN and has a number of methods and properties:
$ListBox = new-object Windows.Forms.ListBox
You now need to add the controls to the Form. To do this, you use the controls.add method. Each item is added by using the items collection from the list box, and calling the add method. The Get-Content cmdlet is used to read the contents of the text file. Because you do not want to see the feedback from adding the item to the list box we pipeline the results to the Out-Null cmdlet. This is seen here.
$winform.controls.add($listbox)
ForEach($i in (Get-Content c:fsoitemlist.txt))
{ $ListBox.items.add($i) | Out-Null }
The last thing you need to do is to activate the Form and show the dialog. This is seen here.
$WinForm.Add_Shown($WinForm.Activate())
$WinForm.showdialog() | out-null
When you select an item from the list box, it is assigned to the SelectedItem property of the ListBox class. The function returns this to the calling code. This is seen here.
$ListBox.SelectedItem
} #end function Get-ListBox
The entry point to the script calls the Get-ListBox function. It does not pass any values to it; however, one improvement would be to modify the function to accept the path to the items it will use to populate the list box. This is seen here.
# *** Entry Point to Script ***
Get-ListBox
The complete Get-ListBoxFunction.ps1 script is seen here.
Get-ListBoxFunction.ps1
# ————————————————————————-
# Get-ListBoxFunction.ps1
# ed wilson, msft, 8/20/2009
#
# Keywords: .NET Framework, Reflection, windows.Forms,
# graphical,gui
#
# HSG-08-31-09
# MSDN documentation for Listbox : http://bit.ly/OaTCg
# ————————————————————————
Function Get-ListBox
{
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) |
out-null
$WinForm = new-object Windows.Forms.Form
$WinForm.text = “ListBox Control”
$WinForm.Size = new-object Drawing.Size(200,125)
$ListBox = new-object Windows.Forms.ListBox
$winform.controls.add($listbox)
ForEach($i in (Get-Content c:fsoitemlist.txt))
{ $ListBox.items.add($i) | Out-Null }
$WinForm.Add_Shown($WinForm.Activate())
$WinForm.showdialog() | out-null
$ListBox.SelectedItem
} #end function Get-ListBox
# *** Entry Point to Script ***
Get-ListBox
When the script is run, the Windows form seen here is created:
MC, thanks for asking a cool question. It has been quite some time since I wrote an HTA. The last one was about 10 months ago when I created one to do temperature conversions. We used your question to kick off graphical scripting week, as we answer some of the graphical scripting questions we have been getting.
Join us next week as graphical Windows PowerShell week continues. Tomorrow we will be looking at … wait you will need to follow us on Twitter or Facebook for that information. If you have any questions, shoot us an e-mail at scripter@microsoft.com or post it to the Official Scripting Guys Forum. See you tomorrow. Until then, peace!
Ed Wilson and Craig Liebendorfer, Scripting Guys
0 comments