Sunday, September 16, 2007

Make a choice


Remember the old MS-DOS choice command? It prompts the user to make a choice in a batch program by displaying a prompt and pausing for the user to choose from a set of user-option keys.

In PowerShell, you can provide the same mechanism with .NET built-in classes without the need for external files. You can see this method in action on every cmdlet declared with -confirm parameter.

PS > Get-Process s* | Stop-Process -confirm

Confirm
Are you sure you want to perform this action?
Performing operation "Stop-Process" on Target "services (520)".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"):



The user is presented with "plain old message box kind" prompt, built from a caption, message and number of choices to choose from:

Confirm = Caption
Are you sure you want to... = Message

And the choices:

[Y] Yes
[A] Yes to All
[N] No
[L] No to All
[S] Suspend

 

In my scripts I used to build this mechanism manually and write to the host the caption/message and choices until I 'found' this while digging on Mighty MoW's PowerTab scripts. The method is also described on MSDN PSHostUserInterface.PromptForChoice.

 

PS > $host.ui | gm

   TypeName: System.Management.Automation.Internal.Host.InternalHostUserInterface

Name                          MemberType
----                             ----------
...
Prompt                        Method
PromptForChoice         Method
PromptForCredential    Method
...

 

The definition for PromptForChoice is:


System.Int32 PromptForChoice(String caption, String message, Collection`1 choices, Int32 defaultChoice)

 

So, how can you implement it in your scripts?  Here is my variation:


$caption = "Confirm";
$message = "Are you sure?";
$yes = new-Object System.Management.Automation.Host.ChoiceDescription "&Yes","help";
$no = new-Object System.Management.Automation.Host.ChoiceDescription "&No","help";
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no);
$answer = $host.ui.PromptForChoice($caption,$message,$choices,0)

switch ($answer){
    0 {"You entered 0"; break}
    1 {"You entered 1"; break}
}

write-host "Rest of script goes here..."

 

Here is the output:

PS C:\Scripts> .\PromptForChoice.ps1

Confirm
Are you sure?
[Y] Yes  [N] No  [?] Help (default is "Y"): n
You entered 1
Rest of script goes here...
PS >

 

Note that the special character "&" (ampersand) might be embedded in the label string to indicate that the next character is a "hot key" (for example, "Yes to &All"). The hot key allows the user to quickly set the input focus to this choice.

The default choice is determined by the order/index of the collection variables ($choices). To indicate no default, set defaultChoice to -1:

$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no);

$yes = 0
$no = 1


$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no,$retry);

$yes = 0
$no = 1
$retry = 2


 

There is also the "Help" option, which we didn't explicitly define.
The strings "help" in the $yes and $no variables are the help text to display when the user types "?"

$no = new-Object System.Management.Automation.Host.ChoiceDescription "&No","help";

 

I also wanted to implement the Suspend feature. Suspending the process is totally awesome. You have another chance to pause operation and get up to date, verified information, before proceeding. I knew that there is something called "Nested Prompt" and something inside felt that this is the place where it might be used. So I made some tests and here's the code with Suspend.


$caption = "Confirm";
$message = "Are you sure?";
$yes = new-Object System.Management.Automation.Host.ChoiceDescription "&Yes","help";
$no = new-Object System.Management.Automation.Host.ChoiceDescription "&No","help";
$Suspend = new-Object System.Management.Automation.Host.ChoiceDescription "&Suspend","help";
$choices = [System.Management.Automation.Host.ChoiceDescription[]]($yes,$no,$suspend);
$Answer = $host.ui.PromptForChoice($caption,$message,$Choices,0)

switch ($Answer){

    0 {"You entered 0"; break}
    1 {"You entered 1"; break}

    2 {"You entered 2, entering nested prompt, type exit to return"; $Host.EnterNestedPrompt()}   
}

write-host "Rest of script goes here..."

 

Here is the output:

 

PS C:\Scripts> .\PromptForChoice.ps1

Confirm
Are you sure?
[Y] Yes  [N] No  [S] Suspend  [?] Help (default is "Y"): s
You entered 2, entering nested prompt, type exit to return
PS >>> exit
Rest of script goes here...
PS >


Note that the PowerShell prompt is visually changing, colored in yellow ( adding >> to the default prompt) indicating that you're inside a nested prompt. To exit nested prompts simply type exit.

2 comments:

Anonymous said...

Entering "-1" as the default choice doesn't work.

andrey said...

Hello!

Well, but, if i want to change the colors for "Caption", "Message" and for "Answers"?
How can I make this?

Thanks.