Can a PowerShell function handle multiple input types?

5.1k views Asked by At

I'm working on a function that compares two objects so it detects them if they are identical. However I would like it to also work with other types like strings or integers.

C++ lets you declare different functions with the same name to handle function calls with different input types in a different way. I'm aware of the existence of Parameter Sets, however as far as I know the user is the one who must specify which Parameter set he is using.

I'm trying to do something like this

function Compare-Objects{
    Param(
        [Parameter(Mandatory=$true,
        Position=0,
        ParameterSetName = "Hashtables")]
        [ValidateNotNullOrEmpty()] 
        [Hashtable]$Item1Hash,

        [Parameter(Mandatory=$true,
        Position=0,
        ParameterSetName = "Integers")]
        [ValidateNotNullOrEmpty()] 
        [int]$Item1int,
        
        [Parameter(Mandatory=$true,
        Position=1,
        ParameterSetName = "Hashtables")]
        [ValidateNotNullOrEmpty()]
        [Hashtable]$Item2Hash,
        
        [Parameter(Mandatory=$true,
        Position=1,
        ParameterSetName = "Integers")]
        [ValidateNotNullOrEmpty()]
        [Hashtable]$Item2Int
    )
    if($PSCmdlet.ParameterSetNamePositionv -match "Integers"){ Return ($Item1Int -eq $Item2Int)}
    else{
    #do some other stuff with $Item1Hash and $Item2Hash
    }
}

Extra points if I can also name the variables the same (So $Item1Hash and $Item1Int become both $Item1 with the appropriate type assigned)

1

There are 1 answers

0
mklement0 On BEST ANSWER

As Jeff Zeitlin states in his comment:

Users do NOT have to specify a parameter set explicitly - PowerShell infers the applicable parameter set from the specific combination of arguments (or absence thereof) passed on invocation.

The inference is based on argument data types, whether arguments are passed positionally (not preceded by the parameter name) and which parameters are marked as mandatory, and the name of the parameter set that takes effect is reflected in $PSCmdlet.ParameterSetName inside the (advanced) script / function invoked.

This ability to infer the applicable parameter set is similar to automatic method overload resolution in C-like languages.


While any given parameter can participate in multiple parameter sets - and indeed is by default part of all of them - you fundamentally cannot declare parameters with the same name but different data types.

If you want such "polymorphic" parameters, you'll have to implement your own logic, which doesn't rely on parameter sets:

function Compare-Objects {

  [CmdletBinding(PositionalBinding=$false)]
  Param(
      
      [Parameter(Mandatory, Position=0)]
      [ValidateNotNullOrEmpty()] 
      # Define as [object] to initially accept any value; specific types
      # are later enforced inside the function body.
      [object] $Item1 
      ,
      [Parameter(Mandatory, Position=1)]
      [ValidateNotNullOrEmpty()] 
      [object] $Item2
  )

  # Ensure that a supported type was passed.
  if ($Item1.GetType() -notin [int], [hashtable]) { Throw "Unsupported argument type." }

  # Ensure that both arguments have the same type.
  if ($Item1.GetType() -ne $Item2.GetType()) { Throw "Inconsistent argument types." }
  
  if ($Item1 -is [int]) {
    "[int] arguments given."
  }
  else {
    "[hashtable] arguments given."
  }

}

Note: If checking the type of an argument parameter-individually is sufficient, you can decorate a parameter with a [ValidateScript()] attribute, as shown in the bottom section of this answer.


However, if using the same parameter name isn't a requirement and you're content with positional invocation using different data types, parameter sets can help, as the following simplified example demonstrates:

function Foo {
  [CmdletBinding()]
  param(
    [Parameter(ParameterSetName='int', Position=0)]
    [int] $ItemInt
    ,
    [Parameter(ParameterSetName='hash', Position=0)]
    [hashtable] $ItemHash
  )
  "Parameter set chosen: $($PSCmdlet.ParameterSetName)"
 } 

# Call the function first with an [int], then with a [hashtable], positionally.
10, @{ foo = 1 } | ForEach-Object { Foo $_ }

The above yields the following, showing that the argument data type automatically selected the appropriate parameter set:

Parameter set chosen: int
Parameter set chosen: hash