powershell function switch param with string array


To summarize and complement the helpful comments on the question by Lee_Dailey, Matthew and mclayton:

[switch] parameters in PowerShell (aka flags in other shells):

switch parameters are meant to imply $true vs. $false by their presence in an invocation: e.g., passing -sw by itself signals $true, whereas omitting -sw signals $false.

  • It is possible to pass a Boolean value explicitly, for the purpose of passing a programmatically determined value; e.g.: -sw:$var

    • Note the required : following the switch name, which tells PowerShell that the Boolean value belongs to the switch parameter; without it, PowerShell thinks the value is a positional argument meant for a different parameter (see below).

    • Caveat: Commands may interpret -sw:$false differently from omitting -sw; a prominent example is is the use of common parameter -Confirm:$false to override the effective $ConfirmPreference value.

      • If you need to make this distinction in your own code, use $PSBoundParameters.ContainsKey('sw') -and -not $sw to detect the -sw:$false case.
  • Do not assign a default value to a switch parameter variable: while technically possible, the convention is that switches default to $false (which is a [switch] instance’s default value anyway); that is, a [switch] parameter should always have opt-in logic.

A [switch] parameter variable effectively behaves like a Boolean value in most contexts:

  • That is, you can say if ($sw) { ... }, for instance.

  • If you need to access the wrapped Boolean value explicitly, access the .IsPresent property (note that the property name is somewhat confusing, because in a -sw:$false invocation the switch is still present, but its value, as reflected in .IsPresent, is $false).

    • An example of where .IsPresent is needed is the use of a Boolean as an implicit array index, notably to emulate a ternary conditional[1]: ('falseValue', 'trueValue')[$sw.IsPresent]; without the .IsPresent, the effective Boolean value wouldn’t be recognized as such and wouldn’t automatically be mapped to index 0 (from $false) or 1 (from $true).

Ultimately, your problem was that you thought $true was an argument for -sw, whereas it became a positional argument implicitly bound to the -test2 parameter.

[switch] parameters never need a value, so the next argument becomes a separate, positional argument – unless you explicitly indicate that the argument belongs to the switch by following the switch name with :, as shown above.[2]

Positional vs. named argument passing in PowerShell:

Terminology note: For conceptual clarity the term argument is used to refer to a value passed to a declared parameter. This avoids the ambiguity of using parameter situationally to either refer to the language construct that receives a value vs. a given value.

  • Named argument passing (binding) refers to explicitly placing the target parameter name before the argument (typically separated by a space, but alternatively also and / or by :); e.g., -AppName foo.

    • The order in which named arguments are passed never matters.
  • Positional (unnamed) argument passing refers to passing an argument without preceding it by the name of its target parameter; e.g., foo.

    • The passing is positional in the sense that the relative position (order) among other unnamed arguments determines what target parameter is implied.
  • [switch] parameters are the exception in that they:

    • are typically passed by name only (-sw), implying value $true, and if a value is passed, require : to separate the name from the value.
    • never support positional binding.
  • You may combine named passing with positional passing, in which case the named arguments are bound first, after which the positional ones are then considered (in order) for binding to the not-yet-bound parameters.

  • PowerShell functions are simple functions by default. In order to exercise control over positional binding, use of the [CmdletBinding()] and / or [Parameter()] attributes is necessary (see below), which invariably turn a simple function into an advanced function.

    • Making a simple function an advanced one has larger behavioral implications (mostly beneficial ones), which are detailed in this answer.
  • By default, PowerShell functions accept positional arguments for any parameter (other than those of type [switch]), in the order in which the parameters were declared.

    • Additionally, simple functions accept arbitrary additional arguments for which no parameters were declared, which are collected in the automatic $args array variable.
  • To prevent your function from accepting any positional arguments by default, place a [CmdletBinding(PositionalBinding=$false, ...)] attribute above the param(...) block.

    • Since this makes your function an advanced one, this also disables passing arbitrary additional arguments ($args no longer applies and isn’t populated).

    • As an aside: when you implement a cmdlet (a command implemented as a binary, typically via C#), this behavior is implied.

  • To selectively support positional arguments, decorate individual parameter declarations with a [Parameter(Position=<n>, ...)] attribute (e.g, [Parameter(Position=0)] [string] $Path)

    • Note: Whether you start your numbering with 0 or 1 doesn’t matter, as long as the numbers used reflect the desired ordering among all positional parameters; 0 is advisable as a self-documenting convention, because it is unambiguous.

    • Attribute [Parameter(Position=<n>)] is an explicit opt-in that selectively overrides [CmdletBinding(PositionalBinding=$false)]: that is, the latter disables positional binding unless explicitly indicated by individual parameter declarations; in fact, the latter is implied by the former, in that once you use one [Parameter(Position=<n>)] attribute, you must use it on all other parameters you want to bind positionally as well.


[1] Note that PowerShell [Core] 7.0+ supports ternary conditionals natively: $sw ? 'trueValue' : 'falseValue'

[2] In effect, [switch] parameters are the only type for which PowerShell supports an optional argument. See this answer for more information.

Leave a Comment