Powershell: Add a Read-Choice cmdlet

Created on 5 Apr 2018  路  5Comments  路  Source: PowerShell/PowerShell

Due to the current lack to build a GUI with PowerShell core, we need an possibillity to interact with the user of our scripts.
Because Read-Host exist and only a few People know that $host.ui.PromptForChoice() exist
make this a full blown Cmdlet.
So the million scripters has not to implement and ship it by itself.

One implementation of this is currently inside the Module: Plaster.

function PromptForChoice([string]$ParameterName, [ValidateNotNull()]$ChoiceNodes, [string]$prompt,
                                 [int[]]$defaults, [switch]$IsMultiChoice) {
            $choices = New-Object 'System.Collections.ObjectModel.Collection[System.Management.Automation.Host.ChoiceDescription]'
            $values = New-Object object[] $ChoiceNodes.Count
            $i = 0

            foreach ($choiceNode in $ChoiceNodes) {
                $label = InterpolateAttributeValue $choiceNode.label (GetErrorLocationParameterAttrVal $ParameterName label)
                $help  = InterpolateAttributeValue $choiceNode.help  (GetErrorLocationParameterAttrVal $ParameterName help)
                $value = InterpolateAttributeValue $choiceNode.value (GetErrorLocationParameterAttrVal $ParameterName value)

                $choice = New-Object System.Management.Automation.Host.ChoiceDescription -Arg $label,$help
                $choices.Add($choice)
                $values[$i++] = $value
            }

            $retval = [PSCustomObject]@{Values=@(); Indices=@()}

            if ($IsMultiChoice) {
                $selections = $Host.UI.PromptForChoice('', $prompt, $choices, $defaults)
                foreach ($selection in $selections) {
                    $retval.Values += $values[$selection]
                    $retval.Indices += $selection
                }
            }
            else {
                if ($defaults.Count -gt 1) {
                    throw ($LocalizedData.ParameterTypeChoiceMultipleDefault_F1 -f $ChoiceNodes.ParentNode.name)
                }

                $selection = $Host.UI.PromptForChoice('', $prompt, $choices, $defaults[0])
                $retval.Values = $values[$selection]
                $retval.Indices = $selection
            }

            $retval
        }
Issue-Discussion

Most helpful comment

@KevinMarquette:

While it's a good idea to take full advantage of the parameter "plumbing" in general, that is a separate issue from _building friendly user interfaces_, notably _for non-PS-savvy users_.

A Read-Choice cmdlet, or an extended Read-Host cmdlet, as @rkeithhill suggests, would be in support of the latter scenario.


As an aside: The current parameter-"plumbing" UX leaves much to be desired and deserves improving in its own right; e.g., try the following:

& { param([ValidateSet('one', 'two', 'three')] [parameter(Mandatory)] [string] $Foo )
  • There is no tab completion, and no hint as to what the valid values are.
  • Punishment for providing an invalid value is swift: the command is instantly aborted.

Personally, given the poor - and broken (#7093, #4068) -
UX, I instantly terminate an unexpected-by-me prompt resulting from accidentally not supplying an argument to a mandatory parameter, and I never use the feature _intentionally_.

All 5 comments

Read-Host and prompting for a choice are generally discouraged in favor of using parameter features. In that light, I think it's ok for prompting for a choice be a little more work to encourage the use of parameters instead.

Read-Host and prompting for a choice are generally discouraged in favor of using parameter features.

Perhaps. Even so, there are definitely times where you need to prompt for more than a single value. And when I show colleagues what you have to do in PowerShell to implement that, I hang my head in shame. Same as when I show them what a function has to do to "really" handle path parameters. This should be addressed IMO. Perhaps using "parameterset" features of Read-Host?

The choice Feature was there ever since PowerShell and will be stay (forever) i think, because it is console the native runtime Environment of PowerShell.
So i think it makes no sense to discuss the existence or if it makes sense or can be replaced by other technics. It is there.

in my Point of view we discuss here to make a little Babystep. The question of showing such a hidden gem on the Frontdoor or leave it hidden.
For me the decision was already made, because Read-Host is there and Write-Host has even a few use cases.

To put it inside Read-Host is to put it from one hidden place to another, better then nothing because it is implemented.
I think coice is too Feature rich to hide it inside Read-Host

@KevinMarquette:

While it's a good idea to take full advantage of the parameter "plumbing" in general, that is a separate issue from _building friendly user interfaces_, notably _for non-PS-savvy users_.

A Read-Choice cmdlet, or an extended Read-Host cmdlet, as @rkeithhill suggests, would be in support of the latter scenario.


As an aside: The current parameter-"plumbing" UX leaves much to be desired and deserves improving in its own right; e.g., try the following:

& { param([ValidateSet('one', 'two', 'three')] [parameter(Mandatory)] [string] $Foo )
  • There is no tab completion, and no hint as to what the valid values are.
  • Punishment for providing an invalid value is swift: the command is instantly aborted.

Personally, given the poor - and broken (#7093, #4068) -
UX, I instantly terminate an unexpected-by-me prompt resulting from accidentally not supplying an argument to a mandatory parameter, and I never use the feature _intentionally_.

Read-Choice would also be mockable with Pester...https://stackoverflow.com/q/57698737/9660

Stack Overflow
Considering the below Powershell code, is there a way to mock $host.ui.PromptForChoice without the internalMenuWrapper function? <# .Synopsis wrap the menu so we can mock calls to i...
Was this page helpful?
0 / 5 - 0 ratings