Powershell "special" switch parameter
Asked Answered
V

3

5

I have the powershell function below

Function Test
{
    Param
    (               
        [Parameter()]
        [string]$Text = "default text"
    )

    Write-Host "Text : $($Text)"
}

And I would like to be able to call this function like below :

Test -Text : should display the default text on the host

Test -Text "another text" : should display the provided text on the host

My issue is that the first syntax is not allowed in powershell ..

Any ideas of how I can achieve this goal ? I would like a kind of 'switch' parameter that can take values other than boolean.

Thanks

Vacillating answered 13/11, 2019 at 14:6 Comment(14)
Simply add another (optional) parameter $text2 and add Position = 0 to the first parameter.Iila
@Iila That isn't answering the question. They want to be able to use $Text as a string and a switch.Topical
What you're asking is not how PowerShell parameters work. Maybe you could do what you want via dynamic parameters, but I fail to see the problem with just calling the function without parameters for having it display the default text.Hardworking
@TheIncorrigible1 Are you sure? I'm confused now.. With a second parameter you can test if that has any value and if so ignore the first default parameter and display the second one. I thought that was the question.Iila
@Iila From what I understand, they want a default value when the switch -Text is passed, and a different behavior when they pass a string to -Text but powershell's rules around parametersets won't allow this use-case.Topical
@Iila I can use two parameters as you said and it will work but I'm looking for a solution where I can use only one parameter. Using only one parameter makes the final syntax more elegant and and concise IMO.Vacillating
@Ans: The desired behavior - which is a useful idiom, even though currently unsupported by PowerShell - is to have an optional-argument parameter, i.e., a parameter that you may pass by itself - to fundamentally opt into a behavior - but which you may, optionally also qualify - with the optional argument. An example from the Unix world is GNU sed's -i (create a backup) option: if you pass just -i, you opt into creating a backup with the default extension of .bak; alternatively, you can pass the specific backup-file extension desired: -i.sav.Fermentation
(Cont'd) Syntax-wise, PowerShell could support this idiom, by using : as the separator between parameter name and (optional) value, to unequivocally identify the next token as belonging to the parameter. In fact, that's how it already works for [switch] parameters, but currently only for them.Fermentation
@Fermentation Thanks for explaining. I do use the colon syntax for switches, but never thought of using similar for other types of parameters.Iila
@Fermentation Nothing either of us said contradicts what the other said, no?Hardworking
@AnsgarWiechers. The primary purpose of my comment was to frame the issue properly for future readers, while secondarily also notifying previous commenters (I first notified Theo, then realized you might find it interesting too). As for contradictions: I fail to see the problem with just calling the function without parameters for having it display the default text contradicts the idea of an optional-argument parameter.Fermentation
@Fermentation Not really, as the intention of my comment was simply dealing with the status quo. I have no problem should Microsoft decide to change parameter handling in the way you suggested.Hardworking
@AnsgarWiechers: Yes; I would have no problem either and would even welcome the change, though the question is whether it is worth the effort. As for the status quo: Your recommendation of just calling the function without parameters does not address the OP's requirements - it addresses a different scenario.Fermentation
@Fermentation I was questioning that requirement, though. ;)Hardworking
T
6

The problem you're running into is with parameter binding. PowerShell is seeing [string] $Text and expecting a value. You can work around this like so:

function Test {
    param(
        [switch]
        $Text,

        [Parameter(
            DontShow = $true,
            ValueFromRemainingArguments = $true
        )]
        [string]
        $value
    )

    if ($Text.IsPresent -and [string]::IsNullOrWhiteSpace($value)) {
        Write-Host 'Text : <default text here>'
    }
    elseif ($Text.IsPresent) {
        Write-Host "Text : $value"
    }
}

Note: this is a hacky solution and you should just have a default when parameters aren't passed.

