PowerShell Polish, Part 1: Perfecting Your Scripts
Here's how to take your PowerShell script and turn it into the best version of itself.
Written .
Introduction
If you know what PowerShell is, then there's a good chance that you're a Windows power user or a sysadmin of some sort. Moreover, if you're working with PowerShell on a daily or weekly basis, you've probably written a really nice script. Whether you made it to put a new tool in your virtual tool belt, to automate some tedious task, or just for a programming challenge; congratulations on writing a cool script.
Now, you might be tempted to toss it deep inside your Documents folder and call it done. However, if your code is like anything ever written, you'll inevitably be faced with two hurdles: how to share your file, and how to manage version control. With a little work up front, you can transform your script from something static and unchanging into something that you'll want to share with the entire world.
In the PowerShell world, there are two ways to package something for mass consumption: scripts and modules. While modules are a better way to package and share multiple files, we'll save those for another article. Today, we're going to focus on scripts, single files that contain all of your code.
While you can read the article by itself, you're free to use this as a guide to update one of your own creations. If you have a script of yours in mind, go ahead and open it up in your favorite code editor. (If you're looking for one, I recommend Visual Studio Code.)
Our Example Script
For the purposes of this article, I'm going to be working on this little script. It's nothing more than a quick game of guess the number, a rite of passage for any computer science student, somewhere between saying Hello World!
and hunting a wumpus. To illustrate some concepts as we go along, I've tried to violate some best practices and make some garish styling decisions that I otherwise would not do.
Git Good: Versioning and Source Control
If you're following along at home, I'm going to have you make a lot of changes to your script as we go along. Thus, I'm bumping this up to step number one. You'll see why.
Eventually, you (or one of your users) will find a bug in your script, and you'll have to fix it. The biggest problem with managing a script is keeping track of your changes. You might be thinking to yourself, Why would I ever need an old version of my script?
Well, let's assume that, over time, you make a version 1.1, then a version 1.2, and then a version 1.3 that has a bug. How do you know when the bug was introduced? Could it be something you changed recently, or could it be something you changed last week?
This is where a version control system comes into play. The most popular one right now is called Git. You can quickly install it on your system:
- Windows users should download and install Git for Windows. (This is what I use at work.)
- macOS users have a few options, including downloading Xcode from the Mac App Store (which is a lot, unless you install only the command-line tools, but it's a pretty nice IDE, too).
- Linux users might already have it installed, but if not, something like
sudo apt install git
might do the trick.
Git only works on entire folders, so create a new folder and put your script inside it. Once you've done that, we're going to initiate a new repository, add your file to Git's tracking, and commit it to a version 1.3 tag.
Now that we have our initial version safely tucked away in Git, we can work to our heart's content!
Further Reading
I don't have time to go into Git in-depth here. One could write an entire book explaining how Git works, but if you're looking for a good tutorial, I can personally recommend the Microsoft Developer Tools Challenge.
Styling Your Code
Our first goal is to clean up our spaghetti code and make it look pretty. We won't be changing any functionality, but by making it look nicer, it will be easier for you (or others) to read and maintain.
Everyone has or develops their own coding style. As someone who can code in C, C++, and Java, I tend to follow some parts of the Standard C style whenever it makes sense and whenever it makes the code the easiest to read. Some tips I recommend:
- Comment your code wherever appropriate! (If this is something that you're going to share with the world, consider writing your comments in English, as it's the lingua franca of the programming world.)
- Give your variables useful names. If you're not declaring a loop or a simple three-line function, then
$x
is probably a terrible name. - Stick in a line break if it makes things look cleaner.
- Indent lines when opening a new block, and
- Put curly braces on their own line.
- Put spaces around operators.
If you're contributing to someone else's project, look for a CONTRIBUTING
file in the source code. That will tell you what coding style the project expects from all contributors. Please follow that, instead of what you and I think best.
Regardless, no matter what, your PowerShell cmdlets and statements should be properly capitalized, matching however you see them in official documentation. It also goes without saying that you should check your spelling and grammar.
Now, let's commit our changes.
PSScriptAnalyzer; Or, How to Conform to PowerShell's Best Practices
Microsoft has developed an automated tool called PSScriptAnalyzer to scan your PowerShell files for common mistakes, bad practices, and other things you should avoid.
As you can see, it complained that we used an alias in our code. While it's much easier to type out interactively (and something I do frequently), you should always avoid that when writing scripts.
Likewise, we should always spell out our parameters for clarity, and provide default and implied ones for clarity, too. For example, in our call to Get-Random
, we'd used -min
when we should have used -Minimum
, and we can improve readability by adding in the implied -Maximum
.
Remember, you might be a seasoned PowerShell expert, but that doesn't mean the next person reading your code will be.
Adding Metadata
This is where we start to move into the more PowerShell-specific things you can do to make your script a little easier to work on. PowerShell has a comment block known as PSScriptInfo
where you can specify metadata — that is, information about your script.
Rather than type mere comments into your editor, you can and should define your metadata in a standard way (especially if you have dreams of this script being in the PowerShell Gallery someday). You can use the Update-ScriptFileInfo
cmdlet to add a metadata block to your script, or you can edit most of it in your text editor:
Let's go ahead and fill out some of these fields. You can remove any lines that you're not going to use or that don't apply.
Now, you have metadata defined in the proper manner, via two comment blocks. This data can be read by Test-ScriptFileInfo
, online galleries, or by any eyeballs that happen to perusing your source code. Be sure to update the version number and release notes whenever you decide it's time.
Get Help for Get-Help
How often have you looked at someone else's script or code and wondered how to use it? Perhaps that won't be a problem with our script, but it's like I say at work: always document your work.
We can use Get-Help to learn more about this script:
If you haven't already, open up your PowerShell script and take a look at the new blocks in your script. The second one is where we'll be focusing our attention. What you're looking at is PowerShell's comment-based help system. This block consists of several paragraphs of plain text occasionally marked up with headings that begin with a dot. (Though PowerShell was developed by Microsoft, this syntax looks a lot like troff
commands from the UNIX world.)
Let's go ahead and use some of the most common keywords to help describe our script. At the very least, .DESCRIPTION
was filled in for you, but we can add and edit others. Note that we won't be using all of these.
.SYNOPSIS
- A one-line summary of what your script does.
.DESCRIPTION
- A longer summary of what your code does. You can write multiple paragraphs.
.PARAMETER InputObject
- If your script accepts command-line parameters, you should explain what they do. If we had a parameter named
, we would proceed to describe it. You can specify this as many times as you need.-InputObject .INPUTS
and.OUTPUTS
- If your script accepts pipeline input or generates pipeline output, provide the data type and a description.
.EXAMPLE
- This is the only structured help item. On the first line after this, show the command line. Then, after a blank line, describe what that will do. (Ironically, I will show the example later.) You can specify this as many times as you want.
.NOTES
- Is there anything else your users should know? For example, do they need to run a command before attempting to run your script? Does it only support certain data types?
.LINK
- You can use this (multiple times, if you'd like) to recommend users read other help topics. You can name other cmdlets, conceptual help topics, or specify a URL. If you specify at least one HTTP or HTTPS URL, the first one will be used for Get-Help -Online.
Edit the second comment block to look something like this:
Let's try that command again!
Beautiful! If you don't see everything you typed, that's normal; try running Get-Help -Full to learn everything. git add, git commit, and let's wrap this up!
Going the Distance: Signing Your Code
We've worked hard on our script, and now we've got something that's ready to share with the world. Authors put their name on the cover, artists sign or tag their work, and PowerShell scripters also have the option to sign their code. While this is optional, and you can run unsigned code perfectly well, there are many benefits to slapping a digital signature on your script:
- Windows PowerShell or Windows SmartScreen may show a warning when you try to run an unsigned script.
- In some corporate environments, an administrator may have used InTune or Group Policy to go one step further, and modify Windows PowerShell's execution policy such that unsigned scripts will not run.
- If anyone modifies your script, the signature will be invalid, which Windows will treat as worse than unsigned.
- You get to look professional (and enjoy a small ego boost) by seeing your name attached to your script. This goes double if you're a business or if you code for a living.
Where to Find Magic Bits
Now, we have a problem. Where does someone get a code signing certificate? While the ISRG and Let's Encrypt have effectively democratized server certificates by making them available for free, they have no plans to do the same for other types — and that includes code signing certificates. (I'll complain about S/MIME certificates in another blog post.)
You can use a self-signed certificate for small deployments. You can share your certificate with your family and friends, and build your own little web of trust, but that is not scalable. If someone asked me to install a certificate in my root store to run their script, I would do one of two things: either click through the unsigned code warnings, or simply not run their script.
Businesses might run their own internal certificate authority; Active Directory Certificate Services is fairly popular. If your company has its own private CA, you could ask your IT department to take a leap of faith and issue you a code signing certificate. Having both run the CA and used it to sign code, that's a wonderful option for code that will only run on company-owned computers. For sharing with a wider audience, though, we're back to square one.
You can detach-sign your code with PGP keys or distribute hashes, and hope your users verify everything before running it. There's also some chatter about doing something with blockchains, but it's going to be years before this can even be a contender. Ultimately, if you want to sign your code and sign it properly so that anyone in the world can trust your code, then you will need to bite the bullet and pay someone money for these magic bits that make operating systems happy. I was quite happy with SignMyCode.com, but you should find a vendor that's right for you.
In my case, I made an ECDSA private key, then gave my vendor a certificate signing request and a copy of my legal identification. Once they verified that I'm me, I was the proud owner of a globally-trusted code signing certificate.
How to Sign a Script; Or, How to Turn Magic Bits Into Other Magic Bits
You're going to need a Windows computer for the following step, as Microsoft doesn't make all of Microsoft.PowerShell.Security
's cmdlets — namely, Set-AuthenticodeSignature
— available on other platforms.
This little code block assumes that you have your one and only code signing certificate imported into your Personal store.
If you look at your script, you will see a long comment block at the very end. This is the digital signature and timestamp, encoded in a portable format. If you right-click your script and choose Properties, you can also see your name on the new Digital Signatures tab!
In Conclusion
Congratulations on making it this far! If you've been following along at home, you've now taken one of your own scripts and turned it into the best possible version of itself. Go ahead and share it far and wide.
You might have noticed that I didn't talk much about the PowerShell Gallery. While you can publish and install scripts from it, it really focuses on modules, which I'll be touching on in a future blog post.
Links
- Git for Windows
- https://gitforwindows.org/
- Git for macOS
- https://git-scm.com/download/mac
- Microsoft Developer Tools Challenge
- https://learn.microsoft.com/en-us/training/challenges?id=1c865402-c95c-4a52-9f34-35a1c559861c
- GNU Coding Standards
- https://gnu.org/prep/standards/standards.html#Writing-C
- PSScriptAnalyzer
- https://learn.microsoft.com/en-us/powershell/utility-modules/psscriptanalyzer/overview
- PowerShell's Comment-Based Help
- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comment_based_help