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.
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
-foo:bar
---
-foo.bar
-foo:
bar
---
-foo
.bar
The arguments are unexpectedly broken in two. See linked comment for background.
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)
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.
@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
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.