Home > Software engineering >  PowerShell Switch Executing Again On Quit
PowerShell Switch Executing Again On Quit

Time:11-27

I'm attempting to write a PowerShell menu with multiple switches, but can't figure out why previously run commands will execute again on quit. Any help would greatly be appreciated. The code I have so far is as follows:

function Show-Menu {
    param (
        [string]$Title = 'Menu'
    )
    Clear-Host
    Write-Host "`n============= $Title =============`n"
    Write-Host "Press 'A' to run all commands"
    Write-Host "Press '1' to run foo"
    Write-Host "Press '2' to run bar"
    Write-Host "Press 'Q' to quit"
}

do {
    Show-Menu
    $Selection = Read-Host "`nPlease make a selection"

    switch ($Selection) {
        'A' {
            $Actions = @('foo', 'bar')
        }
        '1' {
            $Actions = "foo"
        }
        '2' {
            $Actions = "bar"
        }
    }
    
    switch ( $Actions ) {
        'foo' { 
            Write-Host "foo executed"
            Start-Sleep -Seconds 2
        }
        'bar' { 
            Write-Host "bar executed"
            Start-Sleep -Seconds 2
        }
    }
}
until ($Selection -eq 'q')

CodePudding user response:

This is happening because of your do...until loop. You're committing to executing the loop before the user input is taken. Since this is the case, $Actions is already set from the previous iteration of the loop, so it runs what had previously ran.

This means that if you don't override $Actions for every iteration of the loop, this will happen for other commands as well.

A simple fix to this would be to add a case for q to set $Actions to something that isn't in the switch statement evaluating $Actions. In this case, an empty string should do.

If you need it to work in a similar way for other commands as well, rather than having a case specifically for q, you can use the default case to set the $Actions variable.

CodePudding user response:

Simplify.

Instead of saving actions in a variable, and then taking another step of evaluating that variable... do the actions.

Instead of having a loop that checks the exit condition in a separate location (i.e. using until), use an endless loop and an explicit break. This helps keeping the logic in one place.

function foo { Write-Host "foo"; Start-Sleep -Seconds 1 }
function bar { Write-Host "bar"; Start-Sleep -Seconds 1 }

:menuLoop while ($true) {
    Clear-Host
    Write-Host "`n============= Menu =============`n"
    Write-Host "Press 'A' to run all commands"
    Write-Host "Press '1' to run foo"
    Write-Host "Press '2' to run bar"
    Write-Host "Press 'Q' to quit"

    switch (Read-Host "`nPlease make a selection") {
        'A' { foo; bar }
        '1' { foo }
        '2' { bar }
        'Q' { break menuLoop }
    }
}

Your approach does not work correctly because in your code, pressing Q does not immediately exit the loop, and $Actions is still filled from the last iteration.

That's another lesson: Variable values don't reset on their own in loops. Always set your variables to $null at the start of the loop to get a clean state.


That being said, PowerShell has a pretty nifty menu system built-in that you can use.

using namespace System.Management.Automation.Host

function foo { Write-Host "foo"; Start-Sleep -Seconds 1 }
function bar { Write-Host "bar"; Start-Sleep -Seconds 1 }

# set up available choices, and a help text for each of them
$choices = @(
    [ChoiceDescription]::new('run &all commands', 'Will run foo, and then bar')
    [ChoiceDescription]::new('&1 run foo', 'will run foo only')
    [ChoiceDescription]::new('&2 run bar', 'will run bar only')
    [ChoiceDescription]::new('&Quit', 'aborts the program')
)

# set up script blocks that correspond to each choice
$actions = @(
    { foo; bar }
    { foo }
    { bar }
    { break menuLoop }
)

:menuLoop while ($true) {
    $result = $host.UI.PromptForChoice(
        "Menu",                      # menu title
        "Please make a selection",   # menu prompt
        $choices,                    # list of choices
        0                            # default choice
    )

    & $actions[$result]              # execute chosen script block
}

Run this in PowerShell ISE and regular PowerShell to see how it behaves in each environment.

  • Related