Today, the splatting of a Hashtable in a PowerShell command invocation works just like explicitly specifying those key-value pairs as individual named parameters in the command line, so the following example doesn't work:
$common = @{
AddressPrefix = "10.0.0.0/16"
ResourceGroupName = "MyResourceGroup"
Location = "westus"
}
New-AzVirtualNetwork @common -Name MyNet -AddressPrefix "10.0.0.0/24"
> New-AzVirtualNetwork: Cannot bind parameter because parameter 'AddressPrefix' is specified more than once. To provide
> multiple values to parameters that can accept multiple values, use the array syntax. For example, "-parameter
> value1,value2,value3".
$common may work well for all other 99 New-AzVirtualNetwork calls in my scripts, but there is one instance where a different address prefix is required. In such a case, I would have to temporarily change the Hashtable and restore it after the invocation, like this:
try {
$commonAddrPrefix = $common.AddressPrefix
$common.AddressPrefix = "10.0.0.0/24"
New-AzVirtualNetwork @common -Name MyNet
}
finally {
$common.AddressPrefix = $commonAddrPrefix
}
The proposal is to allow an explicitly specified named parameter to supersede _the same one_ that is expanded from splatting a Hashtable.
To be clear, I want to point out that the proposal targets _"Hashtable splatting"_ and _"explicitly specified named parameters"_ only.
(a). Array splatting won't change its current behavior.
(b). Hashtable splatting won't change its current behavior when working with explicitly specified positional parameters.
(c). Both Array splatting and Hashtable splatting keep their current behaviors when working with native command.
The CommandParameterInternal created from splatting a Hashtable needs to be marked as _came-from-splatting_.
To determine whether 2 named parameters are _the same_, parameters need to be resolved to cover the use of parameter alias or prefix (e.g. -s for -Seconds in Start-Sleep), so the work should be done in the parameter binder.
Script-block-to-PowerShell conversion also does splatting. It calls powershell.AddParameter(string paramName, object paramValue) for each of the key-value pair of the Hashtable. The _"came-from-splatting"_ message needs to be passed on to the PowerShell instance, so we will need to add a boolean property CamFromSplatting to CommandParameter to track that information, which will be used when invoking the PowerShell instance.
__Challenge:__ the script-block-to-PowerShell conversion is also used in remoting scenario when the script block to invoke on the remote side contains command invocations only, for example Invoke-Command { param($hash) dir @hash } -ArgumentList @{ path = "c:\" }. In that case, the generated PowerShell instance will be used to create a remote pipeline and run on the remote side, so the new member added to CommandParameter will likely cause serialization/deserialization problems. More investigation is needed.
Update to (5): powershell committee agreed (https://github.com/PowerShell/PowerShell/issues/13108#issuecomment-655794760) to not use GetPowerShell in remote command execution, so (5) won't be a blocker.
/cc @SteveL-MSFT @JamesWTruher @joeyaiello
Additionally, we could provide _new_ behavior where the position of the splatted provides a clue as to which values should be applied - here's an example of what I'm talking about:
PS> function get-test {
param (
[Parameter()]$first,
[Parameter()]$second
)
"$first : $second"
}
PS> $h = @{ first = 1; second = 2 }
PS> get-test -first "what" @h
1 : 2
PS> get-test @h -first what
what : 2
in the first example get-test -first "what" @h the values in the splatted hashtable are used and override the explicit parameter use. in the second example get-test @h -first "what", since the explicit parameter is used after the splatted hashtable, the explicitly provided value is used. Configuration systems sometimes provide this behavior.
Having parameter argument overrides based on position in the argument list feels very confusing to me. It is something that is easy to forget, get wrong, and difficult to read and maintain. Having explicit parameter arguments override splatting arguments seems more intuitive to me.
Having parameter argument overrides based on position in the argument list feels very confusing to me.
I agree with @PaulHigin.
I think it will cause burden to the script maintenance for sure. For example, when I need to add a few more named parameters to the following existing command call in a script, I have to figure out why the arguments were specified in that order, was it intentional or by accident? If it was by accident and it happened to work, then it may break when $hashConfig gets changed (e.g. added Param1 = arg0).
Invoke-XXXX -Param1 args1 @hashConfig -Param2 arg2 -Param3 arg3
:tada:This issue was addressed in #13162, which has now been successfully released as v7.1.0-preview.6.:tada:
Handy links:
Most helpful comment
Having parameter argument overrides based on position in the argument list feels very confusing to me. It is something that is easy to forget, get wrong, and difficult to read and maintain. Having explicit parameter arguments override splatting arguments seems more intuitive to me.