Splatting is a really great feature in PowerShell that lets you take a [hashtable]
of parameters and call a function or cmdlet with the hash instead of having to type out every parameter name and value. It seems natural then that if you’re writing a wrapper or proxy function, where your function takes the same or nearly the same parameters as the function you’re calling, you could use $PSBoundParameters
to do the heavy lifting (this special variable contains all of the parameter values passed into the function).
The problem comes when your proxy function has defaults for its optional parameters. $PSBoundParameters
only includes the values of parameters that were explicitly supplied by the caller. There is no such variable that contains the default values.
Workaround
I’ve created a small code snippet which alleviates this. It finds the parameters of your script or function, then gets the actual current values (you can override the supplied parameter values), and puts them all in a [hashtable]
.
$params = @{}
foreach($h in $MyInvocation.MyCommand.Parameters.GetEnumerator()) {
try {
$key = $h.Key
$val = Get-Variable -Name $key -ErrorAction Stop | Select-Object -ExpandProperty Value -ErrorAction Stop
if (([String]::IsNullOrEmpty($val) -and (!$PSBoundParameters.ContainsKey($key)))) {
throw "A blank value that wasn't supplied by the user."
}
Write-Verbose "$key => '$val'"
$params[$key] = $val
} catch {}
}
Details
I’m using $MyInvocation.MyCommand.Parameters
to get all of the parameter names, and then I’m using Get-Variable
to get the actual current value of it.
That means any default value of a parameter will be counted. It was also especially useful for me because I was doing additional checking and in some cases changing the values of the parameters. This method will see the new values, so the resulting hash will be “complete”.
I found that in some cases Get-Variable
threw an exception because it couldn’t find the variable. This was actually because of automatic parameters like $Verbose
which actually translate into $VerbosePreference
and the like, so these will not be included in the hash.
I also found that optional parameters which didn’t supply a default were being included in the hash. That’s the part where I check for $val
being null or empty. I also check to see if it’s included in $PSBoundParameters
and if not, it won’t be included.
This check is specifically for the case where you intentionally passed in a $null
or an empty string, so that they won’t erroneously be excluded.
Splatting Example
Here’s a complete example with 2 functions.
function Test-Params {
[CmdletBinding()]
param(
[Parameter()]
[Int]
$Optional1 ,
[Parameter()]
[String]
$Optional2 = 'Value2' ,
[Parameter(
Mandatory=$true
)]
[Int]
$Mandatory3 ,
[Parameter()]
[Int]
$Optional4 = 100 ,
[Parameter()]
[Switch]
$DoThing
)
if($DoThing) {
$Mandatory3 = $Mandatory3*2
}
$params = @{}
foreach($h in $MyInvocation.MyCommand.Parameters.GetEnumerator()) {
try {
$key = $h.Key
$val = Get-Variable -Name $key -ErrorAction Stop | Select-Object -ExpandProperty Value -ErrorAction Stop
if (([String]::IsNullOrEmpty($val) -and (!$PSBoundParameters.ContainsKey($key)))) {
throw "A blank value that wasn't supplied by the user."
}
Write-Verbose "$key => '$val'"
$params[$key] = $val
} catch {}
}
$params.Remove('DoThing')
Test-Callee @params
}
function Test-Callee {
[CmdletBinding()]
param(
[Parameter()]
[Int]
$Optional1 ,
[Parameter()]
[String]
$Optional2 ,
[Parameter(
Mandatory=$true
)]
[Int]
$Mandatory3 ,
[Parameter()]
[Int]
$Optional4
)
Write-Host $Optional1
Write-Host $Optional2
Write-Host $Mandatory3
Write-Host $Optional4
}
Test-Params -Optional1 22 -Mandatory3 55
Test-Params -Optional1 22 -Mandatory3 55 -DoThing
Notes
Note that the proxy function here has an additional switch parameter called DoThing, and if that’s specified then we’re modifying the value of Mandatory3. Note that since the function we’re proxying doesn’t contain that parameter, we have to remove it from the hash before splatting.
The output of the above script is:
22
Value2
55
100
22
Value2
110
100
Happy scripting!