Powershell: Parameter parsing/passing: unquoted tokens that look like named arguments with colon as the separator are broken in two when passed indirectly via $Args / @Args

Created on 11 Mar 2018  路  3Comments  路  Source: PowerShell/PowerShell

Related: #6291, #6292, and #4624

Following up from https://github.com/PowerShell/PowerShell/issues/6292#issuecomment-371344550:

Note: The tokens in question, such as -foo:bar, _look_ like named PowerShell arguments (and, behind the scenes, are initially always parsed as such - see @BrucePay's comment below), but should be passed through as-is (except for possibly expanding the string) when calling external programs.

Tokens that happen to look like -foo.bar suffer a similar fate (see #6291).

This already works as expected with _direct_ argument passing (e.g.,
echoArgs -foo:bar passes -foo:bar as a single argument
), but not when passing $Args / splatting it (@Args).

Note: The problem is fundamental to the use of $Args / @Args and, while perhaps less common, also affects its use when calling PowerShell-native commands - see comment below.

Steps to reproduce

Run on macOS or Linux.

function baz {
  bash -c 'for a; do echo $a; done' - $Args   # note: same with @Args
}

baz -foo:bar
'---'
baz -foo.bar

Expected behavior

-foo:bar
---
-foo.bar

Actual behavior

-foo:
bar
---
-foo
.bar

The arguments are unexpectedly broken in two. See linked comment for background.

Environment data

PowerShell Core v6.0.1 on macOS 10.13.3
PowerShell Core v6.0.1 on Ubuntu 16.04.3 LTS
PowerShell Core v6.0.1 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Windows PowerShell v5.1.15063.674 on Microsoft Windows 10 Pro (64-bit; v10.0.15063)
Issue-Discussion WG-Language

Most helpful comment

I just spent the past week or so wrestling with this issue. After tracking it down to forced switch parameter values (such as -foo:$bar) not getting set after splatting with @Args, I thought this must be an obscure scoping issue. Imagine my surprise when I discovered that forcing any switch parameter value this way wouldn't have the expected behavior. I tested it even with the Powershell 7 Preview 1 release and the same issue still arises. I would hope that it would be fixed after more than a year of being a known problem...

For reference, this is the test I created to show the bug. Saw it on a Server 2016 machine with Powershell 7 Preview 1, as well as a Server 2012 R2 machine with Powershell 5.1.

function foo
{
    param(
        [switch]$testArg = $false
    )

    write-host "Test arg value: '$testArg'"
}

function bar
{
    foo @Args
}

$testSplat = @{
    testArg = $false
}

write-host "#### Foo tests ####"

foo
foo -testArg:$true
foo -testArg:$false
foo @testSplat

write-host "#### Bar tests ####"

bar
bar -testArg:$true
bar -testArg:$false
bar @testSplat

All 3 comments

Note: The tokens in question, such as -foo:bar look like named PowerShell arguments,

@mklement0 As i describe in the comments in #6292, these _are_ named PowerShell arguments. Always. Parameter binding is done long after the compile is complete. Now in the compiled AST, the token for -foo: has a flag indicating that there was no space after it in the source code. The NativeCommandParameterBinder looks at this AST element to see if it has the flag is set then concatenates the parameter and argument with no space between them. If it's not set, then a space is inserted. This only works if the arguments are literal (i.e. the NativeCommandParameter was access to the AST for the argument). In the splatting case, the arguments are values, not literal arguments so you get a space.

One way to fix this is to propagate the "NoSpace" token property into metadata on the corresponding string value. The NativeCommandParameterBinder could then check for this metadata when converting it's arg array into a string. It's probably worth thinking about this a bit more to see if there are other cases (especially *nix specific cases) that should be addressed.

6492 (since closed as a duplicate) shows that splatting @args is also broken when calling PowerShell functions (not just external programs):

Supporting that scenario is presumably even trickier, because the expectation here is that the named parameters are passed through as-is, with their original types.

Here's a simplified repro:

function b {
  Param
  (
      [Switch] $p1,
      [int] $p2,
      $rest
  )
  "`$p1: [$p1]"
  "`$p2: [$p2]"
  "`$rest: [$rest]"
}

& { b @args } -p1:$false 666
$p1: [True]  # `-p1:` was interpreted as just `-p1`
$p2: [0]      # `$false`, as a separate argument, was coerced to [int] 0
$rest: [666] # what was meant to be the 2nd argument was passed as the 3rd

The workaround - and preferable solution to begin with - is to define the same param() block in the relaying function and splat with @PSBoundParameters.

I just spent the past week or so wrestling with this issue. After tracking it down to forced switch parameter values (such as -foo:$bar) not getting set after splatting with @Args, I thought this must be an obscure scoping issue. Imagine my surprise when I discovered that forcing any switch parameter value this way wouldn't have the expected behavior. I tested it even with the Powershell 7 Preview 1 release and the same issue still arises. I would hope that it would be fixed after more than a year of being a known problem...

For reference, this is the test I created to show the bug. Saw it on a Server 2016 machine with Powershell 7 Preview 1, as well as a Server 2012 R2 machine with Powershell 5.1.

function foo
{
    param(
        [switch]$testArg = $false
    )

    write-host "Test arg value: '$testArg'"
}

function bar
{
    foo @Args
}

$testSplat = @{
    testArg = $false
}

write-host "#### Foo tests ####"

foo
foo -testArg:$true
foo -testArg:$false
foo @testSplat

write-host "#### Bar tests ####"

bar
bar -testArg:$true
bar -testArg:$false
bar @testSplat
Was this page helpful?
0 / 5 - 0 ratings