Search code examples
powershellwhile-loop

Detect when user cancels $host.ui.promptforchoice prompt


This is driving me nuts. If one runs the below script, cancels the $host.ui.promptforchoice, and checks $Choice, they get:

PS C:\> $Choice.gettype()

IsPublic IsSerial Name   BaseType                                                                                                      
-------- -------- ----   --------                                                                                                      
True     True     Int32  System.ValueType                                                                                               

PS C:\> write-host """$Choice"""
"-1"

PS C:\> $Choice -eq -1
True

What on earth am I missing in my do...while loop that has the script exiting (it isn't the return; I commented out the switch statement) without looping!

$s = ""

do
{
  $Choice = $host.ui.promptforchoice($ScriptFileName, "$($s)User this script is running under is not an admin acount.`r`n`r`nRunning this script will cause multiple password entries throughout.`r`n`r`nRecommend logging out and logging back in as an admin, then re-running. Proceed anyway?", @("&Yes", "&No"), 1)

  switch($Choice)
  {
    0 {write-host "hello";<#Log ...Proceeding with script#>}
    1 {write-host "`r`n`r`nQuitting script. Please login under an admin account and rerun`r`n" -foregroundcolor red -backgroundcolor yellow; return}
  }

  $s = "Selection Cancelled. Please make a selection. No to quit the script`r`n`r`n"
}
while($Choice -eq -1)

Solution

  • If you want to be able to catch CTRL+C as an option you can set TreatControlCAsInput to true however, using $HOST.UI.PromptForChoice is no longer an option if going this route. You can use ReadKey in this case, here a simple example of how your code should look:

    [console]::TreatControlCAsInput = $true
    while ($true) {
        Write-Host 'Options: [Y / N]'
        switch ([System.Console]::ReadKey($true)) {
            { $_.KeyChar -eq 'Y' } { Write-Host 'Y, do stuff...' }
            { $_.KeyChar -eq 'N' } { Write-Host 'Quitting script...'; return }
            { $_.Modifiers -eq 4 -and $_.Key -eq 67 } { Write-Host 'Ctrl+C... do something' }
            default { Write-Host 'Invalid choice' }
        }
    }
    

    Another option that wouldn't allow the user to CTRL+C during the prompt, would be to use a MessageBox from Windows Forms. You can also combine this approach with TreatControlCAsInput set to true if you also want them to avoid being able to terminate the script on one of the switch options code paths.

    Add-Type -AssemblyName System.Windows.Forms
    
    while ($true) {
        $result = [System.Windows.Forms.MessageBox]::Show(
            'Do stuff?',
            'Doing stuff',
            [System.Windows.Forms.MessageBoxButtons]::YesNo, # Probably just 'OK' is better here
            [System.Windows.Forms.MessageBoxIcon]::Question)
    
        switch ($result) {
            ([System.Windows.Forms.DialogResult]::Yes) { Write-Host 'Do stuff' }
            ([System.Windows.Forms.DialogResult]::No) { Write-Host 'Quitting...'; return }
        }
    }