How to get user input more nicely in PowerShell

Long, long ago when I was using my first Microsoft product, I knew one way to get input from the user. The product was Commodore BASIC (in those days we wrote it in uppercase and knew it stood for Beginners All-purpose Symbol Instructional Code). and the method was INPUT. This was back in early 1979 : the user typed something pressed Enter and you could then process it. Later I learnt about GET so you could input with a single keystroke (and process it to see if the user had given you the right input). After all the myriad ways of getting input in the GUI,   PowerShell seems a bit retro (on the surface at least). But recently, I was poking around looking for something else and found that under the surface PowerShell has got some useful tricks which I’ve started to use and I’ll share here.

PowerShell has a variable $host which contains information about the program where it is running (the console host or the ISE environment). $host.ui contains a “User Interface” object which has some interesting methods

  • Prompt / PromptForChoice / PromptForCredential
  • ReadLine / ReadLineAsSecureString
  • Write  /  WriteDebugLine  /  WriteErrorLine  / WriteLine  / WriteProgress  / WriteVerboseLine / WriteWarningLine

The write ones have associated write- cmdlets, and the read ones are the basis of the read-host cmdlet. What about the prompt ones ? PowerShell has get-Credential but the  $host.ui.PromptForCredential has a couple of extra options: for example you can change the caption on the title bar and the message above the text boxes in the prompt. Like this:

 $Host.ui.PromptForCredential("","Enter an account to add the machine to the domain","$env:userdomain\$env:username","")

The two empty strings are the caption, which defaults to “Windows PowerShell credential Request” and the password field; so the result looks like this.

image

What about promptForChoice ? For some time I have been using a function first named “choose-list” and now named “Select list” to fall in line with the “approved verbs” from the PowerShell team. Dan who wrote the “soliciting new verbs”  post on the team blog got in touch with me to say I ought to do this, I didn’t think what “choose” did matches the description of “select” and put forward “choose” as new verb. It didn’t get approved but at some point the definition of Select will be broadened. This works well when you want to choose from something which is naturally a table, but if you want something like PowerShell’s own prompts.

image

That is where Prompt for choice comes into play

the choices which are passed to the method are in a slightly awkward format , you need to  use New-Object System.Collections.ObjectModel.Collection[System.Management.Automation.Host.ChoiceDescription]

and for each item on the list use its Add method so the natural thing was to wrap it in a function.

 Function Select-Item 
{    
<# 
     .Synopsis
        Allows the user to select simple items, returns a number to indicate the selected item. 

    .Description 

        Produces a list on the screen with a caption followed by a message, the options are then
        displayed one after the other, and the user can one. 
  
        Note that help text is not supported in this version. 

    .Example 

        PS> select-item -Caption "Configuring RemoteDesktop" -Message "Do you want to: " -choice "&Disable Remote Desktop",
           "&Enable Remote Desktop","&Cancel"  -default 1
       Will display the following 
  
        Configuring RemoteDesktop   
        Do you want to:   
        [D] Disable Remote Desktop  [E] Enable Remote Desktop  [C] Cancel  [?] Help (default is "E"): 

    .Parameter Choicelist 

        An array of strings, each one is possible choice. The hot key in each choice must be prefixed with an & sign 

    .Parameter Default 

        The zero based item in the array which will be the default choice if the user hits enter. 

    .Parameter Caption 

        The First line of text displayed 

     .Parameter Message 

        The Second line of text displayed     
#> 

Param(   [String[]]$choiceList, 

         [String]$Caption="Please make a selection", 

         [String]$Message="Choices are presented below", 

         [int]$default=0 

      ) 

   $choicedesc = New-Object System.Collections.ObjectModel.Collection[System.Management.Automation.Host.ChoiceDescription] 

   $choiceList | foreach  { $choicedesc.Add((New-Object "System.Management.Automation.Host.ChoiceDescription" -ArgumentList $_))} 

   $Host.ui.PromptForChoice($caption, $message, $choicedesc, $default) 
}  

One of the things on my blogging backlog is the some the of the V2 features like the help text which I’ve used here. As you can see there is a lot more help than anything else, and the parameters take up more space than the business end of the code. The function will return a number, starting from zero which indicates which option was chosen. There are a couple of ways I have been using this, because two options return 0 or 1 I can easy convert that to true or false – the first one will always be FALSE. Also notice the selection character for an option is prefixed with &

 PS > $enabled=[boolean](select-item -Caption "Configuring RemoteDesktop" -Message "Do you want to: " -choice "&Disable Remote Desktop", 
"&Enable Remote Desktop"  -default 1 ) 
Configuring RemoteDesktop 
Do you want to: 
[D] Disable Remote Desktop  [E] Enable Remote Desktop  [?] Help (default is "E"): d 

PS C:\Users\jamesone\Documents\windowsPowershell> $enabled 
False

This throws up an issue though – what if the user doesn’t want to change the state ? I can have Disable , Enable and Cancel  but cancel will return 2 , and any non zero value evaluates to true, so cancel will enable remote desktop in that case! The obvious thing to do is to use a switch statement, which does nothing if the cancel option is chosen.

 Switch (select-item -Caption "Configuring RemoteDesktop" -Message "Do you want to: " -choice "&Disable Remote Desktop",

                   "&Enable Remote Desktop","&Cancel"  -default 1 )
              {

                   1 {Enable-RemoteDesktop -confirm }  
                   0 { Disable-RemoteDesktop -confirm }  
              }  

Enable and disable- remotedesktop are functions I'm working on , not standard powershell cmdlets. More on that another time. This is part of a menu for configuring systems and I’m making use of something I got from James Brundage (who post more on the PowerShell blog than his own these days). I don’t want to steal his thunder because it’s another technique which I’m using in lots of places and I think should go on the list of good practices.