My Powershell tips and tricks
Recently I had an interesting dicussion on Powershell and how one chould structure script and functions. As with coding in general there are multiple ways on accomplishing your goals and there's lots of best practices and techniques. In addition these change over time, both because smart people find better and more effective ways to accomplish things, and because tools and languages evolve and become better.
I thought I would write up a post on how I think around things in Powershell, if nothing just for my own reference. If it can help others, great, but please keep in mind that there's lots of available content on best practices from people awarded and recognized for their effort in evangelising Powershell so use my post as a part of your exploration in Powershell and not the definitive truth!
Here's a short list of resources you can use if you're interested in learning Powershell:
- https://www.lucd.info/ (Luc has lot's of content, a lot of it focused on VMware, but there's so much to learn from Luc even if you're not into VMware)
In this post I will not write much on coding techniques, i.e. how and when to use Try/catch etc, but focus on the things around. We will cover a few tips on why you should write functions, create modules and what I use to put in my help text. The following is presented with no particular order.
Let's dive in!
Either if you're new to Powershell or have strong knowledge of it you'll always need some help or information about the stuff you are going to use. There are three (four) commands that will help you:
- This command can be run against a cmdlet/function or a powershell language capability and your console will display some information about it. You can add in i.e -Examples to the command when you're running it against a cmdlet and you'll (hopefully) get some examples on how you can use said cmdlet.
- Be sure to run Update-Help now and then to download updated help text for your cmdlets
- This is a command that you run on a Powershell module to get a list of the available commands in that module. Great if you're searching for a specific thing within the module
- This can be run on a Powershell object to learn what methods and properties that are available on the object.
Writing a solid and descriptive help text is important, that being either it's a small function or a large script. I try to standardize on the "comment based help syntax"
The following is always part of the help text:
- A short description of what the script/function does
- A more detailed description. I try to include a line or two about what is required, maybe if there are some short comings or other things that the user needs to be aware of
- The notes part is sort of a "metadata" section. I add in the following:
- Revised (revision date)
- Changelog (short description)
- The notes part is sort of a "metadata" section. I add in the following:
- I include a parameter section on each input parameter
- In functions I include some examples on the usage
The nice thing about the syntax is that the user can run the Get-Help cmdlet on the script or function and get the information right in their console.
Another neat thing I've done lately is to use the information in the help section to build some documentation around the scripts/functions. There's lots of resources on this, check out psDoc as an example.
I also try to comment my code. Especially the lines or the parts where I do some neat and complicated stuff. Or places where I do stuff that doesn't (at least to me) explain it self. However, don't go commenting every single line.
Commenting is important as you can go back and remember why do did that and this. And for others reading your code it's key to get an understanding on what and how you were thinking.
Remember that comments does not replace the need for a help text section and/or other documentation.
Basically don't use them (at least not until you know what you're doing..). Yes it's less typing to write "where" than "Where-Object", and in that specific case most users will probably understand what you're doing, but consider "gci". Although this is a fairly common cmdlet it is far from understandable to a user new to Powershell or not used to work with the file system. Please use the full cmdlet name (that would be Get-ChildItem in this case). With tab completion the typing "overhead" shouldn't be huge.
Over the years this is probably the part that have changed the most in my scripting (besides becoming better on writing help). Nowadays I usually structure my scripts like this (top to bottom):
- Help text
- Input parameters
- Functions (I usually put these in modules, more on this later on)
- Script variables (which is not created by the running code)
- The script / code to be run
I try to use consistent versioning in my code. This has changed over the years, and I admit it can be difficult to keep the consistency. I follow the Semantic versioning principals with Major, minor and patch changes and the syntax <Major>.<Minor>.<Patch>. The major part is easy, I bump this when there's breaking / not backwards compatible changes, but what is a minor change and what is a patch? Normally I'll bump the minor version when I make additions to the functionality and the patch version if I only do minor stuff that's not adding or altering stuff, or I do a bugfix.
The bump to 1.0.0 comes when the script "is ready for production" and/or when I pass it on to others to use.
Of course, there's little point in versioning your code if you're not tracking and controlling the changes. This is maybe one of the most crucial things to start doing if you're not already into it.
Use version control! Learn git!
It's not that difficult. And when you've learnt git, push your code to a Git repository. That way you can keep track of your code, and if you're using public (or company internal) repositories (and you should if you can) other people can benefit from your code.
Start to write functions. Break up your code into small functions. I tend to do the same stuff in lots of scripts. This is perfect for pulling out to a function that can be reused multiple places in a script or in multiple scripts. A function should preferably do only a few things, it's better to write small and more functions than large and a few. When you're creating functions try to put them in a Powershell Module. Preferably in a module that do "similar" stuff.
Be sure to follow the naming conventions for functions. Use the Approved verbs, i.e. Get, New, Add, Set etc. For the noun be sure to use a descriptive name and be aware of possible namespace problems. I tend to prefix my functions in a way that they are specific and hopefully won't come in conflict with existing or future functions.
While you're at it learning about functions, you might as well learn to create Advanced functions. It's actually really easy to create Advanced functions. You just put [cmdletbinding()] before your input parameter block and you're done. Well this is not entirely true, a true/fully advanced function needs a bit more, but with the cmdletbinding() in the function you get a lot more advanced function than without.
With advanced functions you can get access to the common Powershell parameters like -Verbose etc.
Let's keep being advanced and talk about Advanced parameters. This is something that can really help you create great functions. In an advanced parameter you can specify the expected type of parameter (i.e. string, int etc), the default value, accepted values etc.
I've built functions that essentially is just a wrapper of a vendor function where I've only included my own accepted values for an input parameter. By doing this I can have tab completion on i.e. server names etc. This saves me from some typing and help reduce errors (and the need for error handling).
Powershell modules are easier to create than you'd think. In essence a module can be as simple as a file containing a few functions saved with the file extension .psm1
This module can be imported to your host and voila you have your functions available.
An example on creating a module can be found here. This is also a nice resource for learning how you can create Powershell modules as a wrapper for an API. I find that consuming an API (interactvely) from Powershell functions is the easiest way (in a Windows env).
Lately I've been building more advanced modules. I've used this article as a learning resource. What this is about is to split your functions out in separate ps1 files and then import them in your module (psm1) file so they become a part of your module. This makes it easier to maintain and test your code amongst other things.
In that blog post there's also examples on how you do some CI/CD stuff which I've also started doing, but if you are new to module making I would suggest to wait a bit with that. My latest PS module adventures lead me to create this which is an "advanced module" with a CD pipeline that runs som (very basic) tests. The module is also published to the Powershell Gallery for easy install/update.
Powershell is a powerful (pun intended) tool as you most certainly know. It has grown tremendously over the years and has also been converted to a cross-platform tool that you can use on both Windows, Linux and Mac. It's a must have in my day-to-day tasks and what's great is that there's always something new to learn and develop. The broad Powershell community is key in this, showing of their skills and techniques.
Hopefully I have given you some valuable input on how you can develop your Powershell skills. If you have any questions or comments, please feel free to reach out to me.
Thanks for reading!