Denis Molodtsov
Denis Molodtsov

Reputation: 824

Is there a Module or Something Similar for Interactive Prompts in PowerShell?

Is there something we can use in PowerShell to ask users to select one item from an array of items? For example, I like how Inquirer.js can do it.

enter image description here

I have also seen PoshGui, but it seems too much work to create just a simple prompt.

The reason we want something similar is that we need to provide deployment scripts for our clients and make deployment guides as easy as possible. Asking users to select one item on a screen is much better than asking them to insert some guid to a config file.

Do you have any suggestions for user prompts for arrays?

Upvotes: 4

Views: 3063

Answers (5)

mklement0
mklement0

Reputation: 439058

Unfortunately, there's little that is built in, and that little is hard to discover - see below.

Potentially providing a dedicated Read-Choice cmdlet or enhancing Read-Host is being discussed in GitHub issue #6571.

The $host.ui.PromptForChoice() method supports presenting a menu of choices, but it has limitations:

  • The choices are presented on a single line (which may wrap).

  • Only single-character selectors are supported.

  • The selector character must be a part of the menu-item text.

  • Submitting a selection always requires pressing Enter

  • A ? option is invariably offered, even if you don't want to / need to provide explanatory text for each menu item.

Here's an example:

# The list of choices to present.
# Specfiying a selector char. explicitly is mandatory; preceded it by '&'.
# Automating that process while avoiding duplicates requires significantly
# more effort.
# If you wanted to include an explanation for each item, selectable with "?",
# you'd have to create each choice with something like:
#   [System.Management.Automation.Host.ChoiceDescription]::new("&Jumbo", "16`" pie")
$choices = '&Jumbo', '&Large', '&Standard', '&Medium', 'Sma&ll', 'M&icro'

# Prompt the user, who must type a selector character and press ENTER.
# * Each choice label is preceded by its selector enclosed in [...]; e.g.,
#   '&Jumbo' -> '[J] Jumbo'
# * The last argument - 0 here - specifies the default index.
#   * The default choice selector is printed in *yellow*.
#   * Use -1 to indicate that no default should be provided
#     (preventing empty/blank input).
# * An invalid choice typed by the user causes the prompt to be 
#   redisplayed (without a warning or error message).
$index = $host.ui.PromptForChoice("Choose a Size", "Type an index and press ENTER:", $choices, 0)

"You chose: $($choices[$index] -replace '&')"

This yields something like:

enter image description here

Upvotes: 3

frizik
frizik

Reputation: 2026

You can also try ps-menu module: https://www.powershellgallery.com/packages/ps-menu

Sample:

enter image description here

Upvotes: 5

Denis Molodtsov
Denis Molodtsov

Reputation: 824

All of the answers are correct, but I also wrote a few reusable PowerShell helper functions. Readme. I auto-generate basic looking WinForms. Looks ugly, but works.

https://github.com/Zerg00s/powershell-forms

$selectedItem = Get-FormArrayItem (Get-ChildItem)

enter image description here

$Delete = Get-FormBinaryAnswer "Delete file?"

enter image description here

$newFileName = Get-FormStringInput "Enter new file name" -defaultValue "My new file"

enter image description here

# -------------------------------------------------------------------------------
# Prepare the list of inputs that user needs to populate using an interactive form    
# -------------------------------------------------------------------------------
$preDeployInputs = @{
    suffix                       = ""
    SPSiteUrl                    = "https://ENTER_SHAREPOINT_SITE.sharepoint.com"
    TimeZone                     = "Central Standard Time"
    sendGridRegistrationEmail    = "ENTER_VALID_EMAIL_ADDRESS"
    sendGridRegistrationPassword = $sendGridPassword
    sendGridRegistrationCompany  = "Contoso & Tailspin"
    sendGridRegistrationWebsite  = "https://www.company.com"
    fromEmail                    = "[email protected]"
}

$preDeployInputs = Get-FormItemProperties -item $preDeployInputs -dialogTitle "Fill these required fields"

enter image description here

Upvotes: 1

Theo
Theo

Reputation: 61148

You can be as creative as you like of course.. Here's a small function that builds a console menu:

function Simple-Menu {
    Param(
        [Parameter(Position=0, Mandatory=$True)]
        [string[]]$MenuItems,
        [string] $Title
    )

    $header = $null
    if (![string]::IsNullOrWhiteSpace($Title)) {
        $len = [math]::Max(($MenuItems | Measure-Object -Maximum -Property Length).Maximum, $Title.Length)
        $header = '{0}{1}{2}' -f $Title, [Environment]::NewLine, ('-' * $len)
    }

    # possible choices: didits 1 to 9, characters A to Z
    $choices = (49..57) + (65..90) | ForEach-Object { [char]$_ }
    $i = 0
    $items = ($MenuItems | ForEach-Object { '[{0}]  {1}' -f $choices[$i++], $_ }) -join [Environment]::NewLine

    # display the menu and return the chosen option
    while ($true) {
        cls
        if ($header) { Write-Host $header -ForegroundColor Yellow }
        Write-Host $items
        Write-Host

        $answer = (Read-Host -Prompt 'Please make your choice').ToUpper()
        $index  = $choices.IndexOf($answer[0])

        if ($index -ge 0 -and $index -lt $MenuItems.Count) {
            return $MenuItems[$index]
        }
        else {
            Write-Warning "Invalid choice.. Please try again."
            Start-Sleep -Seconds 2
        }
    }
}

You can use it like below:

$menu = 'Pizza', 'Steak', 'French Fries', 'Quit'
$eatThis = Simple-Menu -MenuItems $menu -Title "What would you like to eat?"
switch ($eatThis) {
    'Pizza' {
        $menu = 'Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'
        $eatThat = Simple-Menu -MenuItems $menu -Title "What size do you need?"
        Write-Host "`r`nEnjoy your $eatThat $eatThis!`r`n" -ForegroundColor Green
    }
    'Steak' {
        $menu = 'Well-done', 'Medium', 'Rare', 'Bloody', 'Raw'
        $eatThat = Simple-Menu -MenuItems $menu -Title "How would you like it cooked?"
        Write-Host "`r`nEnjoy your $eatThat $eatThis!`r`n" -ForegroundColor Green
    }
    'French fries' {
        $menu = 'Mayonaise', 'Ketchup', 'Satay Sauce', 'Piccalilly'
        $eatThat = Simple-Menu -MenuItems $menu -Title "What would you like on top?"
        Write-Host "`r`nEnjoy your $eatThis with $eatThat!`r`n" -ForegroundColor Green
    }
}

Result:

main menu

side menu

Upvotes: 2

mjsqu
mjsqu

Reputation: 5452

I've used the Out-GridView cmdlet for this in the past. When used with the -PassThru switch it allows the selected item to be passed to a variable. The example image you've shown, when written using Out-GridView (ogv if you want to use the alias) is:

$task = Read-Host -Prompt "What do you want to do?"

if ($task -eq "Order a pizza") {
  $pizza_sizes = @('Jumbo','Large','Standard','Medium','Small','Micro')
  $size = $pizza_sizes | Out-GridView -Title "What size do you need?"  -PassThru
  Write-Host "You have selected $size"
}

There are many considerations to take into account with this, the windows might not appear where you want them to and they may appear behind others. Also, this is a very simple example that obviously needs error handling and other aspects built in. I'd suggest some testing or to get a second opinion from others on SO.

Upvotes: 2

Related Questions