1

In Powershell, how to pass $args of type string[] to a "function param()"... see code below, it doesn't work...

(Note: I dont want to pass a hash @{} of type Hashtable to the function as required by splatting. I want to pass a string[] type as arguments to function param)...

# FILE: run1.ps1
function run1{
   param(
       [switch]$go,
       [string]$sim,
       [switch]$rec
   )
   
   write-host "go:  $go"
   write-host "sim: $sim"
   write-host "rec: $rec"   
}


write-host ""
foreach ($a in $args) {
    write-host "a: $a"  
}
write-host ""

run1 $args


PS> ./run1.ps1

a: -go
a: -rec:
a: dfsdf
a: -rec

go:  False
sim: -go -rec: dfsdf -rec
rec: False
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • how to do this using "&" or invoke-command? I tried splatting an Array instead of a hashtable and its flakey... and doesn't work with ps2exe.... – Bimo Feb 08 '22 at 16:24

2 Answers2

1

Instead of run1 $args, use run1 @args, i.e. splatting with the automatic $args variable.

That way, whatever arguments are passed to the run1.ps1 script - whether named or positional - are correctly passed through to the run1 function.

  • Caveat: This does not work for pass-through [switch] arguments with an explicit value (which is rare, however). For instance, if you were to invoke run1.ps1 with -rec:$false (instead of simply omitting this argument), -rec and $false would mistakenly be passed as two arguments, and $rec would end up being $true.

Note:

  • No user-defined array can provide this functionality[1] - the automatic $args array has magic built in that enables this functionality; splatting with a user-defined array only supports passing positional arguments through, and that wouldn't work in your case, because [switch] parameters are always named (non-positional).

  • For splatting based on user-defined variables that supports passing named arguments, hashtable-based splatting is necessary.

As an aside:

  • If you make your function an advanced one (in the simplest case by decorating the param(...) block with [CmdletBinding()]), it'll ensure that no extra arguments are passed (ones that don't bind to predefined parameters), for added robustness.

  • If you additionally make your script an advanced one - which requires defining parameters explicitly for the script itself too - you can pass the script's parameter values through via the automatic $PSBoundParameters variable variable, i.e. with run1 @PSBoundParameters. This would also avoid the [switch]-related bug mentioned above. This answer shows an example of $PSBoundParameters-based splatting in the context of a proxy (wrapper) function around Write-Host.


[1] As Santiago Squarzon points out, there a limited way to make this work, but it is both highly obscure and more cumbersome than using a hashtable for splatting and therefore not worth considering.
For the sake of completeness: To have an array element represent a parameter name, use a dummy string decorated with a NoteProperty member literally named <CommandParameterName> whose value is the target parameter name (with or without the - prefix). A minimal example that is the equivalent of
Get-ChildItem -Directory -LiteralPath \:
$arr = ('' | Add-Member -PassThru '<CommandParameterName>' 'Directory'), ('' | Add-Member -PassThru '<CommandParameterName>' 'LiteralPath'), '\'; Get-ChildItem @arr
In fact, this technique is behind the magic of @args, as the following example shows:
function foo { $args | % { "`n---`n[$_]"; $_ | Get-Member -Type NoteProperty -Force } }; foo -param1 val1

mklement0
  • 382,024
  • 64
  • 607
  • 775
-1
function run1{
   param(
       [switch]$go,
       [string]$sim,
       [switch]$rec
   )
   
   write-host "go:  $go"
   write-host "sim: $sim"
   write-host "rec: $rec"   
}

$cmd = "run1 $args"
write-host "cmd: $cmd"
invoke-expression "$cmd"
PS> run1.ps1 -go -sim:asdfasdf +rec


PS> run1.ps1 -go -sim:asdfasdf -rec
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • [`Invoke-Expression` (`iex`) should generally be avoided](https://stackoverflow.com/a/51252636/45375); definitely [don't use it to invoke an external program or PowerShell script / function](https://stackoverflow.com/a/57966347/45375). – mklement0 Feb 08 '22 at 18:15
  • A simple example that shows that your approach isn't robust: `run1.ps1 -go -sim:'foo bar' -rec` doesn't bind `-sim` correctly (only binds `foo` as the value). – mklement0 Feb 08 '22 at 18:20
  • Also, PowerShell `[switch]` parameters don't support syntax such as `+rec`. Pass `-rec` to set `$rec` to `$true`, and _omit it_ to default to `$false`. If you need to _explicitly_ pass the switch value, use `-rec:$true` or `-rec:$false` (which also wouldn't work with your `Invoke-Expression` approach). Your `+rec` argument was simply _ignored_, because it didn't bind to any declared parameter (but it would have been reflected in `$args`). If your function were an advanced one, you would have gotten a _syntax error_. – mklement0 Feb 08 '22 at 18:24