PowerShell – Best Practices – Part 1

PowerShell is a very flexible language with many ways to solve a problem, and many ways to write the same code. To ensure readability, consistency, and reuseability, it’s very important to always adhere to community accepted best practices. Not everyone will agree with everything, but the majority will agree that these practices will improve your code by leaps and bounds, and make it easier for other people to both understand and use. We will split up this into a series of blog post, so we have room for showing examples along the way.

Note that these practices only apply when authoring scripts. What happens in the command line, stays in the command line.. So without further ado, here’s some best practices you should follow when writing PowerShell code.

Use PSScriptAnalyzer

When authoring scripts, even if you know all the best practices, accidents can happen. To ensure consistency, and that your code is automatically checked against many best practice rules, you should be leveraging the power of PSScriptAnalyzer.

You can run it as a standalone module:

PSScriptAnalyzer module

But it’s true power shines when developing in Visual Studio Code, where it’s built in. Here, any error or deviation is detected on the fly.

PSScriptAnalyzer in VSCode

Don’t use aliases

Using aliases instead of full command names, makes the code harder to read and understand for other people reviewing your code. This also goes for shortening parameter names. Always write out the full name of commands and parameters. As of PowerShell Core and onward, you’ll also notice many aliases have been removed, as they could collide with native tools on Linus such as ls, cat etc.

# Bad practice:
gsv | ? Name -like "A*" | select -prop Name, DisplayName

# Best practice:
Get-Service | Where-Object Name -like "A*" | Select-Object -Property Name, DisplayName

Use correct capitalization

To make sure your code is uniform, and easily readable, you should always use proper casing, even though PowerShell by default is case-insensetive. Some of these guidelines comes from the fact that PowerShell is based on C# which is a .NET language.

Keywords and operators

All PowerShell keywords, and operator names should be written in lowercase.

# Bad practice:
Function Get-Something {
    $Numbers = 1..10
    Foreach ($Number In $Numbers) {
        If ($Number -EQ 3) {
            Try {
                $Number / 0
            }
            Catch {
                Write-Error -Message "oh my.. didn't see that coming"
            }
        } Else {
            Write-Output $Number
        }
    }
}

# Best practice:
function Get-Something {
    $Numbers = 1..10
    foreach ($Number in $Numbers) {
        if ($Number -eq 3) {
            try {
                $Number / 0
            }
            catch {
                Write-Error -Message "oh my.. didn't see that coming"
            }
        } else {
            Write-Output $Number
        }
    }
}
Public identifiers: functions, cmdlets, parameters, classes, enums, and variables

All public identifiers like functions, cmdlets, parameters, classes, enums, and variables should be written using PascalCase. That means uppercase for the first letter in every word.

# Bad practice:
function get-mynumber {
    $mynumbers = 1..10
    write-output $mynumbers
}

get-mynumber | sort-object { get-random }

# Best practice:
function Get-MyNumber {
    $MyNumbers = 1..10
    Write-Output $MyNumbers
}

Get-MyNumber | Sort-Object { Get-Random }
Private identifiers: variables

If adhering to the .NET standard, one should use camelCase for private variables. By that we mean variables that exist only internally to a function or a class. But the reality is that the PowerShell community feel that it’s okay to use PascalCase for all variables. CamelCase use a capital first letter in every word after the first one, the first word has no capital letters.

# Note how $firstName, $lastName, and $name is in camelCase, while the public ones are in PascalCase
class Person {
    hidden [string]$firstName
    hidden [string]$lastName
    [int]$Age

    Person ([string]$firstName, [string]$lastName, [int]$age){
        $this.firstName = $firstName
        $this.lastName = $lastName
        $this.Age = $age
    }

    [string] Whoami(){
        return $this.firstName + " " + $this.lastName
    }
}

$Me = [Person]::new('Martin', 'Norlunn', 100)

function Get-PersonName ([Person]$Person)
{
    $name = "My name is $($Person.Whoami()), I am $($Person.Age) years old."
    Write-Output $name
}

Get-PersonName -Person $Me

Use full parameter names

When calling a function or cmdlet, always specify the full parameter names. Don’t shorten them, and don’t use positional parameters.

function Get-RandomNumber {
    param ([int]$Min, [int]$Max)
    $MyNumber = $Min..$Max | Get-Random
    Write-Output $MyNumber
}

# Bad practice:
Get-RandomNumber 0 10
Get-RandomNumber -Mi 0 -Ma 10
$Sb = New-Object System.Text.StringBuilder
Get-ChildItem D:\

# Best practice:
Get-RandomNumber -Min 0 -Max 10
$Sb = New-Object -TypeName System.Text.StringBuilder
Get-ChildItem -Path D:\

Use standard parameter names

Wherever possible, use standard parameter names, instead of inventing your own. By standard I mean those that are present in the built-in cmdlets. This makes your functions much easier to use, because these are the names users are accustomed to. Also, this makes it easier to accept pipeline input by parameter name to/from your function.

# Bad practice:
function Get-PSVersion {
    param (
        [string]$Server,
        [string]$File
    )
    #...
}

# Best practice:
function Get-PSVersion {
    param (
        [string]$ComputerName,
        [string]$Path
    )
    #...
}

To easily identify if you’re using a standard parameter name, you can use the following method:

Get-Command -ParameterName <YourParameterName> -ErrorAction SilentlyContinue | Measure-Object
Check how many cmdlets contains a certain parameter

Naming functions and cmdlets correctly

When naming a function, or a cmdlet, you should always conform to the established practices by the creators and maintainers of PowerShell. This makes the names easier to read, and understand.

Such a name should always follow the following convention:
<Verb>-[<Prefix>]<Noun>
Note that the prefix is optional, but encouraged when creating custom functions, so that their name doesn’t collide with other peoples code/functions, or the built-in ones.

It’s also very important to choose good, descriptive names, so that one can easily understand what that piece of code does. This does not only apply to functions and cmdlets, but to everything really.

Approved verbs

You should only use a verb that has been pre-approved by the PowerShell team. You can view these verbs like this:

Get-Verb
# Bad practice:
function Fetch-MyData {
    # ...
}

# Best practice:
function Get-MyData {
    # ...
}
Singular nouns

You should always use the singular form of the noun, and never the plural form.

# Bad practice:
function Invoke-MyTests {
    # ...
}

# Best practice:
function Invoke-MyTest {
    # ...
}

Conclusion

In this post you have been introduced to some of the most important best practices in order to make your code consitent, readable, reusable, and self-documenting. In the next blog post in this series, we’ll take a look at more PowerShell best practices you should be following.

Leave a Reply