Search code examples
powershellparameter-passingparameter-expansion

Powershell equivalent of Bash "Alternate Value"


I'm trying to call a command in a Powershell script that has optional switches, and it must dynamically determine whether to include the switches.

In Bash, you can do something like this (from this question):

curl -o - ${PARAMS:+"--data" "$PARAMS"}

So it will only include the --data switch and argument if PARAMS is defined. Is there an equivalent to this in Powershell?


Solution

  • As Mathias states, PowerShell has no direct equivalent to Bash's parameter expansion. Possibly bringing a similar feature to PowerShell in the future is discussed in Github issue #9566.

    If you're calling an external program such as curl[1] (rather than a PowerShell cmdlet/script/function), you can use the following approach:

    curl -o - $(if ($PARAMS) { '--data', $PARAMS })
    

    This takes advantage of the following behaviors:

    • The ability to pass the output from arbitrary statements as command arguments, using $(...), the subexpression operator; note that for simple expressions and commands just (...), the grouping operator is sufficient.

      • If such a $(...) or (...) argument produces no output (is effectively $null), no argument is passed when calling external programs; in the case at hand, this applies if the if ($PARAMS) conditional evaluates to $false, i.e. if $PARAMS is "falsy", based on PowerShell's to-Boolean coercion rules.

      • Note: If $PARAMS can contain a non-string value such as 0 that would also be considered $false, use a more explicit conditional: if ($null -ne $PARAMS), which is only $true if $PARAMS is undefined (or explicitly contains $null).

    • Arrays passed as arguments to external programs result in the array elements being passed as individual arguments.


    Note that different rules apply when calling PowerShell-native commands (cmdlets, scripts, functions):

    • Arrays are considered single arguments that are passed as a whole.

    • You cannot pass parameter names (e.g., -Body) via variables or expressions.

    The solution to both problems is to use splatting:

    This involves first storing the dynamic arguments in a variable, which is then passed via @ rather than $ (e.g., define a variable $dynArgs and pass it as @dynArgs), and it comes in two flavors:

    • To pass positional (unnamed) arguments dynamically, define the splatting variable as an array (e.g. $dynArgs = 'foo', 'bar')

    • To pass named arguments (arguments preceded by their target parameter name) dynamically, define the splatting variable as a hashtable.

    Therefore, if you were to invoke Invoke-RestMethod instead of curl, for instance, and you wanted to conditionally pass a -Body argument, you would do something like the following:

    $dynArgs = @{} # Initialize a hashtable
    
    # Conditionally create a 'Body' entry. The key name
    # must match the target parameter (without "-"):
    if ($PARAMS) { $dynArgs['Body'] = $PARAMS }
    
    # Pass $dynArgs via *splatting* - note the "@" sigil.
    # If the hashtable is empty, nothing is passed.
    # Splatting can be *combined* with directly passed arguments
    # (symbolized by "..." here).
    Invoke-RestMethod @dynArgs ...
    

    [1] Note that in Windows PowerShell curl is (unfortunately) a built-in alias for the Invoke-WebRequest cmdlet, which shadows the external curl.exe utility that now comes with Windows. However, by using curl.exe for invocation (i.e. by explicitly including the filename extension), the alias can be bypassed.