2

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?

6
  • 1
    Change your wrapper to function wrapper { Write-Host "Args are '$args'";echoName @args }. Removing the param block and CmdletBinding attribute will cause PowerShell to start passing the argument values as-is, without attempting any validation or type coercion during binding. Commented Nov 22, 2023 at 8:38
  • @MathiasR.Jessen what if I must use CmdletBinding? Commented Nov 22, 2023 at 8:47
  • Then you have two options, either 1) explicitly declare the parameters you want to forward and then call echoName @PSBoundParameters from the wrapper, or 2) accept a dictionary instance and splat that, eg. param([System.Collections.IDictionary]$kwArgs) echoName @kwArgs (original call needs to be wrapper $dict instead of wrapper @dict then) Commented Nov 22, 2023 at 8:51
  • so either use IDictionary that's not general and breaks when wrapper has more parameters or I must know the echoName parameters in advance? Commented Nov 22, 2023 at 9:00
  • I fail to see how it "breaks when wrapper has more parameters"? You can define any additional parameters for the wrapper function itself, and you can stuff as many entries into the dictionary as you like. What exact use are you not able to solve/support? Commented Nov 22, 2023 at 9:03

1 Answer 1

1

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 caller must use a hashtable whose entries are the parameter name-value pairs.

  • The callee must have explicit, individual parameters whose names match the entry keys of the hashtable.

    • When name-matching, PowerShell's "elastic syntax" applies: If a hashtable entry is an unambiguous prefix of the target parameter name, binding succeeds (e.g. an N entry in your example would bind to parameter -Name, as long as there are no other parameters whose name starts with N). For long-term stability, however, this convenience is best avoided.
  • If the input hashtable has entries that do not match parameters of the callee:

    • They are passed as extra, positional arguments, with each name-value pair becoming two arguments: the name itself in a way that makes it look like the parameter part of a named argument (e.g. '-Name:') and the value as a separate argument (e.g. 'John')

    • If the callee is a simple (non-advanced) function or script, these extra positional arguments are collected in the automatic $args variable variable.

    • If the callee is an advanced function or script, the only way to receive these extra positional arguments is to explicitly define a catch-all parameter - which invariably only supports positional arguments - via the ValueFromRemainingArguments parameter-attribute property, as shown in your question. Without that, an error would occur, because advanced functions by design prevent unrecognized arguments from getting passed.


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

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.