Chapter 05Lesson 04~120 minutes

Splatting, Calculated Properties, and Readable Command Construction

Refactor long command lines into inspectable parameter splats, add optional behavior without dynamic evaluation, and derive reusable properties for projection, sorting, and presentation.

Learning objectives

  • Refactor long named-parameter invocations into hashtable splats that can be inspected before execution.
  • Explain the difference between $hash and @hash syntax in command invocation context.
  • Use array splatting for positional arguments only when position is truly an appropriate contract.
  • Build optional parameter sets conditionally without Invoke-Expression and combine non-conflicting splats when useful.
  • Create readable Name/Expression calculated properties for Select-Object, Sort-Object, and Format-* commands.
  • Use splatting to improve testing, logging, code review, and safe command construction.

1. Long command lines hide the configuration that produced them

A command can be syntactically valid and still be difficult to review. When several parameters are conditional, repeating a long invocation makes tests and logging awkward. Splatting lets you collect arguments in a data structure and then apply them to a command.

# Harder to scan and change safely:
Get-ChildItem -Path . -Filter '*.ps1' -File -Recurse -ErrorAction Stop

# Same command, with named parameters represented as data:
$parameters = @{
    Path = '.'
    Filter = '*.ps1'
    File = $true
    Recurse = $true
    ErrorAction = 'Stop'
}

Get-ChildItem @parameters

The hashtable now describes the command configuration. This makes it easier to inspect, conditionally add options, test, or log without constructing executable source code as a string.

2. Hashtable splatting maps keys to parameter names and values to arguments

For named parameters, create a hashtable whose keys match parameter names. At invocation time, use @parameters instead of $parameters. The dollar-sign form evaluates the hashtable as a normal object; the at-sign form tells PowerShell to expand the dictionary into command arguments.

$parameters = @{
    Path = '.'
    Filter = '*.log'
    File = $true
}

# Inspect the dictionary itself.
$parameters

# Apply it as command parameters.
Get-ChildItem @parameters
Syntax distinction: $parameters means “the hashtable value.” @parameters in a command invocation means “splat these entries as parameters.” The variable still has the name parameters; splatting changes how it is used.

3. Array splatting supplies positional arguments, but named splats are usually clearer

PowerShell can also splat an array. Its elements are passed positionally. This can be useful when wrapping a command whose positional contract is stable, but it is less self-documenting because the array does not show parameter names.

$arguments = @('.', '*.ps1')
Get-ChildItem @arguments

For team automation, prefer a hashtable splat when named parameters are available. Positional array splatting is best reserved for cases where position is genuinely part of the interface or when calling tools that do not expose PowerShell-style named parameters.

4. Build optional behavior by changing data, not by generating command text

Because a splat is ordinary data before invocation, you can add or remove keys conditionally. This is safer and easier to test than concatenating a command string and passing it to Invoke-Expression.

$includeSubdirectories = $true
$filePattern = '*.json'

$parameters = @{
    Path = '.'
    File = $true
    ErrorAction = 'Stop'
}

if ($filePattern) {
    $parameters.Filter = $filePattern
}

if ($includeSubdirectories) {
    $parameters.Recurse = $true
}

$parameters | Format-Table Key, Value -AutoSize
Get-ChildItem @parameters

The command remains a real command invocation. PowerShell still performs normal parsing, parameter binding, type conversion, and validation.

5. Splats can be combined when parameter responsibilities are separate

You can split parameters into separate dictionaries when they represent distinct concerns—for example, stable defaults versus optional filters. Multiple splats can be used in one invocation as long as the same parameter is not supplied more than once. PowerShell 7.x also allows an explicitly named parameter to override a value supplied by a splat, but avoiding duplicates is usually clearer.

$base = @{
    Path = '.'
    File = $true
    ErrorAction = 'Stop'
}

$filter = @{
    Filter = '*.ps1'
    Recurse = $true
}

Get-ChildItem @base @filter

Separate splats are valuable when they correspond to independently testable configuration fragments. Do not split one command into many tiny hashtables merely because the syntax allows it.

6. Calculated properties create derived fields without mutating the source object

A calculated property is a property definition supplied to commands such as Select-Object, Sort-Object, or formatting commands. The readable form uses a hashtable with Name and Expression. The expression is a script block evaluated for each input object.

Get-ChildItem -File |
    Select-Object Name, Length, @{
        Name = 'SizeKiB'
        Expression = { [math]::Round($_.Length / 1KB, 2) }
    }

The source file object is not changed. Select-Object emits a new projected object containing the requested properties, including the derived SizeKiB field.

7. Sort-Object can calculate the key used for ordering

Sorting also accepts a calculated expression. This is useful when the value you want to rank by is not already a property or needs normalization first.

$files = Get-ChildItem -File

