More Choices in PowerShell

powershell-hero-img
A while back I wrote a few articles on creating a menu driven script in the PowerShell console. In those articles, I used Read-Host to prompt the user for a choice.


If you don’t mind digging into the .NET Framework, then that’s not your only option. You have most likely encountered something like this in PowerShell:

Typical PowerShell prompt for choice
Typical PowerShell prompt for choice (Image Credit: Jeff Hicks)

You enter one of the bracketed values and something happens. With a little scripting on your part, you can use this same concept in your scripts. The method is part of the builtin $host variable.
The PromptForChoice method
The PromptForChoice method (Image Credit: Jeff Hicks)

The PromptForChoice() method has several variations. The parameters we’ll use are essentially a caption, a message prompt, a collection of choices, and the default choice. Let’s play.
The most difficult part of the process is creating the collection of choices. This will be a collection of [System.Management.Automation.Host.ChoiceDescription] objects. First, I’ll initialize an array.

$coll = @()

Next, I’ll create one of these ChoiceDescription objects and save the result to a variable.

$c = [System.Management.Automation.Host.ChoiceDescription]::new("Choice &1")

The text “Choice &1” will be displayed in the prompt. The & indicates what character to use for the choice. Whatever immediately follows will be used as you’ll see. In this case, the number 1 will be the option. What does $c look like?

The choice object
The choice object (Image Credit: Jeff Hicks)


The HelpMessage is empty, so I’ll set a value.

$c.HelpMessage = "run choice 1 commands"

The choice object with a help message
The choice object with a help message (Image Credit: Jeff Hicks)

The last step is to add the choice to the collection.

$coll+=$c

It doesn’t make sense to have a prompt for choice menu with only a single item, so I’ll add some more.

$d = [System.Management.Automation.Host.ChoiceDescription]::new("Choice &2")
$coll+=$d
$e = [System.Management.Automation.Host.ChoiceDescription]::new("Choice &3")
$coll+=$e
$f= [System.Management.Automation.Host.ChoiceDescription]::new("Choice &4")
$coll+=$f
$g = [System.Management.Automation.Host.ChoiceDescription]::new("Choice &5")
$coll+=$g
$q = [System.Management.Automation.Host.ChoiceDescription]::new("&Quit")
$coll+=$q

The collection of choices
The collection of choices (Image Credit: Jeff Hicks)

Now I can prompt for a choice.

$r = $host.ui.PromptForChoice("Your Options","Select a choice",$coll,0)

This is what I get when running in the PowerShell ISE:

ISE choice prompt
ISE choice prompt (Image Credit: Jeff Hicks)

And this is what it looks like in the PowerShell console:
PowerShell console choice prompt
PowerShell console choice prompt (Image Credit: Jeff Hicks)

Note that the ? shows the help messages. Remember when I used the & symbol to indicate what to use for a response? You’ll see those characters in the brackets. In my code, the answer is stored in $r. The value will be an integer reflecting the index number of the selected array member. You can use a simple Switch statement to process the results.

Switch ($r) {
  0 { Write-Host "I am the 1st choice" -foregroundcolor yellow }
  1 { Write-Host "I am the 2nd choice" -foregroundcolor yellow }
  2 { Write-Host "I am the 3rd choice" -foregroundcolor yellow }
  3 { Write-Host "I am the 4th choice" -foregroundcolor yellow }
  4 { Write-Host "I am the 5th choice" -foregroundcolor yellow }
  5 { Write-Host "Thank you" -ForegroundColor green }
}

Remember that arrays start counting at 0. Here’s the complete script in action.

The choice prompt in action
The choice prompt in action (Image Credit: Jeff Hicks)

I am merely displaying a message but you could invoke whatever PowerShell commands you needed to.
But we can take advantage of PowerShell’s ability to extend objects and add some action to our object choices. Here’s a more practical example.
I’ll reset the collection variable.

$coll = @()

I’ll create my first entry and add a help message.

