If you’re building a script that’s capable of doing various tasks simultaneously, a great way of providing input for that script is to use an interactive menu in the console.

In order to write code that’s reusable, it's important not to hardcode values that may change at some future point. The best practice is to separate the input values from the processing and the output. In PowerShell, there are many ways to make this happen including using command-line parameters, building fancy GUIs, using input boxes, and so on. Your exact situation will dictate which method you choose.

Constructing an interactive menu that allows the user to select between different options is a great way to build scripts for less PowerShell-savvy people, or it can simply be used as a kind of input gateway to a lot of the functionality that you’ve already built in PowerShell.

Let’s go over how to build a rudimentary interactive menu in the PowerShell console.

This menu consists of two main components: a function that displays the menu, and a switch statement that takes the input and directs the code which way to go, depending on the selection. First, we’ll build the menu function. I’ll call this Show-Menu.

function Show-Menu
{
    param (
        [string]$Title = 'My Menu'
    )
    Clear-Host
    Write-Host "================ $Title ================"
    
    Write-Host "1: Press '1' for this option."
    Write-Host "2: Press '2' for this option."
    Write-Host "3: Press '3' for this option."
    Write-Host "Q: Press 'Q' to quit."
}

This is a simple function that first clears the console and then writes a few lines to it. When ran with the title parameter set to 'My Menu', it will look like this:

Show-Menu –Title 'My Menu'
Show-Menu -Title 'My Menu' output

Show-Menu -Title 'My Menu' output

This is great, but as you can see, it dropped me back to the console again. I had no way to actually provide input to the menu. We need a way to make it pause after the menu is displayed to allow me to send it a keystroke. To do this, I can put a Read-Host immediately afterward to ask me for input.

Show-Menu –Title 'My Menu'
$selection = Read-Host "Please make a selection"
Menu prompts for input

Menu prompts for input

I can now provide some kind of input, but it's still not functional. I have no way to do anything with the input once PowerShell receives it. I need a way to achieve something, depending on the input. A great construct to use here is the switch statement.

Show-Menu –Title 'My Menu'
 $selection = Read-Host "Please make a selection"
 switch ($selection)
 {
     '1' {
         'You chose option #1'
     } '2' {
         'You chose option #2'
     } '3' {
         'You chose option #3'
     } 'q' {
         return
     }
 }

I now have the opportunity to do something with the input I receive from Read-Host.

Menu accepts input

Menu accepts input

Awesome! This works great. but maybe I want to do something and then keep the menu up until I say otherwise (by hitting the 'Q' key). I need the menu to come up again and again after I select an option. To do this, I can use a do/until loop. This lets me do something based on the menu selection, and then immediately render the menu again until 'Q' has been selected.

Subscribe to 4sysops newsletter!

do
 {
     Show-Menu
     $selection = Read-Host "Please make a selection"
     switch ($selection)
     {
         '1' {
             'You chose option #1'
         } '2' {
             'You chose option #2'
         } '3' {
             'You chose option #3'
         }
     }
     pause
 }
 until ($selection -eq 'q')

At this point, when this script is run, it will display the menu and prompt for a selection. Once the selection is made, the switch statement will decide what to do. It will then pause, as a demonstration, to show the output of the selection. Then, if only 'Q' is chosen, it will break out of the loop and return control to the console.

