Advanced Functions: CmdletBinding, Parameter Validation & Pipeline Input
PowerShell Intermediate/Advanced
Chapter 1 · Advanced Functions: CmdletBinding, Parameter Validation & Pipeline Input
PowerShell Fundamentals covered functions honestly, but deliberately kept them simple: a param() block, a type, maybe [Parameter(Mandatory)], and — as the capstone's own Get-OldFileReport showed — real discipline about never letting stray output pollute the return value. That's genuinely enough for scripts you write and run yourself. This course exists for the next step: functions built to behave like real, first-class cmdlets — with automatic parameters, real input validation, native pipeline support, and the same -WhatIf safety net Remove-Item itself has. It starts with the one attribute that unlocks all of it.
[CmdletBinding()]: Turning a Function Into a Real Cmdlet
Adding [CmdletBinding()] immediately after function Name { — before param() — promotes a plain function into what PowerShell calls an advanced function, and the built-in cmdlets used throughout Fundamentals have always secretly worked this way:
That one attribute adds a whole family of CommonParameters automatically — -Verbose, -Debug, -ErrorAction, -WarningAction, -ErrorVariable, -WarningVariable, -OutVariable, -OutBuffer, and -PipelineVariable — with zero extra param() entries needed:
This is exactly what made -Verbose and -ErrorAction Stop feel like built-in language features throughout Fundamentals — every cmdlet you called there had [CmdletBinding()] (or its C#-level equivalent) applied the whole time.
Parameter Validation Attributes
Fundamentals' own [Parameter(Mandatory)] only checked that a value was supplied at all. Validation attributes check that the value supplied is actually acceptable, rejecting bad input before the function body ever runs:
| Attribute | Checks |
|---|---|
| [ValidateNotNullOrEmpty()] | Rejects $null or an empty string/collection |
| [ValidateRange(1, 100)] | Numeric value must fall within the given range |
| [ValidateSet("Low","Medium","High")] | Value must be one of a fixed, explicit list |
| [ValidatePattern('^\d{5}$')] | Value must match a regular expression |
| [ValidateScript({ Test-Path $_ })] | Value must pass an arbitrary custom script block |
| [ValidateCount(1, 5)] | An array parameter must have between the given number of elements |
Pipeline Input: ValueFromPipeline vs. ValueFromPipelineByPropertyName
A Fundamentals-style function only ever received arguments passed explicitly by name or position. An advanced function can accept objects piped directly in, in two genuinely different ways:
ValueFromPipeline is right when the whole piped-in object is the value you want. ValueFromPipelineByPropertyName is right when you're receiving richer objects (like Chapter 3's own Get-Process output) and only want to pull one matching property off each one — exactly the kind of binding that makes so many built-in cmdlets pipe together without you ever manually extracting a property first.
Why Pipeline-Bound Functions Need a process {} Block
begin/process/end — runs exactly once, no matter how many objects the pipeline sends it, and by the time that single run executes, a pipeline-bound parameter only still holds the last object received. begin {} runs once, before the first pipeline object arrives — the right place for one-time setup. process {} runs once per pipeline object — the only block that actually sees every item. end {} runs once, after the last object has been processed — the right place for a final summary. Fundamentals' own functions never needed this distinction because none of them declared ValueFromPipeline — the moment a parameter does, process {} stops being optional.
SupportsShouldProcess: Real -WhatIf/-Confirm Support
The Fundamentals capstone leaned on Remove-Item's own built-in safety — this is how to give a function you write that exact same behavior:
$PSCmdlet.ShouldProcess() is only meaningful once SupportsShouldProcess is declared — it automatically wires up both -WhatIf (preview only, shown above) and -Confirm (an interactive yes/no prompt before each action), with zero extra parameter declarations of your own.
[CmdletBinding(SupportsShouldProcess)] on by default — the same instinct that made Fundamentals' own capstone wrap Remove-Item in try/catch, just built into the function itself this time.
Show-NameBroken above didn't throw an error — it just quietly processed one object instead of three, with no warning that anything was wrong. This is exactly the kind of bug that survives a quick manual test (where you might only ever pipe in one item) and then breaks the moment it meets a real, multi-item pipeline in production. Any parameter marked ValueFromPipeline or ValueFromPipelineByPropertyName is a signal to double-check that a process {} block actually exists.
Hands-On Exercises
Explain why Show-NameBroken only prints one line ("Carol") when three names are piped into it, using this chapter's own explanation of how a plain function body executes versus a process {} block.
Explain the difference between ValueFromPipeline and ValueFromPipelineByPropertyName. Using this chapter's own Get-Process | Test-ByProperty example, explain specifically why ValueFromPipeline wouldn't have worked correctly there instead.
Write a function Remove-OldLog that accepts a mandatory, pipeline-bound [string]$Path, supports -WhatIf/-Confirm via SupportsShouldProcess, and correctly processes every object piped into it (not just the last one). Explain how your function avoids both gotchas covered in this chapter.
Chapter 1 Quick Reference
[CmdletBinding()]— promotes a plain function to an advanced function; unlocks CommonParameters (-Verbose,-Debug,-ErrorAction, etc.) automatically- Validation attributes —
[ValidateNotNullOrEmpty()][ValidateRange()][ValidateSet()][ValidatePattern()][ValidateScript()][ValidateCount()], checked before the function body ever runs ValueFromPipeline— the entire piped object becomes the parameter's valueValueFromPipelineByPropertyName— binds by matching a property name on the incoming object to the parameter namebegin {} / process {} / end {}— once before / once per pipeline object / once after; a pipeline-bound parameter needsprocess {}or it silently only sees the last item[CmdletBinding(SupportsShouldProcess)]+$PSCmdlet.ShouldProcess()— real, built-in-WhatIf/-Confirmsupport, the same safetyRemove-Itemitself has- Next chapter: Regular Expressions & Advanced String/Text Processing