Windows PowerShell Tip of the Week

Here’s a quick tip on working with Windows PowerShell. These are published every week for as long as we can come up with new tips. If you have a tip you’d like us to share or a question about how to do something, let us know.

Find more tips in the Windows PowerShell Tip of the Week archive.

Selecting Items From a List Box

OK, we have to admit it: when it comes to creating graphical user interface elements in Windows PowerShell we’ve been teasing you a little. After all, we’ve shown you how to create a graphical date picker, and we’ve shown you how to create an input box. But there’s at least one thing, one big thing, that we haven’t shown you: how to use Windows PowerShell to create a dialog box that allows you to select items from a list box. For the typical system administrator, that’s a far more pressing need than selecting a date from a calendar.

But never let it be said that the Scripting Guys don’t listen to their readers. (We actually don’t listen to our readers; we just don’t want it to be said.) You want a PowerShell script that lets you select items from a list box? Okey-doke; here’s a PowerShell script that lets you select items from a list box:

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

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Select a Computer"
$objForm.Size = New-Object System.Drawing.Size(300,200)
$objForm.StartPosition = "CenterScreen"

$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
    {$x=$objListBox.SelectedItem;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
    {$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({$x=$objListBox.SelectedItem;$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({$objForm.Close()})
$objForm.Controls.Add($CancelButton)

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please select a computer:"
$objForm.Controls.Add($objLabel)

$objListBox = New-Object System.Windows.Forms.ListBox
$objListBox.Location = New-Object System.Drawing.Size(10,40)
$objListBox.Size = New-Object System.Drawing.Size(260,20)
$objListBox.Height = 80

[void] $objListBox.Items.Add("atl-dc-001")
[void] $objListBox.Items.Add("atl-dc-002")
[void] $objListBox.Items.Add("atl-dc-003")
[void] $objListBox.Items.Add("atl-dc-004")
[void] $objListBox.Items.Add("atl-dc-005")
[void] $objListBox.Items.Add("atl-dc-006")
[void] $objListBox.Items.Add("atl-dc-007")

$objForm.Controls.Add($objListBox)

$objForm.Topmost = $True

$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()

$x

What’s that? You say this looks kind of complicated? Well, maybe. But that’s because PowerShell has no built-in methods for creating and displaying dialog boxes; instead, we need to rely on the .NET Framework, and the .NET Framework can be a bit complicated at times. On the bright side, however, much of this script is boilerplate code. (If you don’t believe us, compare it to our script for creating an input box.) Even better, this script really does let you select computers from a list box:

That kind of makes it all worthwhile, don’t you think?

Besides, this script isn’t as complicated as you might think. As a matter of fact, the script actually starts off in fairly straightforward fashion; we simply use these two lines of – yes, boilerplate code – to load the .NET Framework classes System.Windows.Forms and System.Drawing:

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

By the way, if you’re wondering what the [void] is for, that simply suppresses any messages from appearing onscreen when we load the classes. If we don’t discard these messages we’ll end up seeing something like this each time we load a .NET class:

GAC    Version        Location
---    -------        --------
True   v2.0.50727     C:\WINDOWS\assembly\GAC_MSIL\System.Windows.Forms\2.0.0.0__b77a5c561934e089\...

Pretty fascinating stuff, we admit. But not something we need to see each time we run our script.

Our next task is to create the base form on which all our controls (e.g., buttons and list boxes) will be placed; that’s what this block of code does:

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Select a Computer"
$objForm.Size = New-Object System.Drawing.Size(300,200)
$objForm.StartPosition = "CenterScreen"

See? There’s nothing very complicated here at all. In line 1 we simply use the New-Object cmdlet to create an instance of the System.Windows.Forms.Form class. In the next three lines we don’t do anything fancier than assign values to three properties of our new form:

  • Text. Despite the name, this is actually the window title.

  • Size. This is the size of the form, in pixels. For this example, that’s going to be a form 300 pixels wide by 200 pixels tall.

  • StartingPosition. The truth is, we don’t have to set this property; if we leave it out Windows will pick a location to use when displaying the form. By setting the StartingPosition to CenterScreen, however, our form will automatically be displayed in the middle of the screen each time it loads. Which seemed as good a place as any for the form to appear.

Note. We’re only going to deal with some of the basic form and control properties in this article. For more information, take a peek at the .NET Framework Class Reference on MSDN.

After defining the basic form properties we encounter this bit of code:

$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
    {$x=$objListBox.SelectedItem;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
    {$objForm.Close()}})

What we’re doing here is telling the form what we want it to do if the user presses either ENTER or ESC. To do that, we first set the form’s KeyPreview property to True ($True); that tells the form to intercept specific keystrokes rather than allow those keystrokes to be used by the controls on the form. As it turns out, one of the keystrokes we want to intercept is the ENTER key; this line of code registers the ENTER key as a key to be intercepted, and instructs the form on the actions we want it to take if and when the ENTER key is pressed:

$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
    {$x=$objListBox.SelectedItem;$objForm.Close()}})

As you can see, we use the Add_KeyDown method to add the ENTER key ($_.KeyCode –eq "Enter") to the intercept list. In addition, we tell the form what to do should it ever intercept the ENTER key; if that happens we want to set the value of a variable $x to the value of the item selected in the list box ($objListBox.SelectedItem). In addition, we want to use the Close method to close the form. That, by the way, is an easy – and reasonably foolproof – method of determining which item was selected in the list box: we simply grab the value of the item and store that value in a variable. That way our script can “remember” which item was selected, even after the dialog box has been dismissed; after all, the value will be safely tucked away in the variable $x.

We then use a similar line of code to add the ESC key to the intercept list:

$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
    {$objForm.Close()}})

