Advanced Functions: `[CmdletBinding()]`

One line, [CmdletBinding()] above param(), turns a function into an advanced function that binds its parameters the way a cmdlet does: it refuses a misspelled parameter instead of quietly ignoring it, and gets -Verbose, -ErrorAction and the other common parameters for free.

Journeyman · PowerShell · Updated

After this lesson you can

  • Make a function advanced with [CmdletBinding()], so it gets -Verbose, -ErrorAction and the other common parameters
  • Predict how a plain function and an advanced function each handle a misspelled parameter, -Verbose and -ErrorAction

Key takeaways

  • A plain function quietly ignores a parameter it doesn't know, -Verbose and -ErrorAction included.
  • [CmdletBinding()] above param() makes an advanced function: a misspelled parameter is an error, as it is for a cmdlet.
  • An advanced function gets the common parameters for free. -Verbose shows its Write-Verbose lines, and -ErrorAction reaches the commands inside it.
  • Get-Command -Syntax shows [<CommonParameters>] at the end when a function is advanced.

Your Get-Song from Functions: Naming a Job works when you type it right. Tools get used by people in a hurry, though, and someone will type Get-Song -Genre Focus when the parameter is called -Mood. A cmdlet would tell them. Your function doesn't, and that's how small mistakes turn into wrong answers nobody notices: a report built from the wrong songs, a cleanup that skipped the folder it was meant for. This lesson adds the one line that makes your function as strict, and as helpful, as a cmdlet.

You need the practice folder and PlaylistTools.ps1 from Functions: Naming a Job. Go to the folder and load the file:

Set-Location "$HOME\terminal-practice\playlist-tools"
. .\PlaylistTools.ps1

A plain function lets mistakes slide

Try the misspelled parameter:

Get-Song -Genre Focus
Title                  Artist         Minutes Mood
-----                  ------         ------- ----
Mile Marker 9          Juniper Kite   4       Upbeat
Gas Station Coffee     The Night Owls 3       Upbeat
Rainy Overpass         Juniper Kite   6       Chill
Backseat Snacks        Maple Street   3       Upbeat
Cruise Control         The Night Owls 5       Chill
Left at the Lighthouse Maple Street   7       Focus
Last Exit Home         Juniper Kite   4       Chill

No error, and every song instead of the one Focus song. A plain function puts any parameter it doesn't know on a leftover list and does nothing with it. The parameters every cmdlet has go the same way. Ask it to keep quiet about a missing playlist:

Get-Song -Path rock-trip.csv -ErrorAction SilentlyContinue
Import-Csv: C:\Users\Sam\terminal-practice\playlist-tools\PlaylistTools.ps1:6
Line |
   6 |      $songs = Import-Csv -Path $Path
     |               ~~~~~~~~~~~~~~~~~~~~~~
     | Could not find file 'C:\Users\Sam\terminal-practice\playlist-tools\rock-trip.csv'.

The red text comes anyway: -ErrorAction was ignored too.

One line: [CmdletBinding()]

Open PlaylistTools.ps1 and add [CmdletBinding()] (read "cmdlet binding") on the line above param(). While you're there, add two Write-Verbose lines that say what the function is doing:

function Get-Song {
    [CmdletBinding()]
    param(
        [string]$Mood,
        [string]$Path = 'road-trip.csv'
    )
    Write-Verbose "Reading songs from $Path"
    $songs = Import-Csv -Path $Path
    if ($Mood) {
        Write-Verbose "Keeping only the $Mood songs"
        $songs = $songs | Where-Object Mood -eq $Mood
    }
    $songs
}

Save, load the file again, and repeat the mistake:

. .\PlaylistTools.ps1
Get-Song -Genre Focus
Get-Song: A parameter cannot be found that matches parameter name 'Genre'.

A function with [CmdletBinding()] is an advanced function: PowerShell matches its parameters the way it does a cmdlet's, and a name it doesn't know is an error. Any [Parameter()] line, such as [Parameter(Mandatory)], makes a function advanced too. You've seen the same line at the top of a script in Where Output Goes: Write-Output, Write-Host and Friends: it works the same way in a script.

The common parameters, for free

Ask Get-Command how to call Get-Song now:

Get-Command Get-Song -Syntax
Get-Song [[-Mood] <string>] [[-Path] <string>] [<CommonParameters>]

[<CommonParameters>] at the end is new. The common parameters are the ones every cmdlet has: -Verbose, -ErrorAction, -WarningAction, -ErrorVariable, -OutVariable and a few more. Once help is installed (Reading Help: Get-Help), Get-Help about_CommonParameters lists them all. Two of them matter most for your tools. -Verbose shows your Write-Verbose lines, which stay hidden otherwise:

Get-Song -Mood Focus -Verbose
VERBOSE: Reading songs from road-trip.csv
VERBOSE: Keeping only the Focus songs

Title                  Artist       Minutes Mood
-----                  ------       ------- ----
Left at the Lighthouse Maple Street 7       Focus

And -ErrorAction now reaches the commands inside your function, so Import-Csv keeps quiet when you ask it to. The error is still kept in $Error (Reading Errors, and -ErrorAction):

Get-Song -Path rock-trip.csv -ErrorAction SilentlyContinue
$Error[0].Exception.Message
Could not find file 'C:\Users\Sam\terminal-practice\playlist-tools\rock-trip.csv'.

-ErrorAction Stop reaches inside the same way, so a try and catch around your tool sees its errors (try, catch and finally). All of this works the same in Windows PowerShell 5.1 and on a Mac or Linux.

Try it. Run Get-Song -Mood Chill -Verbose, then Get-Song -Moods Chill. What does the second one say, and what would the plain function from Functions: Naming a Job have done with it?

Check yourself

2 questions to try on the lesson page.

So what

Every function you share should start with [CmdletBinding()]: it costs one line, catches mistakes a plain function hides, and gives your tool the switches people already know from every cmdlet.

Key terms

Cheat sheets

Related