$a = [System.Management.Automation.Host.ChoiceDescription]::new("&Services")
$a.HelpMessage = "Get Running Services"

Before I add it to the array, I’m going to add a new member to the object using Add-Member.

$a | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Get-service | where {$_.status -eq "running"}} –force

I’ve added a new method called Invoke that will run the scriptblock assigned to the Value parameter. I can even test it.

Testing the new method
Testing the new method (Image Credit: Jeff Hicks)

The reason for adding the action will become clearer in a moment. For now, I’ll add some additional choices.

$coll+=$a
$b = [System.Management.Automation.Host.ChoiceDescription]::new("&Processes")
$b.HelpMessage = "Get top processes"
$b | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Get-Process | sort WS -Descending | select -first 10} -force
$coll+=$b
$c = [System.Management.Automation.Host.ChoiceDescription]::new("&Disks")
$c.HelpMessage = "Get disk information"
$c | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Get-Ciminstance win32_logicaldisk -filter "drivetype=3"} -force
$coll+=$c
$q = [System.Management.Automation.Host.ChoiceDescription]::new("&Quit")
$q.HelpMessage = "Quit and exit"
$q | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Write-Host "Have a nice day." -ForegroundColor Green } -force
$coll+=$q

I can prompt for a choice:

$r = $host.ui.PromptForChoice("Task Menu","Select a task:",$coll,0)

Because the value of $r is the index number of the selected item from $coll, I can run my Invoke method.

$coll[$r].invoke()

Invoking the selected choice
Invoking the selected choice (Image Credit: Jeff Hicks)

Here’s what it looks like from the console.
Demo in the console
Demo in the console (Image Credit: Jeff Hicks)

Not too bad. But if I want the ability to bring the menu back I’ll need a loop.

do {
  $r = $host.ui.PromptForChoice("Task Menu","Select a task:",$coll,0)
  $coll[$r].invoke() | Out-Host
} Until ($r -eq $coll.count-1)

The prompt will keep looping until the last item is selected, which is my Quit choice.

Looping
Looping (Image Credit: Jeff Hicks)

To wrap this up, let’s combine the console based menu idea I demonstrated in the past with these techniques.

cls
$Title = "Task Menu"
$Caption = @"
1 - Get Running Services
2 - Get Top Processes
3 - Get Disk Utilization
Q - Quit
Select a choice:
"@
$coll = @()
#$coll = New-Object System.Collections.ObjectModel.Collection
$a = [System.Management.Automation.Host.ChoiceDescription]::new("&1 Services")
$a.HelpMessage = "Get Running Services"
$a | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Get-service | where {$_.status -eq "running"}} -force
# $a.invoke()
$coll+=$a
$b = [System.Management.Automation.Host.ChoiceDescription]::new("&2 Processes")
$b.HelpMessage = "Get top processes"
$b | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Get-Process | sort WS -Descending | select -first 10} -force
$coll+=$b
$c = [System.Management.Automation.Host.ChoiceDescription]::new("&3 Disks")
$c.HelpMessage = "Get disk information"
$c | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Get-Ciminstance win32_logicaldisk -filter "drivetype=3"} -force
$coll+=$c
$q = [System.Management.Automation.Host.ChoiceDescription]::new("&Quit")
$q | Add-Member -MemberType ScriptMethod -Name Invoke -Value {Write-Host "Have a nice day." -ForegroundColor Green ; Return} -force
$q.HelpMessage = "Quit and exit"
$coll+=$q
#loop until last option is selected
do {
$r = $host.ui.PromptForChoice($Title,$Caption,$coll,0)
$coll[$r].invoke() | Out-Host
if ($r -lt $coll.count-1) {
Read-Host "Press any key to continue"
cls
}
} Until ($r -eq $coll.count-1)

A sample menu script
A sample menu script (Image Credit: Jeff Hicks)


So if you are looking to build some tools for the Help Desk or even yourself, I hope these examples will prove useful. As always, please let me know what you think in the comments.