Topical answered 13/11, 2019 at 14:54 Comment(2)
Great solution ! Thank you :) ! One last question though. What if I have a second parameter $Text1 that should work exactly as the $Text parameter ? I would expect to copy past the code of $Text and $Value and create $Text1 and $Value1 but there is a gotcha because if I call Test -Text -Text1 "Hehe" , The text "Hehe" will be assigned to $value instead of $value1 ..Vacillating
Nicely done - that's indeed the best you can do in PowerShell. @Riana, this workaround only works for a single optional-argument parameter. TheIncorrigible1, using a regular parameter with a default value cannot implement the desired behavior, because the fundamental opt-in aspect (was the parameter passed at all?) is lost.Fermentation
F
4

tl;dr

  • PowerShell does not support parameters with optional values.

  • A workaround is possible, but only for a single parameter.


Maximilian Burszley's helpful answer provides a workaround for a single parameter, via a catch-all parameter that collects all positionally passed arguments via the ValueFromRemainingArguments parameter property.

Fundamentally, though, what you're asking for is unsupported in PowerShell:

PowerShell has no support for parameters with optional values as of 7.2 - except for [switch] parameters, which are limited to [bool] values.

That is:

  • Any parameter you declare with a type other than [switch] invariably requires a value (argument).

  • The only other option is to indiscriminately collect any unbound positional arguments in a ValueFromRemainingArguments-tagged parameter, but you won't be able to associate these with any particular other bound parameter.

In other words:

  • If you happen to need just one optional-argument parameter, the ValueFromRemainingArguments can work for you (except that you should manually handle the case of mistakenly receiving multiple values), as shown in Maximilian Burszley's answer.

  • If you have two or more such parameters, the approach becomes impractical: you'd have to know in which order the parameters were passed (which PowerShell doesn't tell you) in order to associate the remaining positional arguments with the right parameters.


With [switch] parameters (using an imagined -Quiet switch as an example):

  • The default value - if you just pass -Quiet -is $true.
  • $false is typically indicated by simply not specifying the switch at all (that is, omitting -Quiet)

However, you may specify a value explicitly by following the switch name with :, followed by the Boolean value:

  • -Quiet:$true is the same as just -Quiet

  • -Quiet:$false is typically the same as omitting -Quiet; in rare cases, though, commands distinguish between an omitted switch and one with an explicit $false value; notably, the common -Confirm parameter allows use of -Confirm:$false - as opposed to omission of -Confirm - to override the value of the $ConfirmPreference preference variable.

While : as the separator between the parameter name and its argument (as opposed to the usual space char.) is supported with all parameters, with [switch] parameters it is a must so as to unequivocally signal that what follows is an argument for the switch parameter (which by default needs no argument) rather than an independent, positional argument.


The above tells us that PowerShell already has the syntax for general support of optional-argument parameters, so at some point in the future it could support them with any data type, as suggested in GitHub issue #12104.

Fermentation answered 14/11, 2019 at 2:59 Comment(2)
Actually, powershell does give you a way to view the order of the arguments passed to the function with $MyInvocation.Line. If you really want to do this with a function supporting multiple parameters, then you can simply parse that string to match parameters to their values.Ezmeralda
@JKony, yes, but at that point you may better off not using a param() block at all and inspecting the automatic $args variable instead - either way, it is a nontrivial effort. As an aside: Avoid $MyInvocation.Line, because (a) it can include other statements placed on the same line and (b) conversely, it only reports the first line if the call happens to have been spread across multiple lines (which is arguably a bug). The robust alternative is (Get-PSCallStack)[1].Position.Text. Either way, an additional challenge is that the raw command is reported, with unexpanded arguments.Fermentation
C
0

I like @Maximilian Burszley's answer (and his name!) for String, I tweaked it for Ints:

function Optional-SwitchValue {
  [CmdletBinding()]
  param (
    [Switch]
    $Bump,

    [Int]
    $BumpAmount
  )

  Begin {
    # nifty pattern lifted from https://mcmap.net/q/832670/-powershell-quot-special-quot-switch-parameter
    # default Bump to 1
    if ($Bump.IsPresent -and -not $BumpAmount) {
      $BumpAmount = 1
    }
  }
  
  Process {
    if($Bump) {
        #use $BumpAmount in some way
    }
  }
}
Charo answered 26/9, 2022 at 16:18 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.