Weekend Scripter: Fixing PowerShell GUI Examples

Weekend Scripter: Fixing PowerShell GUI Examples

  • Comments 4
  • Likes

Summary: Microsoft PowerShell MVP, Dave Wyatt, talks about using Windows PowerShell GUI example code.

Microsoft Scripting Guy, Ed Wilson, is here. Welcome back guest blogger, Dave Wyatt. To read more of Dave’s previous guest posts, see these Hey, Scripting Guy! Blog posts.

Dave is a Windows PowerShell MVP and a member of the board of directors at PowerShell.org. He works in IT, and he mainly focuses on automation, configuration management, and production support of computers running the Windows Server operating system and point-of-sale systems for a large retail company.

Blog: http://davewyatt.wordpress.com/
Twitter: @MSH_Dave

Take it away, Dave...

In the early days of Windows PowerShell, there was a series of “Windows PowerShell Tip of the Week” posts about how to create GUIs with Windows PowerShell, including:

  • Creating a Custom Input Box
  • Creating a Graphical Date Picker
  • Selecting Items From a List Box
  • Multi-Select List Boxes – And More!

These examples worked until Windows PowerShell 3.0 was released. If you tried to run them after that, they would not work. I can’t tell you how many times I’ve seen people asking questions about why the code based on these examples doesn’t work. This week, we released updated versions of these topics on TechNet because people still seem to be interested in writing GUIs.

I’m not going to spend a lot of time talking about most of the code in these examples. I’m going to focus on why they stopped working, and discuss some other cleanup I did on the code. The main reason they no longer work has to do with variable scope. All four examples had code similar to this:

OKButton.Add_Click({$x=$objTextBox.Text;$objForm.Close()})

This line assigns a script block as an event handler for the button’s Click event. When the button is clicked, the results of $objTextBox.Text are assigned to variable $x, and the form is closed. However, this variable assignment is local to the script block. It doesn’t modify any variable named $x in the parent scope where the rest of the code is running.

I honestly can’t tell you why it worked in Windows PowerShell 1.0 and Windows PowerShell 2.0, or why that behavior changed. However, the current behavior in Windows PowerShell 3.0 is more in line with what I would expect to see. Script blocks are supposed to have their own scope, and variable assignments are local unless you specify otherwise.

There is a very quick-and-dirty way to “fix” these scripts. You can change the variable assignments to use a scope modifier, for example:

$script:x=$objTextBox.Text

However, I decided to clean up these scripts a bit, and I took a different approach. You can see the complete scripts on GitHub: WinFormsExampleUpdates. Here’s a summary of the changes I made:

  • I got rid of all the KeyPreview and KeyDown bits, in addition to the Click events. These sections of code were intended to allow you to press ENTER or Escape to activate the same effects as the OK and Cancel buttons. However, Windows Forms already has a much simpler way of accomplishing this. You simply assign the buttons to the form’s AcceptButton and CancelButton properties:
    $form.AcceptButton = $OKButton
    $form.CancelButton = $CancelButton
  • I stopped throwing away the dialog’s results. In the original script, the return value of the ShowDialog() method was discarded by casting it to [void]. However, this return value is normally intended to tell you how the user closed the form (OK, Cancel, Abort, Retry, Ignore, Yes, or No) depending on what buttons you’ve shown.

    You also need to assign these dialog result values to the corresponding buttons. If the user pressed OK, the code inside the “If” block performs basically the same thing that the old Click and KeyDown event code used to do in each example. This avoids the scope issue that caused the old scripts to fail in Windows PowerShell 3.0. The code that checks the user’s input no longer happens in a separate event handler script block.

$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel

$result = $form.ShowDialog()
if ($result –eq [System.Windows.Forms.DialogResult]::OK)
{
 # Examine the form controls to retrieve the user’s selection
}

  • I renamed many of the variables. Names like $objForm, $objListBox, and $dtmDate are an old VBScript convention, and they are generally discouraged in Windows PowerShell. These variables became $form, $listBox, and $date, respectively.
  • I replaced the calls to Assembly.LoadWithPartialName() with Add-Type instead. The LoadWithPartialName() method is deprecated, and it is often not necessary anyway. The Add-Type cmdlet maintains a table that maps the short names of many built-in .NET Framework assemblies to their full names. This allows you to do things like this:

Add-Type -AssemblyName System.Windows.Forms

Here’s the end result of the first example (a custom input box):

Image of command output

If you’d like to read the updated Windows PowerShell GUI-building topics on TechNet, they’re right here:

Thanks to Gaby Kaplan on the documentation team for updating the topics on TechNet.

~Dave

Thank you, Dave, for taking time to share your time and knowledge.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • thanks

  • I got the fix direct from source from the very helpful Dave Wyatt. Would be really helpful to have links to this from the original technet articles though. Spent several hours trying to figure out why it wasn't working after using code from the orginal article.

  • You're killing me, Scripting Guy!

    PS3 royaled a logon script with I had with multiple forms and returns associated with it, and this post didn't really address it that well. I found a solution somewhere else, basically change:

    OKButton.Add_Click({$x=$objTextBox.Text;$objForm.Close()})
    ...
    Return $x

    to:

    OKButton.Add_Click({$Global:x=$objTextBox.Text;$objForm.Close()})
    ...
    Return $Global:x

    I'm not sure if it's a best practice, but it's a good work around that got my script working again.