Reputation: 4273
Consider this function:
function Test-Discrimination
{
[CmdletBinding()]
param
(
[parameter(ValueFromPipeline = $true,
Mandatory = $true,
ParameterSetName = 'string')]
[string]
$String,
[parameter(ValueFromPipeline = $true,
Mandatory = $true,
ParameterSetName = 'hashtable')]
[hashtable]
$Hashtable,
[parameter(ValueFromPipeline = $true,
Mandatory = $true,
ParameterSetName = 'pscustomobject')]
[pscustomobject]
$PsCustomObject
)
process
{
$PSCmdlet.ParameterSetName
}
}
Piping [pscustomobject]
behaves as I expect:
PS C:\> New-Object pscustomobject | Test-Discrimination
pscustomobject
However, piping [string]
throws an exception:
PS C:\> 'string' | Test-Discrimination
Test-Discrimination : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:12
+ 'string' | Test-Discrimination
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (string:String) [Test-Discrimination], Paramete
rBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Test-Discrimination
So does [hashtable]
:
PS C:\> @{} | Test-Discrimination
Test-Discrimination : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:7
+ @{} | Test-Discrimination
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (System.Collections.Hashtable:Hashtable) [Test-
Discrimination], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Test-Discrimination
Adding DefaultParameterSetName='hastable'
causes [hashtable]
but not [string]
to resolve correctly.
I'm not experienced at interpreting the output from Trace-Command. I did notice the output for [string]
includes this line:
BIND arg [string] to param [PsCustomObject] SUCCESSFUL
Which seems like PowerShell is considering [string]
to be a [PsCustomObject]
. But 'string' -is [pscustomobject]
evaluates to $false
.
This all leaves me with the following questions:
[string]
and a [pscustomobject]
?[string]
to be a [pscustomobject]
? If so, why would that be?Upvotes: 4
Views: 350
Reputation: 47872
I believe that the reason for this is that anything can be casted to [PSObject]
([PSCustomObject]
). PowerShell tries to coalesce values to the target type. This is why when you have a parameter that's [int]
, you can pass "5"
and it will work, or why when you have a parameter that's [ipaddress]
, you can give it a string "1.2.3.4"
.
So during parameter binding, what's happening when you pass a [string]
or [hashtable]
is that it's successfully binding it to the [pscustomboject]
parameter, as well as (at least) one of the others, and therefore it cannot resolve the set.
I don't believe there's any way to turn off this behavior or make it "stricter".
And by the way, the reason anything can be casted to [PSObject]
is because in PowerShell, every object is a [PSObject]
already! This is also why you can add members to any instance of any object. PowerShell makes this all really transparent, which is why as you said, it violated the principle of least surprise in this (and some other cases).
If you interact with PowerShell from within C#, the fact that everything is wrapped in [PSObject]
becomes much more obvious (and in many cases annoying), which is how I first realized that this is the case.
Upvotes: 3