modules/HomeLab.UI/Public/Show-Menu.ps1
<#
.SYNOPSIS Displays a menu and returns the user's selection. .DESCRIPTION Displays a formatted menu with the provided title and menu items, and returns the user's selection. Supports customizable formatting, input validation, and multiple navigation options. .PARAMETER Title The title of the menu. .PARAMETER MenuItems A hashtable containing the menu items, where the key is the menu item number and the value is the menu item text or a hashtable with Text and Color properties. .PARAMETER ReturnToMain If specified, adds a "Return to Main Menu" option with key "M". .PARAMETER ExitOption Specifies the key for the exit/return option (default is "Q" for quit). .PARAMETER ExitText Text to display for the exit option (default is "Quit" or "Return to Main Menu" if ReturnToMain is specified). .PARAMETER ShowProgress If specified, shows a progress bar when loading the menu. .PARAMETER DefaultOption Specifies the default option that will be selected if the user presses Enter without a selection. .PARAMETER TitleColor The color to use for the menu title (default is Cyan). .PARAMETER BorderChar Character to use for menu borders (default is "-"). .PARAMETER ValidateInput If specified, validates user input against available options and prompts again if invalid. .PARAMETER ShowStatus Optional status message to display at the bottom of the menu. .PARAMETER StatusColor Color for the status message (default is Yellow). .PARAMETER ShowHelp If specified, adds a help option to the menu with key "?". .EXAMPLE $menuItems = @{ "1" = "Option 1" "2" = "Option 2" "3" = @{Text = "Important Option"; Color = "Red"} } $selection = Show-Menu -Title "Main Menu" -MenuItems $menuItems -DefaultOption "1" -ValidateInput .NOTES Author: Jurie Smit Date: March 12, 2025 Version: 1.0.2 #> function Show-Menu { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Title, [Parameter(Mandatory = $true)] [hashtable]$MenuItems, [Parameter(Mandatory = $false)] [switch]$ReturnToMain, [Parameter(Mandatory = $false)] [string]$ExitOption = "0", [Parameter(Mandatory = $false)] [string]$ExitText, [Parameter(Mandatory = $false)] [switch]$ShowProgress, [Parameter(Mandatory = $false)] [string]$DefaultOption, [Parameter(Mandatory = $false)] [System.ConsoleColor]$TitleColor = [System.ConsoleColor]::Cyan, [Parameter(Mandatory = $false)] [string]$BorderChar = "-", [Parameter(Mandatory = $false)] [switch]$ValidateInput, [Parameter(Mandatory = $false)] [string]$ShowStatus, [Parameter(Mandatory = $false)] [System.ConsoleColor]$StatusColor = [System.ConsoleColor]::Yellow, [Parameter(Mandatory = $false)] [switch]$ShowHelp ) if ($ShowProgress) { # Show a quick progress bar when loading the menu. for ($i = 0; $i -le 100; $i += 10) { Write-Progress -Activity "Loading Menu" -Status "Please wait..." -PercentComplete $i Start-Sleep -Milliseconds 50 } Write-Progress -Activity "Loading Menu" -Completed } # Clear the screen. Clear-Host # Calculate border width based on title and menu items. $maxLength = $Title.Length foreach ($item in $MenuItems.Values) { $itemText = if ($item -is [hashtable]) { $item.Text } else { $item } $maxLength = [Math]::Max($maxLength, $itemText.Length + 6) # +6 for " [X] " prefix } $borderWidth = [Math]::Min([Math]::Max(40, $maxLength + 4), $Host.UI.RawUI.WindowSize.Width - 4) # Display the title with border. $border = $BorderChar * $borderWidth Write-Host "`n$border" -ForegroundColor $TitleColor $paddedTitle = " $Title " $leftPadding = [Math]::Floor(($borderWidth - $paddedTitle.Length) / 2) $rightPadding = $borderWidth - $paddedTitle.Length - $leftPadding $titleLine = ($BorderChar * $leftPadding) + $paddedTitle + ($BorderChar * $rightPadding) Write-Host $titleLine -ForegroundColor $TitleColor Write-Host "$border`n" -ForegroundColor $TitleColor # Create a sorted list of keys to avoid modifying the collection during enumeration. $sortedKeys = @($MenuItems.Keys | Sort-Object) # Display menu items. $validOptions = @() foreach ($key in $sortedKeys) { $validOptions += $key $item = $MenuItems[$key] if ($item -is [hashtable]) { $text = $item.Text $color = if ($item.ContainsKey('Color')) { $item.Color } else { 'White' } } else { $text = $item $color = 'White' } $prefix = if ($key -eq $DefaultOption) { " [$key]* " } else { " [$key] " } Write-Host "$prefix$text" -ForegroundColor $color } # Add navigation options. $navigationOptions = @() if ($ShowHelp) { Write-Host "`n [?] Help" -ForegroundColor Yellow $validOptions += '?' $navigationOptions += '?' } # Determine exit/return text. if (-not $ExitText) { $ExitText = if ($ReturnToMain) { "Return to Main Menu" } else { "Exit" } } Write-Host " [$ExitOption] $ExitText" -ForegroundColor Yellow $validOptions += $ExitOption $navigationOptions += $ExitOption # Show status if provided. if ($ShowStatus) { Write-Host "`n$($BorderChar * 20)" -ForegroundColor $StatusColor Write-Host $ShowStatus -ForegroundColor $StatusColor Write-Host "$($BorderChar * 20)" -ForegroundColor $StatusColor } # Show default option hint if provided. if ($DefaultOption) { Write-Host "`n* Default option (press Enter to select)" -ForegroundColor DarkGray } # Get user choice with validation if requested. $choice = $null do { Write-Host "`nSelect an option: " -ForegroundColor Cyan -NoNewline $userInput = Read-Host # Use default option if input is empty and default is provided. if ([string]::IsNullOrEmpty($userInput) -and $DefaultOption) { $choice = $DefaultOption break } $choice = $userInput.Trim() $isValid = -not $ValidateInput -or ($validOptions -contains $choice) if (-not $isValid) { Write-Host "Invalid option. Please try again." -ForegroundColor Red } } while (-not $isValid) # Return a PSCustomObject instead of a hashtable to ensure compatibility with Get-Member checks return [PSCustomObject]@{ Choice = $choice IsNavigationOption = ($navigationOptions -contains $choice) IsExit = ($choice -eq $ExitOption) IsHelp = ($ShowHelp -and $choice -eq '?') RawInput = $userInput } } |