Welcome back to PowerShell for Programmers, this week I'm trying gitGist again for the code blocks. Let me know what you think about it vs the normal syntax highlighter I use 🙂
As we've seen in the other posts about functions, attributes are a really cool thing to extend the features we have available to ourselves and for our users. This post is going to deal with an attribute for the function itself as well as ones for the individual parameters. This will let us make our functions behave more like any other cmdlet, by giving us access to the common parameters that users are used to seeing.
Common parameters are available on every single cmdlet. This means that users who are going to consume your functions are used to seeing them. If you're just coming into PowerShell and unfamiliar with them, take a look here. We can get access to these on our code by adding the attribute [cmdletbinding()] at the top of our functions of scripts (before the param keyword).
If we take a look at the syntax or just use the intellisense we can see they have been added:
Note that our custom parameters will always come before the common ones, so we know that once we see -verbose there won't be any more stuff we created.
While they are there, they might not actually do anything yet. The common parameters are all about overriding some default behaviors of powershell. For example, -verbose or -debug will just make those streams visible for that command. If we have write-verbose lines in our function then -verbose will show those lines:
Error action is going to work for us for everything in our code, including custom error messages:
It can be really nice to get this kind of functionality into your tool sets before you distribute them to others, whether they are on your team, in your company or a broader online audience.
The [cmdletbinding()] attribute can also enable a bunch of different flags inside of it. Two really common ones are:
- "SupportsShouldProcess" which gives us the Risk Mitigation parameters -whatif and -confirm. These are similar to the common parameters, but they aren't on everything, they are just on a lot of stuff.
- "ConfirmImpact" which tells PowerShell how dangerous your code is and whether or not it should default a confirmation prompt, or just show it when using -confirm
To leverage these, we are also going to use a built in variable called $PSCmdlet, which contains a bunch of useful metadata and methods for doing interesting things to our functions that we want to be cmdlets.
I've gone and added risk mitigation, but as you can see it isn't doing anything effective yet:
What we can do is wrap our "action code" in an if statement. The action code is whatever you want -whatif to block from happening. For example, most of your code is probably grabbing and looking at data to make decisions, and you still probably want that to happen so you can give a meaningful "whatif:" message, but instead of deleting/changing data you might want to wrap that up for the -whatif to block.
Ok, but how to I determine whether or not the if statement runs?
Great question! That's where $PScmdlet comes in. One of the methods it has is called "ShouldProcess", it returns a bool, and it has a bunch of overloads you can see on the docs page. If the user puts -whatif, ShouldProcess will return false. If they put -confirm and hit no in the box it will also return false. Additionally ShouldProcess is what generates the "Whatif:" and confirmation box messages. The most common overload is this one:
public bool ShouldProcess (string target, string action);
Let's see that in action with PowerShell:
Finally, let's flag our code as dangerous. ConfirmImpact can be "Low", "Medium", or "High".
Notice how it will pop up any time you call it, regardless of if you typed -confirm. This is actually controlled with a setting variable $ConfirmPreference if you ever want to change it. You can also override it inline with -confirm:$false
Using parameter sets can really let you extend your toolset. Think of Parameter Sets as overloads for your function. Instead of declaring them all separately like you might in C#, we have to build them all together in PowerShell. A good example might be if you had a function to do the same work on different objects, so the only part that might change is fetching the objects:
- Stop-process is a good place to see this as it only has 3 parameter sets. One that takes in process IDs, one that takes in process names and one that takes in process objects. All of these scenarios will use the same action code, but fetch the process data differently.
- If you had code for AD users and computers you could offer a -computer set that fetches a computer and a -user set that fetches a user, but then the code that acts on the data could remain the same.
To do this, we go back to our friend the [parameter()] attribute. We can specify the set we want each parameter in, and we can have it be mandatory or optional for each set we specify. If you do not specify a set, then it will be in all sets.
Notice now we can use either set, but we can't use parameters from different sets together.
But how can I make sure I only run the code I want for each set?
We will leverage $PScmdlet again, some of the meta data it holds includes which parameter set we used:
Finally, if someone tries to use our parameters positionally we might run into some issues due to the sets. We can let Power Shell know which one to use as the default with [cmdletbinding()] like this:
cmdlet MyFunction at command pipeline position 1 Supply values for the following parameters: Process:
Well that’s all for now, hopefully this helps you as you start to build out tools in PowerShell that you want to add that professional shine to!
For the main series post, check back here.
If you find this helpful don't forget to rate, comment and share 🙂