Skip to main content

Command Palette

Search for a command to run...

A better PowerShell Console Menu

Updated
4 min read
A better PowerShell Console Menu
C

Caroline is a cloud engineer with a decade of experience in the IT industry. She create content about cloud and programming. You can follow her on YouTube, Twitter and TikTok.

I was looking to add a console-based menu to give the users of my scripts choices. I have worked with PowerShell for years, and most of the time, I needed to give users a choice; I either added it as an argument when running the script or used the classic "enter 1 for option A, 2 for option B" model. I've never liked either option, but they were good enough at the time.

So, I did like anyone would have done, and I googled how to create a PowerShell menu. I found very few examples of people creating interactive menus, but there were no tutorials, just code to download. So I took on the challenge of creating an interactive PowerShell menu from scratch and sharing the process with you.

Design Considerations

Like any good engineering project, let's start with a design doc to keep in mind when creating the script.

  • Users shouldn't have to edit the code for the menu (requires input).
  • Users should select the options using the up and down keyboard arrows and press enter to select the desired option.
  • When selecting a value, the menu should highlight the selected option line.
  • The menu should return the selected value.
  • The menu should not clear the console.

Pseudocode

I usually do this part in my head, but since it is an important part of the process, I figured I would highlight it:

  1. Get the options from the user
  2. Set the current selection to the first item in the list of options
  3. Print all the lines in the console
  4. Wait for Arrow Up or Arrow Down or Enter Keys to be pressed
    • if Arrow up is pressed, store the new selected option position (current selection - 1)
    • if Arrow Down is pressed, store the new selected option position (current selection + 1)
  5. Return the selected option
  6. If the selection is out of bound, set it to the proper position (0 or # of options - 1)
  7. Go back to #3

The code

<#
    .SYNOPSIS
        Displays a selection menu and returns the selected item

    .DESCRIPTION
        Takes a list of menu items, displays the items, and returns the user's selection.
        Items can be selected using the up and down arrow and the enter key.

    .PARAMETER MenuItems
        List of menu items to display

    .PARAMETER MenuPrompt
        Menu prompt to display to the user.

    .EXAMPLE
        PS C:\> Get-MenuSelection -MenuItems $value1 -MenuPrompt 'Value2'

    .NOTES
        Additional information about the function.
#>
function Get-MenuSelection {
    [CmdletBinding()]
    [OutputType([string])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$MenuItems,
        [Parameter(Mandatory = $true)]
        [String]$MenuPrompt
    )
    # store initial cursor position
    $cursorPosition = $host.UI.RawUI.CursorPosition
    $pos = 0 # current item selection

    #==============
    # 1. Draw menu
    #==============
    function Write-Menu {
        param (
            [int]$selectedItemIndex
        )
        # reset the cursor position
        $Host.UI.RawUI.CursorPosition = $cursorPosition
        # Padding the menu prompt to center it
        $prompt = $MenuPrompt
        $maxLineLength = ($MenuItems | Measure-Object -Property Length -Maximum).Maximum + 4
        while ($prompt.Length -lt $maxLineLength + 4) {
            $prompt = " $prompt "
        }
        Write-Host $prompt -ForegroundColor Green
        # Write the menu lines
        for ($i = 0; $i -lt $MenuItems.Count; $i++) {
            $line = "    $($MenuItems[$i])" + (" " * ($maxLineLength - $MenuItems[$i].Length))
            if ($selectedItemIndex -eq $i) {
                Write-Host $line -ForegroundColor Blue -BackgroundColor Gray
            }
            else {
                Write-Host $line
            }
        }
    }

    Write-Menu -selectedItemIndex $pos
    $key = $null
    while ($key -ne 13) {
        #============================
        # 2. Read the keyboard input
        #============================
        $press = $host.ui.rawui.readkey("NoEcho,IncludeKeyDown")
        $key = $press.virtualkeycode
        if ($key -eq 38) {
            $pos--
        }
        if ($key -eq 40) {
            $pos++
        }
        #handle out of bound selection cases
        if ($pos -lt 0) { $pos = 0 }
        if ($pos -eq $MenuItems.count) { $pos = $MenuItems.count - 1 }

        #==============
        # 1. Draw menu
        #==============
        Write-Menu -selectedItemIndex $pos
    }

    return $MenuItems[$pos]
}

The key to this code is $host.UI.RawUI.CursorPosition this property allows you to read or move the cursor in the console to the desired location. It is basically the same idea as Clear-Host, except Clear-Host resets it to the 0,0 position.