When to use CmdletBinding in PowerShell?
At Sabin.IO we are big proponents of clean code. We use PowerShell a lot for automation, and want our code to be clean. You are automating everything, right? If not, please see a slide from a recent meetup:
For me, clean code in PowerShell means (and not limited to):
- Small self-contained functions that have a single responsibility
- Number of arguments to a function kept as small as possible
- Consistent formatting
- No duplication of code
- Modules that hide internal functions, and only expose what’s needed
One way to make code a bit cleaner is to make use of PowerShell’s common parameters. If you would like a refresher as to what these are, there is extensive documentation in PowerShell itself which you can access with
Here’s a snippet from the help file:
The following list displays the common parameters. Their aliases are listed
The risk mitigation parameters are:
All of these become available to you in your functions when you use [CmdletBinding()] at the top of the function. There are some gotchas though, so let’s walk through it.
Consider the following code:
If we dot source this file and call the function Get-Message with no parameters we get:
In the above example we did not specify the –UseInternal parameter, so the function doesn’t call the inner function Get-MessageFromInternalFunction. Notice that the Verbose message was not printed. That is because the parameter –Verbose was not passed to the function AND the preference variable $VerbosePreference is set to the default of SilentlyContinue. For information about preference variables see the help with
If we pass in –Verbose now we should see the verbose output:
Now, what do you think will happen if we call Get-Message with parameter –UseInternal and –Verbose? Have a think about it, because it surprised me.
Notice that we did not specify CmdletBinding in our inner function, but the verbose messages were printed anyway.
So, back to our clean code idea earlier, does that mean that a PowerShell module designer can write all public functions with CmdletBinding and not have to bother with internal functions? Maybe. However there’s some subtle things I want to show you that make life a bit easier if you put CmdletBinding in all your functions.
Let’s change the code in our inner function and add CmdletBinding. If we call Get-Message with the same parameters as before it behaves the same way.
What if you wanted to call your Get-Message function and see your verbose messages, but you want to suppress verbose messages in your inner (or internal) functions? If you don’t specify CmdletBinding in your internal functions you can’t do it. Consider this code, especially lines 2 and 14:
If we call Get-Message –UseInternal –Verbose now:
We see our verbose message from our public functions, but we have suppressed verbose messages in our inner functions. Might be useful, might not be useful, but good to know.
We need to be aware of the behaviour of preference variables and that users of your modules may have it set to non-defaults. The default for –Verbose is SilentlyContinue which means don’t print any verbose messages unless the Verbose parameter is set. What if I had run the above function with $VerbosePreference = ‘Continue’? The verbose messages get printed without having to use –Verbose as a parameter.
Notice that the -Verbose:$False parameter is respected even when $VerbosePreference is set to ‘Continue’.
If we now call Get-Message –UseInternal
Verbose messages in the outer function are displayed without specifying –Verbose as a parameter, but the verbose messages in the inner function are suppressed.
Use CmdletBinding in all your public and internal functions.
Write clean code.