Home > Net >  Problems with interconnected parameters in PowerShell Param block
Problems with interconnected parameters in PowerShell Param block

Time:02-01

I'm having troubles wrapping my head around the code logic needed to get my Param block functioning as I want in PowerShell 5.1. A very simplified (but usable for my request) script with the Param block is here and this works exactly as I want:

[CmdletBinding(DefaultParameterSetName = 'All')]

Param (
    [Parameter(Position=0,Mandatory=$True)]
    [String]$Name,
    
    [Parameter(ParameterSetName = 'All')]
    [Switch]$All,
    
    [Parameter(ParameterSetName = 'Individual')]
    [Switch]$P1,
    
    [Parameter(ParameterSetName = 'Individual')]
    [Switch]$P2
    
    )

If ($All -Or ($P1.ToBool()   $P2.ToBool() -eq 0)) {
    $All = $True
    $P1 = $True
    $P2 = $True
    }

"`$Name is $Name"
"`$All is $All"
"`$P1 is $P1"
"`$P2 is $P2"

The auto-completion of parameters when running this script above works as intended. If I use the "-All" switch, then -P# are not available. If I use -P# switche, -All is not available. If I omit the -Name, then it prompts me to put in a name. If I use the -All switch, I want all of the individual -P# options to later be set to $True. If I use no switches at all, it prompts me for a Name then sets all options to $True.

The first problem is that when using DefaultParameterSetName = 'All' (which I had to do in order to make the script work without any switches on the command line), then the $All variable is NOT actually being set to $True when it is not present on the command line. I had to make the "If" block in order to overcome that behavior. This makes the next problem come up because the actual script I'm trying to use this in will have fifteen or more -P# switches. That will make the "If" test more complex and ugly.

Is there a better way I can do this? Maybe something in the layout of my Parameter Sets? I could even eliminate the "-All" switch entirely if there's an easier way to evaluate that none of the -P# switches are used. Is there an easier way to add up the boolean value of all Parameters named P#? I've stumbled across the $MyInvocation variable and $MyInvocation.MyCommand.Parameters seems promising but I'm not sure exactly how to process that either.

Update after answer found: Here is my new, simplified working code sample which I arrived at thanks to all the suggestions here. The "-All" Switch was unnecessary. I decided to go with the Get-Variable method here for now due to its simplicity and scalability, but it does require a common prefix on the Switch variables. The If() block will remain the same no matter how many variables are used.

Param (
    [Parameter(Position=0,Mandatory=$True)]
    [String]$Name,
    [Alias('Blue')]
    [Switch]$OptionBlue,
    [Alias('Red')]
    [Switch]$OptionRed,
    [Alias('Yellow')]
    [Switch]$OptionYellow
    )

$AllOptions = Get-Variable -Name 'Option*'
If (-Not $AllOptions.Value.Contains($True)) {
    "None of the Option Switches were used, setting all Option Variables to $True"
    ForEach ($Option In $AllOptions) {$Option.Value = $True}
    }

"`$Name:          $Name"
"`$OptionBlue:    $OptionBlue"
"`$OptionRed:     $OptionRed"
"`$OptionYellow:  $OptionYellow"

and here's how it works:

PS C:\Scripts\PowerShell> .\Test-Params.ps1

cmdlet Test-Params.ps1 at command pipeline position 1
Supply values for the following parameters:
Name: Testing
None of the Option Switches were used, setting all Option Variables to True
$Name:          Testing
$OptionBlue:    True
$OptionRed:     True
$OptionYellow:  True
PS C:\Scripts\PowerShell> .\Test-Params.ps1 -Name Testing -OptionRed
$Name:          Testing
$OptionBlue:    False
$OptionRed:     True
$OptionYellow:  False
PS C:\Scripts\PowerShell> .\Test-Params.ps1 -Name Testing -Blue -Yellow
$Name:          Testing
$OptionBlue:    True
$OptionRed:     False
$OptionYellow:  True
PS C:\Scripts\PowerShell> .\Test-Params.ps1 -Yellow -OptionRed

cmdlet Test-Params.ps1 at command pipeline position 1
Supply values for the following parameters:
Name: Testing
$Name:          Testing
$OptionBlue:    False
$OptionRed:     True
$OptionYellow:  True

CodePudding user response:

You can use the $PSBoundParameters "automatic variable" to access the parameters specified in the call to the script / function and alter the function's behaviour accordingly.

For example:

function Invoke-MyFunction
{
    param
    (
        [string] $Name,
        [switch] $P1,
        [switch] $P2
    )

    # how many $Pn parameters were specified in the call to the function?
    $count = @( $PSBoundParameters.GetEnumerator()
        | where-object { $_.Key.StartsWith("P") }
    ).Length;

    # if *none* specified then enable *all*
    $all = $count -eq 0;
    if( $all )
    {
        $P1 = $true;
        $P2 = $true;
    }

    "`$Name is $Name"
    "`$all is $all"
    "`$P1 is $P1"
    "`$P2 is $P2"

}

And some tests:

PS> Invoke-MyFunction
$Name is
$all is True
$P1 is True
$P2 is True

PS> Invoke-MyFunction -Name "aaa"
$Name is aaa
$all is True
$P1 is True
$P2 is True

PS> Invoke-MyFunction -Name "aaa" -P1
$Name is aaa
$all is False
$P1 is True
$P2 is False

PS> Invoke-MyFunction -Name "aaa" -P2
$Name is aaa
$all is False
$P1 is False
$P2 is True

PS>  Invoke-MyFunction -Name "aaa" -P1 -P2
$Name is aaa
$all is False
$P1 is True
$P2 is True

but watch out because the way I've evaluated $count means that specifying -P1:$false or -P2:$false makes $all = $false:

PS> Invoke-MyFunction -Name "aaa" -P1:$false
$Name is aaa
$all is False
$P1 is False
$P2 is False

so you might need to refine the expression to suit whatever you want to happen in this edge case...


Update

If your parameter names don't follow a simple pattern you can do something like this instead:

$names = @( "SomeParam", "AnotherParam", "Param3" );
$count = @( $PSBoundParameters.GetEnumerator()
    | where-object { $_.Key -in $names }
).Length;

CodePudding user response:

Assuming those are all switches and they belong to the parameter set of 'Individual', you can set the unset parameters to $true if $All is specified like so:

if ($All.IsPresent)
{
    (Get-Command -Name $MyInvocation.MyCommand.Name).Parameters.Values | 
        Where-Object -FilterScript { $_.ParameterSets.Keys -eq 'Individual' } | 
        Foreach-Object -Process {
            Set-Variable -Name $_.Name -Value $true -PassThru # remove -PassThru to silence the output
        }

}

Referencing the current executing command with $MyInvocation.MyCommand.Name, you can pass it to Get-Command for more detailed info. on its parameters. This will allow you to filter by ParameterSets grabbing just the ones falling under Individual. Finally, it will pass it to Set-Variable setting all the switches to $true in a dynamic sense.


Note: I typed this up on my phone so there may be some typos that should be easy to correct.

  • Related