Better PowerShell Splatting

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