3

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?
TylerH
  • 20,799
  • 66
  • 75
  • 101
alx9r
  • 3,675
  • 4
  • 26
  • 55
  • As far as I can tell, it's being resolved as BOTH `String` and `PSObject` which creates a signature ambiguity. Hence the term `AmbiguousParameterSet` in the error record. – Eris Oct 08 '16 at 05:46

1 Answers1

3

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.

briantist
  • 45,546
  • 6
  • 82
  • 127
  • It does seem that way. If that is the case, that really violates the principle of least surprise. – alx9r Oct 06 '16 at 19:02
  • @alx9r I didn't have time before I submitted the answer, but I've added to add some bonus info. – briantist Oct 06 '16 at 19:18
  • 1
    For anyone coming across this in the future, it's worth noting that [`PSObject` and `PSCustomObject` are very closely related but can produce different results](https://stackoverflow.com/a/35894817/1404637). – alx9r Oct 06 '16 at 19:23