Chances are that you’ve already seen and hopefully read the How Do I Learn PowerShell? sticky thread. Maybe you’ve also read What Do You Need To Know To Be An Expert In PowerShell. If you haven’t, I encourage you to read both as they have a lot of great information.

What happens in the in-between as you start writing code? You may see something online that does mostly what you want, but it’s terribly written, possibly over engineered, and hard to follow. How does that help anyone?

In the years I’ve been using PowerShell, I wouldn’t consider myself a beginner or an expert, but I’ve seen a LOT of code. Whether it was to find information online, creating my own code/modules, assisting here on SpiceWorks, or updating someone else’s code. This how-to is about writing scripts that can be easily understood by anyone. It’s a set of “best practices” that I’ve come up with. It’s not an all encompassing list, and I’d say some might argue some of the points or suggest additions. If you’d like to add something, speak up!

That said, please keep in mind that it’s not always the easiest thing to do every one of these things all the time. Please use this list as something to strive for. If you can achieve it, great, your code will be that much better. If not, maybe next time.

‘’[Anyone] can write code that a computer can understand. Good programmers write code that humans can understand.‘’ - Martin Fowler

With the exception of the first 3, there is no order/preference/recommendation on which is more important than the other. The first 3 should be priority though. The list is here at the top so it’s easy to read and for the TL;DR people. However each item has further details, clarifications, and examples below and definitely worth the read (in my biased opinion). Please note that the examples provided may not appear correctly due to the narrow width of how-tos.

  1. Install and use VS Code
  2. Install PowerShell 7
  3. Use a standard formatting style
  • Dry (Don’t Repeat Yourself)
  • Don’t use backticks aka ` as a line continuation
  • Use a line continuator instead of really long lines that need to be scrolled
  • Don’t assign variables to variables to variables
  • Don’t assign variables just to use them once, one line below
  • Don’t use aliases
  • Don’t use +=
  • Don’t concatenate strings with the + operator
  • Don’t append to your csv one line at a time
  • When comparing to $null, ensure $null is on the left side of the comparison operator
  • Use Try/Catch/Finally blocks
  • Use Transcripts
  • Use PascalCase with meaningful names
  • Filter left, format right
  • Format-* Cmdlets break an object
  • Use WhatIf (when supported)
  • Don’t specify -Properties * on ActiveDirectory Cmdlets, only specify the ones you need
  • Don’t leave code undocumented
  • Use git to create local repositories (and optionally remote)
  • Use Pester to test your code and implement TDD (Test Driven Development)
  • Read the documentation (Google Cmdlet, Get-Help, Get-Command, Get-Member, GetType(), F1 on PowerShell 7, Ctrl + Space)
  • If you migrate scripts, test them
  • PowerShell is not designed for making GUI applications
  • Use Parameter and Validate Sets (where applicable)
  • Don’t use Get-WMIObject, use Get-CimInstance

1. Install and use VS Code

Visual Studio Code is ‘‘A standalone source code editor that runs on Windows, macOS, and Linux.’’, not to be confused with Visual Studio ‘‘The best comprehensive IDE for .NET and C++ developers on Windows.’’ VS Code can be extended with a multitude of plugins, including PowerShell. VS Code is a feature rich editor. Here’s a great introduction to VS Code with some of the major features: PowerShell in Visual Studio. I remember switching from ISE to VS Code a number of years back and it was daunting at first, but I just kept using it and not ISE. Now, I avoid ISE like the plague. Every time I use it, I’m reminded why I switched. If that’s you, stick with it. You won’t be disappointed.

2. Install PowerShell 7

PowerShell Core was first announced in 2016 and Generally available in 2018. PowerShell 7 which is the successor of PowerShell Core 6 was generally available in 2020. The current LTS version of PowerShell Core is 7.2. It can be installed side by side with your existing PowerShell 5.1. One of the main reasons to install 7.2 is that it can now be updated through WSUS/Windows Update. Even if you don’t want to utilize any of the other features it provides, at least you can rest assured that it’s updated regularly. Install it and start using it, test your scripts and ensure that they work. Unfortunately, there are some modules that are dependent on Windows PowerShell 5.1, but it’s pretty easy to isolate them and keep them running with powershell.exe. I use PowerShell 7 exclusively on my desktop and utility server that runs all of the PowerShell scripts I’ve created. Since I also write modules that need to support both 5.1 and 7.0+, I don’t typically get to take advantage of most of the new features such as ForEach-Object -Parallel, ternary operator, or null coalescing, but I DO use PSReadline’s predictions in ListView mode. Try it, it will change your life. I still cringe every time I have to type “powershell” instead of “pwsh”.

3. Use a standard formatting style

Using VS Code will assist with this as it will force indentation, place opening and closing brackets and parenthesis, and so on. Generally speaking, there’s two schools of thought on opening and closing bracket/parenthesis placement. You’re either in the the everything on a new line or opening on same line and closing parallel with opening command.

function Get-Something
{
  # Does Something
}
else
{
  # Does Something Else
}
function Get-Something {
  # Does Something
} else {
  # Does Something Else
}

The latter being the default in VS Code, but it does allow for changing. My preference is the default, yours may be neither. Find something that is easy to read and run with it. Nobody can read a “one-liner” and it’s very difficult to read improperly indented code as it matters where your brackets are placed and the next line of code. You may want to execute something within a foreach, but you put it outside of the scope so it either never runs, or just once. Or maybe nested foreach within a foreach!

Which is easier to read? You decide…

$users = Get-ADUser -Filter {Enabled -eq 'True'} -properties manager,cn

foreach ($user in $users){
    $usermanager = $user.manager
if (!$usermanager)
{
Write-Output $user.cn
}
}
$Users = Get-ADUser -Filter {Enabled -eq 'True'} -Properties manager,cn

foreach ($User in $Users) {
    if (-not $User.Manager) {
        Write-Information -MessageData $User.cn -InformationAction Continue
    }
}

If you’ve made it this far, congratulations and thank you! I never expected this how-to to be as long as it is. There’s a lot of room for improvement and greater detail/explanations. Without writing a book, hopefully there’s enough information here that can be used to further educate yourself by searching on-line. If you have any comments, questions, additions, deletions, or suggestions feel free to leave a comment.

21 Spice ups

This is great! Thanks for this information. I learned something.

Awesome! Just the kind of info I need! Starting a project to automate parts of an workflow at a customer. I have a little programming experience, but not much PowerShell. This is valuable information, and I’ll be looking back to this as a reference for sure.

Thanks for this!! Very helpful, monuments will be erected in your honour

This is good work.
I take one exception: I help our team manage certain data which comes from one forest and gets moved to another forest.
The users are given specific requirements WRT filename formatting but they don’t pay attention and do it right.
Thus, I build code to determine who the file came from and re-name each file as I move it from a source hierarchy into its appropriate subdirectory.

The required file name is constructed using both variables and string characters.

So, for example, I might rename a file:
$newName = $yr+““+$mth+””+$Sam+“_”+$PKI_Num$ext

The only way to avoid this would be to capture the data, for instance, Year, and then reassign it to include “_” ← either do it once per variable or all at once as shown above.

If someone knows a better way, I’m all ears.