$files | Sort-Object @{
    Expression = { $_.Length }
    Descending = $true
} | Select-Object -First 5 Name, Length

The hashtable here is not parameter splatting. It is a command-specific data structure that describes one sort key. Context determines what @{...} means: it always creates a hashtable, but the receiving command decides how to interpret that hashtable.

8. Formatting commands also accept calculated properties—but formatting remains terminal

You can use calculated properties with Format-Table and Format-List to improve human-readable output. Chapter 03’s rule still applies: formatting objects belong at the end of the data pipeline. If a derived property must be exported or reused, calculate it with Select-Object first.

Get-ChildItem -File |
    Format-Table Name, @{
        Name = 'SizeKiB'
        Expression = { [math]::Round($_.Length / 1KB, 2) }
    } -AutoSize

Use formatting for humans and projection for data. The syntax may look similar, but the output contracts are intentionally different.

9. Splatting makes logging and code review more precise

A splat can be inspected before execution, compared in tests, or logged after sensitive values are redacted. Reviewers can see which parameters are conditional without mentally parsing a long command line. This becomes especially valuable with APIs, cloud modules, remoting, and CI/CD commands that have many optional parameters.

10. Diagnose splatting failures by inspecting the dictionary and command metadata

If a splatted invocation fails, first inspect the keys and values, then compare them with Get-Help or Get-Command parameter metadata. A misspelled key becomes an unrecognized parameter name; an incompatible value still goes through normal parameter conversion and validation.

$parameters = @{
    Path = '.'
    Filtre = '*.ps1'   # deliberately misspelled
}

try {
    Get-ChildItem @parameters
}
catch {
    $_.Exception.Message
}

Get-Command Get-ChildItem -Syntax

The fix is to correct the data—not to hide the error with dynamic evaluation.

11. Lab: conditionally build a safe file-discovery command

This disposable lab creates a few temporary files, builds a Get-ChildItem splat based on options, derives a reusable size property, and cleans up the temporary directory.

$lab = Join-Path ([System.IO.Path]::GetTempPath()) ('ps-academy-ch05-l04-' + [guid]::NewGuid())
New-Item -ItemType Directory -Path $lab | Out-Null

try {
    Set-Content -Path (Join-Path $lab 'app.json') -Value '{"name":"app"}' -Encoding utf8
    Set-Content -Path (Join-Path $lab 'notes.txt') -Value 'training notes' -Encoding utf8
    New-Item -ItemType Directory -Path (Join-Path $lab 'nested') | Out-Null
    Set-Content -Path (Join-Path $lab 'nested/worker.json') -Value '{"name":"worker"}' -Encoding utf8

    $includeNested = $true
    $extension = '*.json'

    $parameters = @{
        Path = $lab
        File = $true
        ErrorAction = 'Stop'
    }

    if ($includeNested) { $parameters.Recurse = $true }
    if ($extension) { $parameters.Filter = $extension }

    $files = Get-ChildItem @parameters |
        Select-Object FullName, Length, @{
            Name = 'SizeKiB'
            Expression = { [math]::Round($_.Length / 1KB, 3) }
        }

    $files | Sort-Object FullName
    $parameters | Format-Table Key, Value -AutoSize
}
finally {
    Remove-Item -LiteralPath $lab -Recurse -Force -ErrorAction SilentlyContinue
}

Nothing in the lab constructs executable source text. The parameter set is ordinary inspectable data until Get-ChildItem @parameters performs normal command invocation.

Verification checklist

12. Common mistakes to avoid

Confusing $hash with @hash. The first is the hashtable object; the second splats it only in a command invocation context.

Using Invoke-Expression to make optional parameters. Keep parameters as data and invoke the command normally.

Duplicating the same parameter across splats. Build one unambiguous final parameter set.

Using Format-* calculated properties when the derived field must be exported. Use Select-Object to keep data reusable.

13. Knowledge check

Question 1. What does @params mean in Get-ChildItem @params?

Question 2. What does $params mean by itself?

Question 3. When is array splatting used?

Question 4. Why are calculated properties useful?

Question 5. Why is splatting safer than constructing a command string with Invoke-Expression?

14. Summary

Splatting turns command parameters into inspectable data. Hashtable splatting is the clearest approach for named parameters; array splatting is available for positional arguments. Conditional splat construction avoids dynamic command strings and improves reviewability, testing, and logging. Calculated properties use small hashtables to define derived fields for projection, sorting, or formatting—while the Chapter 03 rule still keeps formatting at the end of the pipeline.

15. Further reading

Next lesson

Close the chapter by choosing structures intentionally and understanding copy semantics

Lesson 05 combines arrays, dictionaries, and custom objects into nested configuration, then explains references, shallow copies, equality, and when data belongs outside the script.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.