alx9r
alx9r

Reputation: 4273

Why can't parameter sets be resolved based on [string] vs [hashtable] vs [pscustomobject]?

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:

  1. Why can't PowerShell select a parameter set based on the difference in type between a [string] and a [pscustomobject]?
  2. Is the reason is that PowerShell considers a [string] to be a [pscustomobject]? If so, why would that be?
  3. Is there a workaround that allows me to use different types to select different parameter sets?

Upvotes: 4

Views: 350

Answers (1)

briantist
briantist

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

Related Questions