Powershell: Select Choice

The need to prompt a user for a choice in a text menu (i.e. in the console), and a slight dissatisfaction with the commonly recommended $Host.ui.PromptForChoice() after experimenting a bit with it, triggered me to ultimately write my own function.

PromptForChoice()

For a start, let’s create a script that makes use of such a dialog and uses the well-known PromptForChoice function:
In this minimalistic version, the only parameter that I define is -Interactive, and even that one is just non-essential demonstration material:

Because in my original code, the function initially worked fully automatic, but sometime down the line, the need arose to let the user make a decision directly – that’s when an interactive mode was added.

Anyways, that’s just some background noise and not really relevant to the actual topic…

Example

Param
(
    [Parameter(ParameterSetName="Interactive")]
    [switch] $Interactive
)

# --------------------------------------------------------------------------------------------------
# Helper function:

function HostUIPromptForChoice
{
    Param
    (
        [Parameter(Mandatory=$true)] $Caption,
        [Parameter(Mandatory=$true)] $Message,
        [Parameter(Mandatory=$true)] $Choices,
        $Default # Index of the default selection. '-1': No default.
    )

    [System.Collections.ObjectModel.Collection[System.Management.Automation.Host.ChoiceDescription]] $ChoiceDescriptions = @()
    $Choices | % { $ChoiceDescriptions.Add([System.Management.Automation.Host.ChoiceDescription]::New($_[0], $_[1])) }
    # [int[]] $MultipleDefaults = 0, 2
        # Alternative syntax to allow multiple defaults (e.g. 0 and 2);
        # but one's code must be aware of such a possibilty to be able to handle it!
    $Host.ui.PromptForChoice($Caption, $Message, $ChoiceDescriptions, $Default)
}

# --------------------------------------------------------------------------------------------------
# Main part:
 
if ($Interactive)
{
    $Caption = "--- Server Selection ---"   
    $Message = "Connect to which server?"
    
    $Choices = @(
        ("Server&1", "Logon to server1.example.net"),
        ("Server&2", "Logon to server2.example.net"),
        ("Server&3", "Logon to server3.example.net"),
        ("&TestA"  , "Test A: OK: First letter is the shortcut"),
        #("&TestB"  , "Test B: Fatal error: Not unique (same letter as the shortcut)"),
        ("TestC"   , "Test C: Warning: Without shortcut marker"),
        #("TestD&"  , "Test D: Fatal error: Shortcut marker at invalid position"),
        ("&Quit"   , "Quit/Abort")
    )
    
    $Result = HostUIPromptForChoice -Caption $Caption -Message $Message -Choices $Choices -Default 0    
    
    # The result of PromptForChoice is the index (offset, starting at zero) in the $Choices array.
    # Problem: If the position or order changes, so must this switch statement!
    switch ($Result)
    {
        "0"     { $Server = "server1.example.net" }
        "1"     { $Server = "server2.example.net" }
        "2"     { $Server = "server3.example.net" }
        "3"     { $Server = "TEST" }
        default { $Server = "~ No selection ~" }
    }
    
    write-host "You've chosen: $Server"
}

OK, so this works and is actually not too bad:

Host.UI.PromptForChoice

It looked fine to me on first glance, but it has some issues that I don’t particular like; for example:

Select-saoeChoice

My function, Select-saoeChoice, solves the first issue and switches to a vertical layout. It also makes some checks on the validity of the shortcuts for the menu, plus some more format and layout changes.

Other important difference are these:

Example

# (... The same as before...)

# --------------------------------------------------------------------------------------------------
# Helper function:
#
# -> Separate function "Select-saoeChoice" in my module; see link blog post.

# --------------------------------------------------------------------------------------------------
# Main part:
 
if ($Interactive)
{
    # (... The same as before...)

    # Now calling my function; note the different -Default: It's a character, not an index!
    $Result = Select-saoeChoice -Caption $Caption -Message $Message -Choices $Choices -Default 'T'
    
    # No longer using an index/offset, but the shortcut character to match the selection!
    switch ($Result)
    {
        "1"     { $Server = "server1.example.net" }
        "2"     { $Server = "server2.example.net" }
        "3"     { $Server = "server3.example.net" }
        "T"     { $Server = "TEST" }
        default { $Server = "~ No selection ~" }
    }
    
    write-host "You've chosen: $Server"
}

Select-saoeChoice