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

PowerShell Deployment Options are Awful

I once looked into using Powershell as a foundation for an application that was to be very heavily powered by scripts underneath. It seemed like an idea with a lot of promise—most of the libraries it was to work with were written in .NET, the users were comfortable with scripting, and it would have been easy to expose GUI components in a scriptable way (think VBA for Office)—however, actually getting the thing deployed would have been a totally different manner.

Most firms lock down end-user machines. This makes for a more secure and easier to manage network, but it makes deploying an application an absolute nightmare when an application requires a non-trivial installer and administrative privileges to use.

And that brings me to the title of this post. Powershell deployment options are awful. Considering that Powershell is written largely (exclusively?) in .NET, it’s unbelievable that an installer has to be run, files need to be installed in system directories, and registry keys need to be written. About 99% of what it does can be done in pure .NET, and any app written in pure .NET can be copied to a directory and run as long as its dependencies are in the same directory or in the GAC. Life would have been much simpler for me if I could just include the Powershell .NET assemblies, deploy them with my application, and run Powershell scripts.

And then let’s say I do end up being able to deploy PowerShell properly, with all its funky registry keys and whatever else that installer does; what am I supposed to do with any PSSnapIns that I’m dependent on?

Somewhere on the Microsoft campus, there needs to be a giant poster called “Lessons Learned”. It should include at least the following bullet points:

  • No Clippy. Cartoons don’t help people get work done—functional software does.
  • People like XCOPY deploy—installers are obnoxious.

Well, they removed Clippy from Office 2007. Right now, .NET apps are so simple to deploy it’s criminal. So what’s the deal with Powershell? —DKT