22 Comments
  1. danny 5 years ago

    What a great und useful article!

    Thank you Adam!

  2. readerweb 5 years ago

    User experience starts with a menu.  I will stop showing codes and will put focus on showing menus for each task.  Thank you for he useful post.

  3. Daniel Potter 5 years ago
    function show-menu {
    
    [void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
    $formShowmenu = New-Object 'System.Windows.Forms.Form'
    $combobox1 = New-Object 'System.Windows.Forms.ComboBox'
    
    
    $combobox1_SelectedIndexChanged = {
    $script:var = $combobox1.SelectedItem
    $formShowmenu.Close()
    }
    
    
    $formShowmenu.Controls.Add($combobox1)
    $formShowmenu.AutoScaleDimensions = '6, 13'
    $formShowmenu.AutoScaleMode = 'Font'
    $formShowmenu.ClientSize = '284, 70'
    
    #add array of items
    [void]$combobox1.Items.Addrange(1 .. 10)
    
    #add single item
    #[void]$combobox1.Items.Add('Single Item')
    
    $combobox1.Location = '45, 25'
    $combobox1.Size = '187, 21'
    $combobox1.add_SelectedIndexChanged($combobox1_SelectedIndexChanged)
    
    $formShowmenu.ShowDialog() | out-null
    
    write-output $var
    }
    show-menu

  4. Ayo Dada 4 years ago

    Hi Adam,

    I managed to create an interactive menu, how would I get it to run some get script i have already develop example

    So I have some individual script that can get a AAD user etc and i want to use an interactive menu to give the user the option

    example below

    Write-Host "================ $Title ================"

    Write-Host "1: Press '1' Get AAD Users."
    Write-Host "2: Press '2' Get AAD Devices."
    Write-Host "3: Press '3' Get AAD Devices"
    Write-Host "Q: Press 'Q' to quit."
    }
    do
    {
    Show-Menu
    $input = Read-Host "Please select a task by number Or Q to Quit"
    switch ($input)
    {
    '1' {
    cls
    'You chose option #1'
    } '2' {
    cls
    'You chose option #2'
    } '3' {
    cls
    'You chose option #3'
    } 'q' {
    return
    }
    }
    pause
    }
    until ($input -eq 'q')

    im getting the menu option but i need to now pass the function to a menu parameter the script at the end of each action to execute my script or do i add a loop.

    any direction would be helpful

     

  5. Lev 4 years ago

    i don't know what is the problem, but i stuck here:

    do
    {
        $input = Read-Host " what do you want to do? 
                            `n1)  list all the Vms
                            `n2) do something to the Vms?
                            `n
                            `n * what is your choice?"
    
                           
        switch ($input)
        {
            '1' { "you choose to list all the avalibale VMs on the pc"
                
                if($input -eq '1') {
                    
                    get-vm
                   
                    }
                }
            '2' {
                cls
                'You chose option #2'
                }
            '3' {
                cls
                'You chose option #3'
                }
            'q' {
                return
                }
        }
        pause
    }
    until ($input -eq 'q')

    • function Show-Menu
      {
          param (
              [string]$Title = 'My Menu'
          )
          Clear-Host
          Write-Host "================ $Title ================"
          Write-Host "1: Press '1' to list all the Vms."
          Write-Host "2: Press '2' to delete vms"
          Write-Host "Q: Press 'Q' to quit."
      }
      
      function List-VMs
      {
              Write-Host "Script Block to Display all the VMs"
      }
      function Delete-VMs
      {
              Write-Host "Script Block to Delete VM"
      }
      
      do
      {
          Show-Menu –Title 'My Menu'
          $input = Read-Host "what do you want to do?"
          switch ($input)
          {
              '1' {               
                      List-Vms
                  }
              '2' {
                      Delete-VMs
                  }
              'q' {
                       return
                  }
          }
          pause
      }
      until ($input -eq 'q')

      Complete script for your reference 

       

      • Michael 3 years ago

        The script looks good and I'd like to use it but when I modify it I never see the results onscreen.

        What do I need to change to see the results?

         

        Thank you.

        avatar
        • Hi Michael,
          The script above provides a basic framework for menu and IO system. 
          You could fill in the blocks, extend it or further improve it.
          If you could provide me with details of modifications I would be glad to help you identify the break in the flow.

  6. Nino Crudele 3 years ago

    Great article, exactly was I was looking for, simple and very clear.

  7. Daniel 3 years ago

    Is there a way to add some fun by adding an "else" for if the user selects something not on the list (say they randomly type "whatever..." and press enter, where it'll recognize that it's not an option and return something to them such as "That wasn't an option... what were you thinking?"

  8. TrixM 3 years ago

    A bit old, but you can add "Default" as a keyword to the end of the switch statement (no quotes), with a default action that gets executed if none of the switch options match the input.

    switch ($selection) {
        '1' { 'You chose option #1' } 
        '2' { 'You chose option #2' } 
        Default { 
            'Please consider typing lessons'
            Break
        }
    }
    avatar
  9. Daniel 2 years ago

    This is Awesome!

    Thanks
    Dan

  10. JOhn 2 years ago

    I have a problem with my script, which matches closely to what is above. The first time I run it, I get the menu, make a "1" selection (get-mailbox), it pauses, but I get no results. If I press 1 again, THEN I get results. What I want it to do is give me results and pause at the end of those results so I can review them. I seemed to have narrowed this down to the "sort" option in my menu item. If I remove the sort, this all works. Any ideas?

     

    function Show-Menu

    {
        param ([string]$Title = 'My Menu')
        Clear-Host

    Write-Host "================ Mailbox Report Options ================"
     
    Write-Host "1: Press '1' for HC Mailboxes."
    Write-Host "2: Press '2' for MC Mailboxes."
    Write-Host "3: Press '3' for HP Mailboxes."
    Write-Host "Q: Press 'Q' to quit."
    Write-host "`n"
    }

    DO
    {
    Show-Menu –Title 'My Menu'

    $input = Read-Host "Please make a selection"

    switch ($input)
         {
            '1' {get-mailbox -OrganizationalUnit "ou=HC,OU=Domain Users,DC=contoso,DC=com" | get-mailboxstatistics | select displayname, totalitemsize | sort totalitemsize}
        
        '2' {get-mailbox -OrganizationalUnit "ou=MC,OU=Domain Users,DC=contoso,DC=com" | get-mailboxstatistics | select displayname, totalitemsize | sort totalitemsize}
            
        '3' {get-mailbox -OrganizationalUnit "ou=HP,OU=Domain Users,DC=contoso,DC=com" | get-mailboxstatistics | select displayname, totalitemsize | sort totalitemsize}

        'q' {EXIT}
         }
    pause
    }
    until ($input -eq 'q')

  11. Todd 2 years ago

    John try using Format-Table in the place of Select

  12. Adam Herrera 1 year ago

    Is there a way to make it return to the main menu instead of quitting? 

    • Adam, what do you mean by return to main menu? The article speaks about showing a menu and once a selection made, the selection is used by a switch further in the script.

  13. David H 1 year ago

    Hopefully you are still monitoring this feedback and responding, I know this article is old. And please forgive my newness to PowerShell, I am a Linux guy diving head first into PowerShell.

     

    I've created the script just prior to the do / until section. And that is where you lost me... And this might just be a super simple question, but like I said, I have been working with PowerShell for all of 3 days now...

    My script thus far is as follows.

    function Show-Menu
    {
        param (
            [string]$Title = 'Select a profile'
        )
        Clear-Host
        Write-Host "================ $Title ================"
        
        Write-Host "1: Press '1' ARIZONA."
        Write-Host "2: Press '2' COLORADO."
        Write-Host "3: Press '3' FLORIDA."
        Write-Host "3: Press '4' MONTANA."
        Write-Host "3: Press '5' OHIO."
        Write-Host "3: Press '6' TEXAS."
        Write-Host "3: Press '7' WYOMING."
        Write-Host "Q: Press 'Q' to quit."
    }
    Show-Menu –Title 'Select a profile'
     $selection = Read-Host "Please make a selection"
     switch ($selection)
     {
         '1' {
             'You chose option #1 ARIZONA'
         } '2' {
             'You chose option #2 COLORADO'
         } '3' {
             'You chose option #3 FLORIDA'
         } '4' {
             'You chose option #4 MONTANA'
         } '5' {
             'You chose option #5 OHIO'
         } '6' {
             'You chose option #6 TEXAS'
         } '7' {
             'You chose option #7 WYOMING'
         } 'q' {
             return
         }
     }

     

    Now I want the output of the script to just give the state name, and be held as a variable, let's just say $State

    How would I go about just clipping out the segment I want, and assigning it to the variable in question?

  14. Asif 1 year ago

    You can also do something like this:

    [array]$DropDownArrayItems = "Start VPN", "Stop VPN", "Cancel"
    [array]$DropDownArray = $DropDownArrayItems | sort
    
    # This Function Returns the Selected Value and Closes the Form
    
    function Return-DropDown {
        if ($DropDown.SelectedItem -eq $null) {
            $DropDown.SelectedItem = $DropDown.Items[0]
            $script:Choice = $DropDown.SelectedItem.ToString()
            $Form.Close()
        }
        else {
            $script:Choice = $DropDown.SelectedItem.ToString()
            $Form.Close()
        }
    }
    
    function SelectGroup {
        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
        [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
    
    
        $Form = New-Object System.Windows.Forms.Form
    
        $Form.width = 300
        $Form.height = 150
        $Form.Text = ”DropDown”
    
        $DropDown = new-object System.Windows.Forms.ComboBox
        $DropDown.Location = new-object System.Drawing.Size(100, 10)
        $DropDown.Size = new-object System.Drawing.Size(130, 30)
    
        ForEach ($Item in $DropDownArray) {
            [void] $DropDown.Items.Add($Item)
        }
    
        $Form.Controls.Add($DropDown)
    
        $DropDownLabel = new-object System.Windows.Forms.Label
        $DropDownLabel.Location = new-object System.Drawing.Size(10, 10) 
        $DropDownLabel.size = new-object System.Drawing.Size(100, 40) 
        $DropDownLabel.Text = "Your choice:"
        $Form.Controls.Add($DropDownLabel)
    
        $Button = new-object System.Windows.Forms.Button
        $Button.Location = new-object System.Drawing.Size(100, 50)
        $Button.Size = new-object System.Drawing.Size(100, 20)
        $Button.Text = "OK"
        $Button.Add_Click( { Return-DropDown })
        $form.Controls.Add($Button)
        $form.ControlBox = $false
    
        $Form.Add_Shown( { $Form.Activate() })
        [void] $Form.ShowDialog()
    
    
        return $script:choice
    }
    
    $Group = $null
    $Group = SelectGroup
    while ($Group -like "") {
        $Group = SelectGroup
    }
    
    
    if ($Group -eq "Start VPN") {
    
        Write-Output "Starting VPN"
    
    }
    elseif ($Group -eq "Stop VPN") {
    
        Write-Output "Stopping VPN"
    
    }
    elseif ($Group -eq "Cancel") {
    
        Write-Output "Cancelling"
    
    }

     

  15. Anthony 1 year ago

    Hello,

    Thank you for this very helpful script!

    Now please, cold anyone advise, from this script, how you would trigger the following actions :

    ++ If highlight "option 1" menu, and press ok , the script will open a file located here :

    https://my.server.com/Word/Documents/MyDocument1.docx

    ++ If highlight "option 2" menu, and press ok , the script will open a file located here :

    https://my.server.com/Word/Documents/MyDocument2.docx

    etc..

    Many MANY thanks in anticipation!!

    Anthony

  16. DarkLite1 9 months ago
    Function Show-MenuHC {
        <#
        .SYNOPSIS
            Show a selection menu in the console
    
        .DESCRIPTION
            Allow the user to select an option int he console and return the 
            selected value afterwards. When the parameter `-QuitSelector` is used
            the user is also able to select nothing and just leave the menu with no
            return value.
    
        .EXAMPLE
            Show-MenuHC -Items @('a', 'b', 'c') -QuitSelector @{ 'Q' = 'Quit' }
    
            1) a
            2) b
            3) c
            Q) Quit
            Select an option:
    
            Displays the menu with an option to select nothing by pressing `q`
    
        .EXAMPLE
            $fruits = @(
                [PSCustomObject]@{
                    Name  = 'banana'; Color = 'yellow'; Shape = 'long'
                }
                [PSCustomObject]@{
                    Name  = 'kiwi'; Color = 'green'; Shape = 'oval'
                }
                [PSCustomObject]@{
                    Name  = 'apples'; Color = 'red'; Shape = 'round'
                }
            )
    
            $params = @{
                Items           = $fruits 
                QuitSelector    = $null 
                SelectionPrompt = 'Select a fruit you like:'
            }
            $myFavoriteFruit = Show-MenuHC @params
    
            1) @{Name=banana; Color=yellow; Shape=long}
            2) @{Name=kiwi; Color=green; Shape=oval}
            3) @{Name=apples; Color=red; Shape=round}
            'Select a fruit you like:'
            
            Displays the menu where the user is forced to select one of the 3 
            options. When selected the result is stored in the variable 
            '$myFavoriteFruit'
        #>
    
        [CmdLetBinding()]
        Param (
            [Parameter(Mandatory)]
            $Items,
            $QuitSelector = @{ 'Q' = 'Quit' },
            $displayTemplate = '{0}) {1}',
            $SelectionPrompt = 'Select an option:'
        )
    
        #region Build hash table with selector and value
        $hash = [Ordered]@{}
    
        for ($i = 0; $i -lt $Items.Count; $i++) {
            $key = "$($i+1)"; $value = $Items[$i]
            $hash[$key] = $value
        }
        #endregion
    
        #region Get the quit selector key if there is one
        $quitSelectorKey = $null
    
        if ($QuitSelector) {
            if ($QuitSelector.Keys[0] -match '^[0-9]+$') {
                throw 'The quit selector cannot be a number'
            }
            if ($QuitSelector.Count -ne 1) {
                throw 'The quit selector can hold only one key value pair'
            }
    
            $quitSelectorKey = $quitSelector.GetEnumerator() | 
            Select-Object -ExpandProperty Key
    
            if ($quitSelectorKey -isNot [String]) {
                throw 'The quit selector key needs to be of type string'
            }
        }
        #endregion
    
        #region Display the menu
        do {
            foreach ($item in $hash.getEnumerator()) {
                $params = @{
                    Object          = $displayTemplate -f 
                    $item.key, "$($item.Value)" 
                    ForegroundColor = 'yellow'
                }
                Write-Host @params
            }
            if ($QuitSelector) {
                $params = @{
                    Object          = $displayTemplate -f 
                    $quitSelectorKey, "$($QuitSelector.Values[0])"
                    ForegroundColor = 'DarkGray'
                }
                Write-Host @params
            }
    
            $selected = Read-Host -Prompt $SelectionPrompt
        } until (
            ($hash.Keys -contains $selected) -or 
            ($selected -eq $quitSelectorKey)
        )
        #endregion
    
        #region Return the selected value
        if ($selected -ne $quitSelectorKey) {
            $returnValue = $hash[$selected]
            Write-Verbose "Selected: $selected) $returnValue"
            $returnValue
        }
        #endregion
    }

    I hope this helps you guys a bit.

  17. DarkLite1 9 months ago

    I can't seem to update or edit my post as I made a small fix. Here is the updated version:

    Function Show-MenuHC {
        <#
        .SYNOPSIS
            Show a selection menu in the console
    
        .DESCRIPTION
            Allow the user to select an option int he console and return the 
            selected value afterwards. When the parameter `-QuitSelector` is used
            the user is also able to select nothing and just leave the menu with no
            return value.
    
        .EXAMPLE
            Show-MenuHC -Items @('a', 'b', 'c') -QuitSelector @{ 'Q' = 'Quit' }
    
            1) a
            2) b
            3) c
            Q) Quit
            Select an option:
    
            Displays the menu with an option to select nothing by pressing `q`
    
        .EXAMPLE
            $fruits = @(
                [PSCustomObject]@{
                    Name  = 'banana'; Color = 'yellow'; Shape = 'long'
                }
                [PSCustomObject]@{
                    Name  = 'kiwi'; Color = 'green'; Shape = 'oval'
                }
                [PSCustomObject]@{
                    Name  = 'apples'; Color = 'red'; Shape = 'round'
                }
            )
    
            $params = @{
                Items           = $fruits 
                QuitSelector    = $null 
                SelectionPrompt = 'Select a fruit you like:'
            }
            $myFavoriteFruit = Show-MenuHC @params
    
            1) @{Name=banana; Color=yellow; Shape=long}
            2) @{Name=kiwi; Color=green; Shape=oval}
            3) @{Name=apples; Color=red; Shape=round}
            'Select a fruit you like:'
            
            Displays the menu where the user is forced to select one of the 3 
            options. When selected the result is stored in the variable 
            '$myFavoriteFruit'
        #>
    
        [CmdLetBinding()]
        Param (
            [Parameter(Mandatory, ValueFromPipeline)]
            [Array]$Items,
            $QuitSelector = @{ 'Q' = 'Quit' },
            $displayTemplate = '{0}) {1}',
            $SelectionPrompt = 'Select an option:'
        )
    
        #region Build hash table with selector and value
        $hash = [Ordered]@{}
    
        for ($i = 0; $i -lt $Items.Count; $i++) {
            $key = "$($i+1)"; $value = $Items[$i]
            $hash[$key] = $value
        }
        #endregion
    
        #region Get the quit selector key if there is one
        $quitSelectorKey = $null
    
        if ($QuitSelector) {
            if ($QuitSelector.Keys[0] -match '^[0-9]+$') {
                throw 'The quit selector cannot be a number'
            }
            if ($QuitSelector.Count -ne 1) {
                throw 'The quit selector can hold only one key value pair'
            }
    
            $quitSelectorKey = $quitSelector.GetEnumerator() | 
            Select-Object -ExpandProperty Key
    
            if ($quitSelectorKey -isNot [String]) {
                throw 'The quit selector key needs to be of type string'
            }
        }
        #endregion
    
        #region Display the menu
        do {
            foreach ($item in $hash.getEnumerator()) {
                $params = @{
                    Object          = $displayTemplate -f 
                    $item.key, "$($item.Value)" 
                    ForegroundColor = 'yellow'
                }
                Write-Host @params
            }
            if ($QuitSelector) {
                $params = @{
                    Object          = $displayTemplate -f 
                    $quitSelectorKey, "$($QuitSelector.Values[0])"
                    ForegroundColor = 'DarkGray'
                }
                Write-Host @params
            }
    
            $selected = Read-Host -Prompt $SelectionPrompt
        } until (
            ($hash.Keys -contains $selected) -or 
            ($selected -eq $quitSelectorKey)
        )
        #endregion
    
        #region Return the selected value
        if ($selected -ne $quitSelectorKey) {
            $returnValue = $hash[$selected]
            Write-Verbose "Selected: $selected) $returnValue"
            $returnValue
        }
        #endregion
    }

    Further updated to this code can be found on GitHub here.

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account