What does adding `[CmdletBinding()]` above the param block of a PowerShell function change about how that function behaves?
answer
- it promotes the function's grade
- the caller gains parameters you never wrote
- instrumentation becomes switchable per call
- misspelled arguments stop being silently ignored
- $PSCmdlet, -WhatIf and -Confirm
basics
~20 s[CmdletBinding()] turns a simple PowerShell function into an advanced function: it inherits the common parameters (-Verbose, -Debug, -ErrorAction, -ErrorVariable, -OutVariable and friends), gains access to $PSCmdlet, and binds arguments strictly instead of dumping extras into $args.
solid answer
~40 sIt promotes a plain function to an *advanced function* — one that the engine treats like a compiled cmdlet. Three things follow. First, the common parameters appear automatically: callers get `-Verbose`, `-Debug`, `-ErrorAction`, `-WarningAction`, `-ErrorVariable`, `-OutVariable`, `-PipelineVariable` and the rest without you declaring any of them, so `Write-Verbose` in your body becomes something the caller can switch on per invocation. Second, argument binding becomes strict: an advanced function has no `$args`, so a misspelled or surplus argument is a binding error at the call site instead of silently vanishing. Third, `$PSCmdlet` becomes available, which is how you reach `ShouldProcess` and the parameter-set name. The attribute also carries arguments — most importantly `SupportsShouldProcess = $true`, which adds `-WhatIf` and `-Confirm`, and `DefaultParameterSetName`. It requires a `param` block, even an empty `param()`.
code
powershell · 14 linesfunction Remove-Widget {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param(
[Parameter(Mandatory = $true)]
[string]$Name
)
Write-Verbose "Preparing to remove $Name"
if ($PSCmdlet.ShouldProcess($Name, 'Remove widget')) {
"removed $Name"
}
}
Remove-Widget -Name 'gear' -WhatIf -Verbosego deeper
Recognise the attribute at the top of well-written functions and know it is what gives a function -Verbose and the other standard switches without you writing them.
Explain the promotion to an advanced function precisely: common parameters, strict argument binding with no $args, and access to $PSCmdlet — and say that it needs a param block.
Demonstrate the operational payoff: -WhatIf and -Confirm via SupportsShouldProcess on anything destructive, and -Verbose as instrumentation that stays in the code and costs nothing when unused.
Argue it as a team convention — a function others invoke should present the same control surface as a built-in command, so failure modes and dry-run behaviour are predictable across the whole tooling estate.
## Simple functions and advanced functions PowerShell has two grades of function. A **simple function** is what you get by default: it takes arguments however they arrive, exposes nothing beyond the parameters you declared, and collects anything it did not recognise into the automatic variable `$args`. An **advanced function** is a function the engine treats as though it were a compiled cmdlet, and the switch that promotes one to the other is the `[CmdletBinding()]` attribute placed immediately above the `param` block. ```powershell function Get-Widget { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Name ) Write-Verbose "Looking up $Name" [pscustomobject]@{ Name = $Name; Found = $true } } ``` The attribute must sit with a `param` block. `[CmdletBinding()]` followed by `param()` — empty parentheses — is legal and is the correct way to write a parameterless advanced function; the attribute on its own without any `param` keyword does nothing. ## What you inherit: the common parameters The most visible effect is that your function suddenly accepts a set of parameters you never wrote. These are the *common parameters*, and they include `-Verbose`, `-Debug`, `-ErrorAction`, `-ErrorVariable`, `-WarningAction`, `-WarningVariable`, `-InformationAction`, `-InformationVariable`, `-OutVariable`, `-OutBuffer` and `-PipelineVariable`. This is not cosmetic. `Write-Verbose` inside a body writes nothing by default, because `$VerbosePreference` is `SilentlyContinue`. When the caller adds `-Verbose`, the engine raises the preference for the duration of that call, and your verbose lines appear. Without `[CmdletBinding()]` there is no `-Verbose` to pass, so instrumentation you wrote is unreachable unless the caller mutates a global preference variable — which affects everything, not just your call. The same argument applies to `-ErrorAction` and `-WarningAction`: they give the caller per-invocation control over how your function's non-fatal output behaves. ## What you give up: `$args` and sloppy calls Advanced functions do not have `$args`. Every argument must bind to a declared parameter, positionally or by name, and anything left over is a binding failure: ``` Get-Widget -Name 'gear' -Colur 'red' # A parameter cannot be found that matches parameter name 'Colur'. ``` In a simple function that typo would have landed in `$args` and been ignored, and the function would have run with the wrong intent and no complaint. Strict binding is one of the strongest reasons to use `[CmdletBinding()]` on anything other people call: it converts a silent wrong result into a loud failure at the boundary. ## `$PSCmdlet` and the attribute's arguments Inside an advanced function you can use `$PSCmdlet`, the runtime object representing the invocation. Its most-used members are `ShouldProcess()` and `ShouldContinue()` for confirmation, `ParameterSetName` for branching on which set bound, and `ThrowTerminatingError()`. `ShouldProcess` only works if you opt in through the attribute: ```powershell [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] param( [string]$Name ) ... if ($PSCmdlet.ShouldProcess($Name, 'Remove')) { # do the destructive thing } ``` That single argument adds `-WhatIf` and `-Confirm` to your function for free. `-WhatIf` makes `ShouldProcess` return `$false` while printing what *would* happen, which is the standard way a PowerShell tool offers a dry run. `ConfirmImpact = 'High'` makes the function prompt by default when `$ConfirmPreference` is at its default of `High`. Other useful arguments: `DefaultParameterSetName` names the set to use when the binder cannot tell from the arguments alone; `PositionalBinding = $false` forces every parameter to be passed by name, which is a good discipline for functions with many parameters. ## What it does *not* do `[CmdletBinding()]` does not by itself make your function accept pipeline input — that comes from `[Parameter(ValueFromPipeline = $true)]` on a parameter, together with a `process` block to handle each item. It does not validate your parameters — validation attributes such as `[ValidateSet()]` and `[ValidateNotNullOrEmpty()]` are separate. It does not change what your function outputs. And it does not turn `Write-Error` into an exception; how errors behave is governed by the error-handling machinery, not by the attribute. A related attribute worth knowing is `[OutputType([string])]`, which declares the type your function emits. It is documentation for tooling and tab completion — the engine does not enforce it. ## The practical rule Use `[CmdletBinding()]` on any function that will live longer than the current prompt. It costs one line, gives callers the standard control surface they already expect from built-in commands, and makes bad calls fail immediately.
- How do you give your function a `-WhatIf` switch?Declare `[CmdletBinding(SupportsShouldProcess = $true)]` and guard the destructive work with `if ($PSCmdlet.ShouldProcess($target, $action)) { ... }`. The engine adds `-WhatIf` and `-Confirm` automatically. Under `-WhatIf`, `ShouldProcess` returns `$false` and prints the would-be action, so the guarded block never runs. Add `ConfirmImpact = 'High'` if it should prompt by default.
- Why can't an advanced function use `$args`?Because the whole point of advanced binding is that every argument must resolve to a declared parameter. With `$args` there would be a silent bucket for anything unmatched, so typos and surplus arguments would pass unnoticed. Removing it makes bad calls fail at the boundary. If you genuinely need arbitrary pass-through, declare a `[Parameter(ValueFromRemainingArguments = $true)]` parameter.
- If `Write-Verbose` produces nothing by default, what is the point of writing it?It is dormant instrumentation. `$VerbosePreference` defaults to SilentlyContinue, so verbose lines cost nothing in normal runs, but a caller adding `-Verbose` gets a narration of what the function did — for that call only. It gives you a debugging channel that does not pollute the success output stream and does not need to be stripped before shipping.
saying these in an interview costs you the question
- Thinks [CmdletBinding()] alone makes a function accept pipeline input
- Believes it validates parameter values
- Adds it without any param block and expects an effect
- Says -Verbose must be declared manually as a switch parameter
- Claims it converts errors into terminating exceptions