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.

Adding a Simple Menu to a Windows PowerShell Script

In last week’s tip we noted that one seeming-advantage of a graphical-oriented application is that it’s easy to pause the script until the user chooses to continue. (How? One way is to pop up a message box and leave it up until the user clicks OK; at that point the application can continue.) Of course, after saying that this was an advantage of a graphical-oriented application we then proceeded to show you how you could achieve the very same thing in your console-based Windows PowerShell script.

We Scripting Guys tend to do things like that.

In fact, this week we’re going to do something very similar. One seeming-advantage of a graphical-oriented application is that it’s easy to constrain a user to a finite set of choices: if you display a message box that contains only Yes and No buttons, well, at that point it’s difficult for the user to choose anything other than Yes or No. Of course (wink wink), that’s much harder to do in a console-based Windows PowerShell script, isn’t it?

Let’s just see about that:

$title = "Delete Files"
$message = "Do you want to delete the remaining files in the folder?"

$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
    "Deletes all the files in the folder."

$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
    "Retains all the files in the folder."

$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

$result = $host.ui.PromptForChoice($title, $message, $options, 0)

switch ($result)
    {
        0 {"You selected Yes."}
        1 {"You selected No."}
    }

So what do we have here? Well, what we have here is a simple console-based menu, similar to the classic Abort, Retry, Fail menu of days gone by:

Delete Files
Do you want to delete the remaining files in the folder?
[Y] Yes  [N] No  [?] Help (default is "Y"):

How were we able to create this menu, and how does this menu actually work? Funny you should ask ….

If you take a peek at the script code, you’ll see that we start out by assigning values to a pair of variables:

  • $title is the title for our simple little menu. In this case, we’ve titled our menu Delete Files.

  • $message is the message we want to display to the user. Here we’re simply asking the user if he or she would like to delete the remaining files in the folder.

Next we define our menu options. For this sample menu we’re giving the user only two choices: Yes, I want to delete the remaining files, or, No, I do not want to delete the remaining files. Let’s take a look at the syntax for setting up the Yes option:

$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
    "Deletes all the files in the folder."

As you can see, all we’re doing here is assigning a value to a variable named $yes. Note that you can name this variable anything you want; it does not have to have the same name as the option. Even though we’re setting up a Yes option we could have named the variable $watermelon if we wanted to; we just didn’t want to.

Note. Why not? Well, for one thing, it will make your debugging life much easier if your variable names and options correspond. It also makes it quite a bit easier for other people to read – and understand – your script.

So what value are we assigning to the variable $yes? Well, to generate that value we use the New-Object cmdlet to create an instance of the System.Management.Automation.Host.ChoiceDescription class; the ChoiceDescription class just happens to be a .NET Framework class that enables you to define options for a console-based menuing system. When we create this instance of the ChoiceDescription class we need to pass along two parameters:

  • &Yes. This is the label that will be displayed in the menu; in a Yes/No system it makes sense that one of the options would be labeled Yes. The ampersand (&) is used to indicate the “accelerator key” for this option. By placing the ampersand before the letter Y we’re giving the user two ways to select this option: type Yes and press ENTER; or type Y and press ENTER. What if we wanted to give the user the option of typing the letter S and then pressing ENTER? In that case, put the ampersand before the s, like so: Ye&s. Weird, but it works.

  • "Deletes all the files in the folder." This is a (typically one line) description of what the option does; the description appears on screen if the user types ? in response to the menu prompt. (Don’t worry, we’ll show you what that looks like in a minute.)

After configuring the first option we repeat the process for the remaining options in our menu. In this case we have only one additional option (No) but we could add in as many options as we want. Want to give the user a chance to select Maybe? Then use code similar to this:

