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:
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 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 HelpMessage is empty, so I’ll set a value.
$c.HelpMessage = "run choice 1 commands"
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
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:
And this is what it looks like in the PowerShell console:
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.
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.
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()
Here’s what it looks like from the console.
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.
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)
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.