When working in PowerShell, and especially when scripting, you might want to give the user a choice of actions. For example, you might develop a configuration script that another admin or technician will run. Perhaps one of the steps is to configure networking depending on their location so you want to give the person running the script a menu of choices. Here's one way you might accomplish this, without resorting to graphical tools or WinForms.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
In PowerShell, we use the Read-Host cmdlet to get input from a user.
Enter a name: Jeff
PS S:\> $r
Jeff
PS S:\>
The Read-Host prompt is a string, but it can be a complex string like this.
PS S:\> $menu=@"
>> 1 Show info about a computer
>> 2 Show info about someones mailbox
>> 3 Restarts the print spooler
>> Q Quit
>>
>> Select a task by number or Q to quit
>> "@
>>
PS S:\> $r=Read-Host $menu
1 Show info about a computer
2 Show info about someones mailbox
3 Restarts the print spooler
Q Quit
Select a task by number or Q to quit: 3
PS S:\> $r
3
You could then use a Switch statement to do something based on the value of $r. For today's Friday Fun, I've wrapped all of this up in a demo script that includes a relatively simple menu building function.
Function Show-Menu {
Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage="Enter your menu text")]
[ValidateNotNullOrEmpty()]
[string]$Menu,
[Parameter(Position=1)]
[ValidateNotNullOrEmpty()]
[string]$Title="Menu",
[switch]$ClearScreen
)
if ($ClearScreen) {Clear-Host}
#build the menu prompt
$menuPrompt=$title
#add a return
$menuprompt+="`n"
#add an underline
$menuprompt+="-"*$title.Length
$menuprompt+="`n"
#add the menu
$menuPrompt+=$menu
Read-Host -Prompt $menuprompt
} #end function
#define a menu here string
$menu=@"
1 Show info about a computer
2 Show info about someones mailbox
3 Restarts the print spooler
Q Quit
Select a task by number or Q to quit
"@
#Keep looping and running the menu until the user selects Q (or q).
Do {
#use a Switch construct to take action depending on what menu choice
#is selected.
Switch (Show-Menu $menu "My Help Desk Tasks" -clear) {
"1" {Write-Host "run get info code" -ForegroundColor Yellow
sleep -seconds 2
}
"2" {Write-Host "run show mailbox code" -ForegroundColor Green
sleep -seconds 5
}
"3" {Write-Host "restart spooler" -ForegroundColor Magenta
sleep -seconds 2
}
"Q" {Write-Host "Goodbye" -ForegroundColor Cyan
Return
}
Default {Write-Warning "Invalid Choice. Try again."
sleep -milliseconds 750}
} #switch
} While ($True)
The Show-Menu function takes parameters for the menu body, a title and switch indicating if you want to clear the screen before displaying the menu. Within the function, I create a new string to use as the prompt for Read-Host. I've split it up into distinct steps so you can better understand what I'm doing. The main part of the script is the Do loop which uses a Switch construct to run the Show-Menu function.
Switch (Show-Menu $menu "My Help Desk Tasks" -clear) {
The script will keep running until the user presses Q (or q). Here's what you would see.
The essence of this technique is to create a multi-line prompt with a here string and then a Switch statement based on the Read-Host input. Just don't forget to validate the input. Or you can use my Show-Menu function.
Download my Demo-ConsoleMenu script and enjoy.
I once took a few hours to try and build a general purpose console menu system to make it easy for admins to build the own tools that work like SCONFIG. It turns out to need more than a few hours to do. 🙂 I think this is a super important direction to go in as we’ll see more and more Server Core machines that do not have GUIs on them.
One of the thoughts was to have a content model (schema for the menus and actions) that could be used by multiple tools. It could be given to one program on a server Core box that generated a console menu system. You could connect to a server core box using remoting and then run a client-side tool which grabbed the content over the connection and generated a local GUI and sent the actions across the connection to be executed. It could also be transcoded into an HTML page and served up via HTTP.
It seems pretty reasonable to do all of that. It will just take more than a few hours to finish. 🙂
I strongly encourage the community to pick this up and run with it (as it is not going to surface in PowerShell V3).
Jeffrey Snover [MSFT]
Distinguished Engineer and Lead Architect for Windows Server
Thanks. There’s definitely alot that could be done here. Oisin Grehan mentioned $host.ui.PromptforChoice(). My initial idea here was to show an IT Pro how to do this with cmdlets at his disposal, without having to resort to arcane .NET stuff. Perhaps I’ll kick your ideas around a bit in 2012 before the world ends.
Like a powershell X server? I like it. My mind is already racing.
I wanted to write that you were silly for not using PromptForChoice, but personally, I find PowerShell’s built-in menu system frustrating. I actually wrote a wrapper for it which is (I think) very easy to use. I called it Read-Choice. But I remember being very surprised to discover that PowerShell.exe renders the menus vertical list in the console whenever you specify -MultipleChoice … but as a horizontal list otherwise. Of course, the really hysterical thing is what happens in PowerShell ISE 3 CTP2 …
Maybe we can get a set of cmdlets put into PowerTab or PSCX — actually, maybe we can get the SAME functionality put into both, it seems like that would cover an awful lot of people.
There’s nothing wrong with PromptForChoice() but it requires a bit more experience. I wanted to have something that any IT Pro with basic PowerShell could have put together. I also sometimes think we put people off with overly complex code that looks like programming. If I can do so something with a cmdlet, that’s the first route I’ll take. There is definitely the need for a menuing system as Jeffrey Snover notes. But it sounds like a job for people like you and Oisin.
Question on this as I’ve used fairly large menus is some of my automation scripts. Is there an easy way to build the menu string with different lines being in different colors? I’d like to change options that have already been chosen to be darker (subdued). I haven’t found an easy way to do this without some big function to Write-Host each line individually.
I’ve done variations on this idea using Write-Host. If you want color that is your only option.
I think the line
Switch (Show-Menu $menu “My Help Desk Tasks” -clear) {
should be
Switch (Show-Menu $menu “My Help Desk Tasks” -clearscreen) {
Yes and no. If I was being thorough and explicit, then you are correct. However, a function parameter is like a cmdlet parameter: you only have to type enough of the parameter so that PowerShell knows what you mean. In this situation I can use -Clear and PowerShell knows what I mean. Actually, I could have gotten away with -C but I thought that might be confusing.
I have something you could start with in this old poshcode script I wrote a while back: Select-EnumeratedType. It wraps up the PromptForChoice (including multiple choice) in order to let people construct valid enums with the menu system. For flags enums, it uses multiple choice. It also adds the help string (console only, ISE is broken) – oh, and best of all, it autogenerates hot keys/accelerators 😀
http://poshcode.org/2327
Hi Jeff,
I used the menu script and customized it to perform backups for our Virtual environment as well as issue a shutdown command to the host.
The current script loops until the user hits “Q.” Two of the options I have configured, end with shutting down the environment. I would like to have the Do/While loop terminate after a command is chosen that involves a shutdown. Now, it looks like this:
Function Show-Menu {
Param(
[Parameter(Position=0,Mandatory=$True,HelpMessage=”Enter your menu text”)]
[ValidateNotNullOrEmpty()]
[string]$Menu,
[Parameter(Position=1)]
[ValidateNotNullOrEmpty()]
[string]$Title=”Menu”,
[switch]$ClearScreen
)
if ($ClearScreen) {Clear-Host}
#build the menu prompt
$menuPrompt=$title
#add a return
$menuprompt+=”`n”
#add an underline
$menuprompt+=”-“*$title.Length
$menuprompt+=”`n”
#add the menu
$menuPrompt+=$menu
Read-Host -Prompt $menuprompt
} #end function
#define a menu here string
$menu=@”
1. Backup FileServer VM and Shutdown Environment – End of Day Operation.
2. Backup FileServer VM.
3. Backup Both Application Server and Fileserver and Shutdown.
4. Shutdown the VM’s and Host – End of Day Operation.
Q Quit.
Select a task by number or Q to quit
“@
#Keep looping and running the menu until the user selects Q (or q).
Do {
#use a Switch construct to take action depending on what menu choice
#is selected.
Switch (Show-Menu $menu ” Veeam Backup and Shutdown Options ” -clear){
## The invoke statement will execute the script on J:\ on the remote server specified.
“1” {invoke-command -computername ppsrv02 -filepath J:\VEEAMBackupShutdownScripts\BackupPPSRV01AndShutDown.ps1 -ea silentlycontinue}
“2” {invoke-command -computername ppsrv02 -filepath J:\VeeamBackupShutdownScripts\BackupPPSRV01.ps1 -ea silentlycontinue}
“3” {invoke-command -computername ppsrv02 -filepath J:\VEEAMBackupShutdownScripts\BackupOnly.ps1 -ea silentlycontinue}
“4” {invoke-command -computername ppsrv02 -filepath J:\VEEAMBackupShutdownScripts\VMShutDown.ps1 -ea silentlycontinue}
“Q” {Write-Host “Terminating Script” -ForegroundColor Yellow
Return
}
Default {Write-Warning “Invalid Choice. Try again.”
sleep -milliseconds 750}
} #switch
}While ($True)