Skip to content

Instantly share code, notes, and snippets.

@xv
Last active May 24, 2026 21:19
Show Gist options
  • Select an option

  • Save xv/51ff8b49e96144b6e21ecd7c52a15225 to your computer and use it in GitHub Desktop.

Select an option

Save xv/51ff8b49e96144b6e21ecd7c52a15225 to your computer and use it in GitHub Desktop.
Prompts an arrow-key navigable menu of selectable options. Compatible with Windows PowerShell 5.1.
# The very basic/barebones version
function Show-Menu {
param(
[Parameter(Mandatory)]
[string] $Title,
[Parameter(Mandatory)]
[string[]] $Options
)
$selected = 0
$count = $Options.Count
do {
Clear-Host
Write-Host $Title
Write-Host
foreach ($idx in 0..($count - 1)) {
$option = $Options[$idx]
if ($idx -eq $selected) {
Write-Host "[ $option ]" -b Cyan -f Black
} else {
Write-Host " $option"
}
}
Write-Host
# https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
$key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").VirtualKeyCode
switch ($key) {
# VK_UP
0x26 { $selected = ($selected - 1 + $count) % $count }
# VK_DOWN
0x28 { $selected = ($selected + 1) % $count }
# VK_RETURN
0x0D { return $selected }
}
} while ($true)
}
# Usage
# Returns the zero-based index of the selected option
$menuResult = Show-Menu `
-Title "Select an action:" `
-Options @("Backup", "Restore", "Exit")
Write-Host "You selected option $menuResult"
# This version treats options as objects containing a Name, Description and an
# Enabled/Disabled state. The function returns the selected object
function Show-Menu {
param(
[Parameter(Mandatory)]
[string] $Title,
[Parameter(Mandatory)]
[array] $Options
)
$count = $Options.Count
# Width of the Name column
$nameWidth = (
($Options |
ForEach-Object { $_.Name.Length } | Measure-Object -Maximum
).Maximum
) + 2
# Total menu width
$menuWidth = ($Options |
ForEach-Object {
if ([string]::IsNullOrWhiteSpace($_.Description)) {
$_.Name.Length
} else {
("{0,-$nameWidth} {1}" -f $_.Name, $_.Description).Length
}
} | Measure-Object -Maximum
).Maximum
function Get-NextEnabledIndex {
param(
[int] $Start,
[int] $Direction
)
$idx = $Start
do {
$idx = ($idx + $Direction + $count) % $count
} until ($Options[$idx].Enabled)
return $idx
}
$selected = 0
# Start on first enabled item
while (-not $Options[$selected].Enabled) {
$selected++
}
do {
Clear-Host
Write-Host $Title
Write-Host
foreach ($idx in 0..($count - 1)) {
$option = $Options[$idx]
if ([string]::IsNullOrWhiteSpace($option.Description)) {
$line = $option.Name
} else {
$line = "{0,-$nameWidth} {1}" -f `
$option.Name,
$option.Description
}
# Make all menu items have equal width
$line = $line.PadRight($menuWidth)
if (-not $option.Enabled) {
Write-Host " $line" `
-ForegroundColor DarkGray
} elseif ($idx -eq $selected) {
Write-Host "[ $line ]" `
-BackgroundColor Cyan `
-ForegroundColor Black
} else {
Write-Host " $line"
}
}
Write-Host
# https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
$key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown").VirtualKeyCode
switch ($key) {
# VK_UP
0x26 { $selected = Get-NextEnabledIndex $selected -1 }
# VK_DOWN
0x28 { $selected = Get-NextEnabledIndex $selected 1 }
# VK_RETURN
0x0D { return $Options[$selected] }
}
} while ($true)
}
# Usage
# Returns the selected object from the $Options array
$menuResult = Show-Menu `
-Title "Select an action:" `
-Options @(
@{
Name = "Backup"
Description = "Creates a backup of current configuration"
Enabled = $true
},
@{
Name = "Restore"
Description = "Restores configuration from backup"
Enabled = $false
},
@{
Name = "Exit"
Description = "Exits without making any changes"
Enabled = $true
}
)
Write-Host "You selected option '$($menuResult.Name)'"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment