One of PowerShell’s more useful and differentiating features is its ability to splat command-line arguments. Normally, to invoke a command in PowerShell with arguments, you could do something like this:
Get-Process -Name *Tomcat* -ComputerName somebox.around.here |
It may be useful to capture the arguments first, then invoke the command later:
$myArgs = @{ Name = "*Tomcat*"; ComputerName = "somebox.around.here" } Get-Process @myArgs |
It can also be incredibly useful if you’re writing wrapper functions:
Function Import-ModuleForce { param( [Parameter(Mandatory = $true)] [string[]] $Name ) $PSBoundParameters.Force = $true Import-Module @PSBoundParameters } # unfortunately, aliases can't be set up to script blocks; # only functions and cmdlets get that honor Set-Alias imf Import-ModuleForce |
It can also be useful when you’re passing in the same arguments to multiple functions consecutively:
$myArgs = @{ Name = "*Tomcat*" ComputerName = "somebox.around.here" } Get-Service @myArgs Get-Process @myArgs |
But sometimes splatting doesn’t work, notably when you have extra values in the hashtable that the function isn’t expecting:
$myArgs = @{ Name = "*Tomcat*" ComputerName = "somebox.around.here" DependentServices = $true } Get-Service @myArgs Get-Process @myArgs |
A nasty little error is PowerShell’s response:
Get-Process : A parameter cannot be found that matches parameter name 'DependentServices'. + Get-Process <<<< @myArgs + CategoryInfo : InvalidArgument: (:) [Get-Process], ParameterBindingException + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.GetProcessCommand |
The Get-Service cmdlet has a DependentServices argument, but Get-Process does not. Although this can sometimes be a helpful error message, in this case, it would be nice if the command just ignored the arguments it couldn’t understand.
So that’s why I wrote Invoke-Splat:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | Function Invoke-Splat { <# .Synopsis Splats a hashtable on a function in a safer way than the built-in mechanism. .Example Invoke-Splat Get-XYZ $PSBoundParameters #> param( [Parameter(ValueFromPipeline=$true, Mandatory=$true, Position=0)] [string] $FunctionName, [Parameter(ValueFromPipeline=$true, Mandatory=$true, Position=1)] [System.Collections.Hashtable] $Parameters ) $h = @{} ForEach ($key in (gcm $FunctionName).Parameters.Keys) { if ($Parameters.$key) { $h.$key = $Parameters.$key } } if ($h.Count -eq 0) { $FunctionName | Invoke-Expression } else { "$FunctionName @h" | Invoke-Expression } } |
Rewriting the example from above:
$myArgs = @{ Name = "*Tomcat*" ComputerName = "somebox.around.here" DependentServices = $true } Invoke-Splat Get-Service $myArgs Invoke-Splat Get-Process $myArgs |
And mixing Invoke-Splat with the @ operator:
Function Import-ModuleForce { param( [Parameter(Mandatory = $true)] [string[]] $Name, [string] $SomethingElse ) $PSBoundParameters.Force = $true Invoke-Splat Import-Module $PSBoundParameters } $myArgs = @{ Name = "MyModule" SomethingElse = "AnotherString" } Import-ModuleForce @myArgs |