Notice that, in this case, we don’t set the value of $x to anything; instead we just close the form. Why? You got it: because the ESC key is equivalent to the Cancel button. In other words, pressing ESC means that we want to cancel this operation without retrieving any value from the list box.

Sorry, list box; that’s just the way it goes sometimes.

As we just hinted at, our form includes a Cancel button; it should come as no surprise then that it includes an OK button as well. How do we add an OK button to a form? Here’s one way:

$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({$x=$objListBox.SelectedItem;$objForm.Close()})
$objForm.Controls.Add($OKButton)

Admittedly, adding a button requires a little bit of effort; you can’t just do something like call a method named AddButton. Again, however, things aren’t as bad as they might seem. To add a button we start out by creating an instance of the System.Windows.Forms.Button class. After we create the button we specify a value for the Location property (in this case our button is located 75 pixels from the left size of the form and 120 pixels down from the top of the form), then assign values to the button’s Size and Text properties. Once that’s done we use this line of code to indicate what should happen when the user clicks this button:

$OKButton.Add_Click({$x=$objListBox.SelectedItem;$objForm.Close()})

Ah, very observant of you: these are exactly the same things we want to happen if the user presses ENTER: we want to assign the selected value in the list box to $x, and we want to close the form. Why are these commands exactly the same? Right again: because we want the same thing to happen regardless of whether a user presses ENTER or clicks OK. That’s typical dialog box behavior, and we’d hate for our dialog box to be anything but typical.

As soon as we configure all the property values we then use the Add method to add the button to our form:

$objForm.Controls.Add($OKButton)

And then we repeat the process to add the Cancel button to the form.

Next we use a very similar approach to add a label to our form; that’s what this block of code is for:

$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Please select a computer:"
$objForm.Controls.Add($objLabel)

To add the label, all we have to do is create an instance of the System.Windows.Forms.Label class and then configure the property values for the new control. Once that’s done we again use the Add method to add the label to the form.

And you thought this was going to be complicated. For shame!

At this point we have a dialog box that includes a label and a pair of buttons. About the only thing missing is this: the list box itself. But that’s OK; after all, we were just about to add a list box:

$objListBox = New-Object System.Windows.Forms.ListBox
$objListBox.Location = New-Object System.Drawing.Size(10,40)
$objListBox.Size = New-Object System.Drawing.Size(260,20)
$objListBox.Height = 80

Again, most of this should look familiar; all we’re doing is creating a new instance of the System.Windows.Forms.ListBox class, then configuring the values of the Location, Size, and Height properties. That’s going to give us a list box, albeit an empty list box, one without any items to select.

You know, that’s what we thought, too: what fun is a list box unless there are some items to select? That’s what this block of code is for:

[void] $objListBox.Items.Add("atl-dc-001")
[void] $objListBox.Items.Add("atl-dc-002")
[void] $objListBox.Items.Add("atl-dc-003")
[void] $objListBox.Items.Add("atl-dc-004")
[void] $objListBox.Items.Add("atl-dc-005")
[void] $objListBox.Items.Add("atl-dc-006")
[void] $objListBox.Items.Add("atl-dc-007")

Needless to say, there’s nothing complicated about these lines of code: we’re simply calling the Add method 7 times, and each time we do so we add a different computer name to the list box. Instead of hard-coding in those computer names can we, say, read those names in from a text file? Of course we can; assuming your computer names are in the file C:\Scripts\Test.txt you can replace those 7 lines with this line of code:

Get-Content C:\Scripts\Test.txt | ForEach-Object {[void] $objListBox.Items.Add($_)}

Here we’re simply using Get-Content to read in the contents of the test file; we then pipe those contents to the ForEach-Object cmdlet, which proceeds to create a new list box item for each line (i.e., each computer name) in the file.

Definitely not complicated.

After we add the list box to the form (using the Add method, as usual) we use the following line of code to ensure that, when our dialog box appears on screen, it appears on top of all the other windows:

$objForm.Topmost = $True

After that we use these two lines of code to actually get the dialog box to appear on screen:

$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()

And what happens after the dialog box appears on screen? We’ll let you find out for yourselves. But you should be quite pleased with the results.

That’s all we have time for today. However, we’ll continue our look at the list box in our next column, showing you a few other tricks, like creating a multi-select list box; pre-selecting a specified item; and much, much more.

See you all next week.