pythonpowershellargumentskeyword-argumentparameter-splatting

How to splat Powershell arguments in the same manner that Python *args, **kwargs work


Both python and powershell support a form of splatting array and named arguments as function inputs, a very useful feature.

However powershell seem to be internally inconsistent somewhat. I am trying to reproduce powershell code that behaves similarly to the following python code:

def echoName(Name, *args, **kwargs):
    print("Name is:", Name)


def wrapper(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)
    echoName(*args, **kwargs)

d = {
    "Name": "John",
    "Age": 25
}
wrapper(**d)
# args: ()
# kwargs: {'Name': 'John', 'Age': 25}
# Name is: John

As far as I am aware ValueFromRemainingArguments is the only way to accept left over parameters in a powershell advanced function

function echoName { 
    param(
        [CmdletBinding()]
        [string]$Name,
        [parameter(Mandatory = $False, ValueFromRemainingArguments = $True)]
        [Object[]] $Arguments
    )
    Write-Host "Name is: $Name"
}
function wrapper {
    [CmdletBinding()]
    param(
        [parameter(Mandatory = $False, ValueFromRemainingArguments = $True)]
        [Object[]] $Arguments
    )
    Write-Host "Arguments is: $Arguments"
    echoName @Arguments

}
$d = @{
    Name = 'John'
    Age  = 25
}
wrapper @d
# Arguments is: -Name: John -Age: 25
# Name is: -Name:

I have 3 issues with powershell's output

  1. Arguments is now an array
  2. the named arguments were prefixed with - and suffixed with :
  3. this is a weird behavior at best:
$a = @(1,2,3)
wrapper @a @d
# Arguments is: 1 2 3 -Name: John -Age: 25
# Name is: 1

How can I chain and only partially consume variables as possible in python?

What is the difference between a cmdlet and a function?
Wrapper function for cmdlet - pass remaining parameters
Is there a way to create an alias to a cmdlet in a way that it only runs if arguments are passed to the alias?


Solution

  • Your desire is for a function to support accepting open-ended pass-through arguments and to pass them on to a different PowerShell command as named arguments, i.e. as parameter name-value pairs.

    Fundamentally, PowerShell's splatting supports passing named arguments only within the following constraints:


    The automatic $args variable - despite being an array rather than a hashtable - has built-in magic that allows you to pass its positionally collected arguments on as named arguments (assuming the callee is a PowerShell command).

    However, no custom array supports this, so if you do need an advanced function - which makes $args unavailable - your only option is to reconstruct a hashtable from the ValueFromRemainingArguments array's elements and use the result for splatting; however, this is not only cumbersome, but cannot be done fully robustly - see this answer for an implementation and more information.


    In your specific case, I suggest doing using a mix of splatting and passing (possibly ordered) hashtables directly:

    # Expects -Name as a direct argument and an 
    # -Arguments hashtable with additional name-value pairs.
    function echoName { 
      [CmdletBinding()]
      param(
        [string]$Name,
        [System.Collections.IDictionary] $Arguments
      )
      Write-Verbose -Verbose "echoName: -Name is:"
      $Name
      Write-Verbose -Verbose "echoName: -Arguments is:"
      $Arguments
    }
    
    # Expects just a hashtable, which it passes through to
    # echoName *via a splatting*
    function wrapper {
      [CmdletBinding()]
      param(
        [System.Collections.IDictionary] $Arguments
      )
      Write-Verbose -Verbose "wrapper: -Arguments is: "
      $Arguments
      echoName @Arguments
    }
    
    # Construct an ordered hashtable (dictionary) of name-value pairs.
    $d = [ordered] @{
      Name = 'John'
      # Nested hashtable to ultimately pass to echoName's -Arguments
      Arguments = @{
        Age  = 25
      }
    }
    
    # Pass it *as-is* to wrapper - don't use splatting here.
    wrapper $d
    

    Display output:

    screenshot of output