PowerShell Splatting โ Cleaning Up Commands with Many Parameters
A practical guide to splatting, the PowerShell technique that turns a command buried under ten inline parameters into one short call and a readable variable.
@ instead of $, instead of typing every -Parameter Value pair inline.
Named parameter/value pairs stored in a hashtable. Works for positional and switch parameters too.
Ordered values for positional parameters, passed the same way, with no parameter names attached.
The automatic variable holding every parameter a function was actually called with, ready to splat straight into another command.
What Is Splatting?
Splatting is a way to pass a whole collection of parameter values to a command as a single unit, instead of listing every -Parameter Value pair by hand. The values live in an ordinary variable, either a hashtable or an array, and that variable is passed to the command using the At symbol (@) instead of the usual dollar sign ($). The @ tells PowerShell “bind the contents of this collection as parameters,” rather than “pass this whole object as one value.”
Think of splatting as handing a completed order form to a cmdlet, instead of calling out each item one at a time across the counter. Instead of a command line that keeps growing sideways with -Path, -Destination, -Recurse, -Force, the values live in a named variable, and the call itself stays short and stable.
PowerShell supports two splatting forms: a hashtable for named parameters, which also covers positional and [switch] parameters, and an array for parameters being passed positionally, without names at all. A splatted value can appear anywhere in the parameter list, and it can be mixed freely with ordinary inline parameters in the same call.
Why Splatting Instead of Long Inline Commands?
A one-off command with two or three parameters reads fine on a single line. Once a command needs six, eight, or ten parameters, that line stops being reviewable at a glance, and splatting is the standard way PowerShell scripts deal with that.
A hashtable puts one parameter per line, which is easy to scan, diff, and review compared to one long parameter-laden call.
The same splat variable can be passed into several related commands, so a shared setting changes in exactly one place.
$PSBoundParameters lets a wrapper function forward exactly what it was called with, without re-declaring every parameter of the command underneath it.
None of this changes what the command does. Splatting is purely a different way of supplying the same parameter values – PowerShell binds a splatted hashtable or array exactly the way it would bind the equivalent inline arguments.
Why Production Scripts Care About This
Splatting matters once a script or function is going to be reused, reviewed by someone else, or maintained for longer than a single session. A command with eight or ten inline parameters is hard to review in a pull request and easy to get subtly wrong when only one value needs to change between environments. Storing the shared parameters in a hashtable, then overriding just the one that differs, keeps that difference visible instead of buried in a wall of flags.
This shows up constantly around advanced functions with several mandatory or validated parameters: splatting is how a caller keeps a call to one of these functions readable, and $PSBoundParameters is how a wrapper function forwards whatever it was called with down to the real function underneath it, without hard-coding every parameter name twice.
[Parameter()] attribute and validation rule the target command declares – splatting only changes how the value gets there.
Splatting Syntax
The two forms below produce identical results to their traditional, fully inline equivalents. The hashtable form covers named, positional, and switch parameters; the array form is for positional parameters only.
# Traditional inline call - every parameter typed out on one growing line
Copy-Item -Path 'C:\Data\report.csv' -Destination 'D:\Backup\report.csv' -WhatIf
# Hashtable splat - store name/value pairs, then splat with @ instead of $
$copyParams = @{
Path = 'C:\Data\report.csv'
Destination = 'D:\Backup\report.csv'
WhatIf = $true
}
Copy-Item @copyParams
# Array splat - for positional parameters, values in position-number order, no names
$copyArgs = 'C:\Data\report.csv', 'D:\Backup\report.csv'
Copy-Item @copyArgs -WhatIf
| Line | Purpose |
|---|---|
$copyParams = @{...} |
Builds the hashtable of parameter name/value pairs. Each key must match a real parameter name on the target command. |
WhatIf = $true |
A [switch] parameter needs an explicit $true or $false value inside a hashtable splat – a bare key with no value does not turn the switch on the way -WhatIf does inline. |
Copy-Item @copyParams |
The @ sigil tells PowerShell to bind the hashtable’s contents as separate parameters, not pass the hashtable itself as one value. |
$copyArgs = 'a','b' |
Array splat: values listed in position-number order, with no parameter names attached. |
Copy-Item @copyArgs -WhatIf |
A splatted value can sit alongside ordinary inline parameters in the same call, in any order. |
Scripts / Commands
The patterns below cover the splatting behaviour that shows up most often in real scripts: mixing a splat with inline parameters, overriding one splatted value, forwarding $PSBoundParameters through a wrapper function, splatting more than one hashtable into a single call, and merging two hashtables before splatting them.
# Mix a splatted hashtable with an explicit inline parameter in the same call
$vmParams = @{
ResourceGroupName = 'rg-prod-eastus'
Location = 'East US'
VirtualNetworkName = 'vnet-prod'
}
New-AzVm @vmParams -Name 'web01'
# PowerShell 7.1+: an explicitly typed parameter overrides the same key in a splat
New-AzVm @vmParams -Name 'web02' -Location 'West US'
# Forward whatever a function was actually called with, using $PSBoundParameters
function Invoke-RemoteCheck {
param($ComputerName, $Credential)
Test-WSMan @PSBoundParameters
}
# Splat multiple hashtables into a single command - each covers a different concern
$message = @{ Object = 'Deployment complete' }
$colors = @{ ForegroundColor = 'Black'; BackgroundColor = 'White' }
Write-Host @message @colors
# Merge two hashtables with + before splatting, as long as no key overlaps
$common = @{ Path = 'C:\Data'; Recurse = $true }
$extra = @{ Filter = '*.log' }
$merged = $common + $extra
Get-ChildItem @merged
New-AzVm @vmParams -Name 'web02' -Location 'West US' example, requires PowerShell 7.1 or later. On earlier versions, change the value inside the hashtable itself instead of relying on an inline override.
Splatting Cheat Sheet
| Syntax | Use Case | Example |
|---|---|---|
@hashtable | Named, positional, and switch parameters together. | $p = @{Path='C:\x'}; Copy-Item @p |
@array | Positional parameters only, values in position order, no names. | $a = 'x','y'; Copy-Item @a |
@PSBoundParameters | Forward exactly what the current function was called with. | Test1 @PSBoundParameters |
@args | Represent all unassigned parameters in a proxy function without CmdletBinding. | function Get-MyProcess { Get-Process @args } |
$h1 + $h2 | Merge two hashtables before splatting. Fails if a key exists in both. | $merged = $h1 + $h2 |
| Explicit param after a splat | Overrides the same-named key in the splat, PowerShell 7.1 and later. | New-AzVm @p -Location 'West US' |
Troubleshooting Cheat Sheet
| Symptom | Likely Cause | Fix |
|---|---|---|
| A splatted switch parameter never seems to fire | The hashtable key was left with no value, or set to a plain string instead of a boolean. | Set the value explicitly inside the hashtable, for example WhatIf = $true. |
| A positional value binds to the wrong parameter | A hashtable splat was built assuming position order matters, or an array splat’s values are out of position-number order. | Use a hashtable with the real parameter name as the key for anything that is not strictly positional, and keep array splat values in position order. |
Bad argument to operator '+': Item has already been added |
Two hashtables being merged with + share an identical key. |
Rename the conflicting key in one hashtable, or merge with a custom function that decides which value should win instead of using +. |
$args is always empty inside a function |
CmdletBinding or a Parameter attribute was added to the function, which removes the automatic $args variable. |
Use $PSBoundParameters or an explicit param() block instead of @args once the function becomes an advanced function. |
| Adding an explicit parameter after a splat has no effect | The script is running on a PowerShell version older than 7.1, where an inline parameter did not override a splatted one of the same name. | Update to PowerShell 7.1 or later, or change the value inside the hashtable directly instead of relying on an override. |
| A jagged array splats more or fewer arguments than expected | Splatting a jagged array behaves differently against a native executable than against a PowerShell command or script. | Check whether the target is a native command or a PowerShell command – PowerShell scripts receive the nested array as one argument, native commands receive its elements as separate arguments. |
Final Thoughts
Splatting does not change what a command does. It changes how readable the call is once that command has more than a couple of parameters worth setting. A hashtable with one value per line reviews cleanly, diffs cleanly, and makes it obvious which single value differs the next time the same call is reused somewhere else.
The two forms cover almost everything: a hashtable for named, positional, and switch parameters together, and an array when every value is positional and unnamed. $PSBoundParameters and @args extend the same idea to forwarding parameters through wrapper functions, and the PowerShell 7.1 override behaviour means one splatted default set can serve a whole loop of near-identical calls.
$PSBoundParameters whenever a function’s only job is to forward its own parameters into another command.
Next, we can look at how splatting behaves once it meets an advanced function’s own parameter validation – and why a splatted call that fails validation still needs proper try/catch handling around it, not just a readable parameter list.