$maybe = New-Object System.Management.Automation.Host.ChoiceDescription "&Maybe", `
    "Pauses for 60 seconds and then asks again about deleting the files."

After we define our options we need to add those options to the menu; that’s what this line of code does:

$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

As you can see, we’re doing nothing more complicated here than creating an array named $options, and adding both of our options ($yes and $no) to the array. Suppose we did add a third option ($maybe) to the menu? Then we’d simply include that value when assigning options to the menu:

$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no, $maybe)

Pretty darn easy, when you get right down to it.

At this point we’re ready to display our menu:

$result = $host.ui.PromptForChoice($title, $message, $options, 0)

Here we’re simply calling the PromptForChoice method (which belongs to the UI property of the $host object). When we call PromptForChoice we need to pass four parameters, in order:

  • $title, the title of our menu.

  • $message, the message displayed to the user.

  • $options, the menu options.

  • 0, indicating the index number of our default option. In this case, we want Yes to be the default option, so we pass PromptForChoice the value 0; that’s because Yes is the first item in our array of menu options. (And the first item in an array always has the index number 0.) What if we wanted No to be the default option? That’s fine; in that case we’d pass the value 1 instead. (Being the second item in the array, No has an index number of 1.)

Oh, good point: what is the default option? Well, that’s the option that will be selected if the user simply presses ENTER. In our case, if the user presses ENTER that’s the same thing as typing Yes and then pressing ENTER.

When we call the PromptForChoice method our menu appears onscreen and the script waits for the user to select an option and press ENTER. When that happens the user’s selection is stored in a variable named $result. (Actually, what gets stored is the index number of the option selected. In this case, $result will equal 0 if the user picks Yes and 1 if the user picks No.)

In turn, we can then examine the value of $result, and take the appropriate action, by using a simple little switch block:

switch ($result)
    {
        0 {"You selected Yes."}
        1 {"You selected No."}
    }

In our sample script we’re simply echoing back the user’s selection; in a real-live script, we’d include code for deleting files if the user chose Yes and code for, well, not deleting the files if the user chose No.

And that’s all we have to do. Like we said, pretty darn easy, isn’t it?

Of course, you might be more inclined to agree with us if you knew exactly how this menu system worked. Okey-doke. Here’s what the screen looks like when our menu first appears:

Delete Files
Do you want to delete the remaining files in the folder?
[Y] Yes  [N] No  [?] Help (default is "Y"):

Notice the ? option? Here’s what happens if the user types a question mark and then presses ENTER:

Delete Files
Do you want to delete the remaining files in the folder?
[Y] Yes  [N] No  [?] Help (default is "Y"): ?
Y - Deletes all the files in the folder.
N - Retains all the files in the folder.
[Y] Yes  [N] No  [?] Help (default is "Y"):

See? All those help descriptions we added are automatically displayed. Now, what if the user types a non-existent option; for example, what happens if the user types X and presses ENTER. Well, let’s see for ourselves:

Delete Files
Do you want to delete the remaining files in the folder?
[Y] Yes  [N] No  [?] Help (default is "Y"): X
[Y] Yes  [N] No  [?] Help (default is "Y"):

The long and short of it is this: nothing happened. Because X is not a valid option the menu simply prompted the user to try again. You can enter invalid options all you want; the menu will never accept them.

Ah, but what if you enter a valid option? In that case the option is accepted, we use the switch statement to analyze the value of $result, and then we run the appropriate block of code. Onscreen, that’s going to look something like this:

Delete Files
Do you want to delete the remaining files in the folder?
[Y] Yes  [N] No  [?] Help (default is "Y"): Y
You selected Yes.

Not bad, not bad at all.

Here’s another simple example, one that shows how you might use this menu system to repeatedly prompt the user to take some action. This script writes the values 1 through 10 to the screen, then asks the user if he or she would like to quit. If the user chooses Yes then the script ends; if the user choose No then the next 10 numbers are displayed onscreen. It’s not the most practical script ever written, but it gives you a foundation for building something a little more useful:

$x = 0

$title = "Endless Loop"
$message = "Do you want to quit?"

$yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", `
    "Exits the loop."

$no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", `
    "Displays the next 10 numbers onscreen."

$options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

:OuterLoop do
    {
        for ($i = 1; $i -le 10; $i++)
        {$x = $x + 1; $x}

        $result = $host.ui.PromptForChoice($title, $message, $options, 0)

        switch ($result)
            {
                0 {break OuterLoop}
            }
    }
while ($y -ne 100)

That’s all the time we have for today. See you all next week.