Skip to content

PowerShell Best Practices To Follow When Coding

It is important to follow best practices when coding in PowerShell to ensure that your codes are efficient, maintainable, and secure.


Specify The Variable Types Explicitly

🚫 Don't do this

$Var = 5

✅ Do this instead

[System.Int32]$Var = 5


Use Full Type Names Instead of Type Accelerators

🚫 Don't do this

[String]$Var = 'Hello'

✅ Do this instead

[System.String]$Var = 'Hello'


Use Single Quotes Instead of Double Quotes Unless Absolutely Necessary

🚫 Don't do this

$Var = "Hello"

✅ Do this instead

$Var = 'Hello'

This is because double quotes allow for string interpolation, which can be a security risk if the string is not sanitized properly and also slightly slower than single quotes.


Use Full Cmdlet Names Instead of Aliases

🚫 Don't do this

Gci
cls

✅ Do this instead

Get-ChildItem
Clear-Host


Use Pascal Casing for Everything

🚫 Don't do this

$myvariable
get-childitem
new-item

🚫 or this (camelCase)

$myVariable
get-ChildItem
new-Item

✅ Do this instead

$MyVariable
Get-ChildItem
New-Item


Use Regions to Organize Your Code

✅ Using regions like this allows you to collapse and expand sections of your code for better readability.

#Region Functions
function Get-MyFunction1 {
    # Function code here
}
function Get-MyFunction2 {
    # Function code here
}
function Get-MyFunction3 {
    # Function code here
}
#EndRegion


Use Visual Studio Code PowerShell Extension For Automatic Best Practice Formatting

You can access the settings page of PowerShell extension in VS Code and enable options that automatically apply some of the aforementioned best practices when you format your code with (CTRL + Shift + F) shortcut.


Refrain From Defining and Using Global Variables as Much as Possible

Global variables are not recommended in general because of security implications. They can be overwritten by the user on console as well.

If you need to define global variables, make sure you set them as constants or read-only so that they cannot be overwritten once they are defined.


Use C# If You Need Custom Global Classes

If you need custom types in PowerShell and want them to be globally available to your module, It's recommended to use C# and define custom classes with specific and unique Namespace and Class names so that there won't be any possible conflicts with other classes/types that belong to 3rd party modules.


How Would You Make PowerShell Classes Globally Available?

Even though it's not recommended, here is how you can make custom classes globally available in PowerShell. Classes will be available process-wide and therefore also in other runspaces, defining them with the [NoRunspaceAffinity()] attribute.


[NoRunspaceAffinity()]
Class Items : System.Management.Automation.IValidateSetValuesGenerator {
    [System.String[]] GetValidValues() {
        $Items = ('Item1', 'Item2', 'Item3')
        return [System.String[]]$Items
    }
}

[NoRunspaceAffinity()]
Class BasePolicyNames : System.Management.Automation.IValidateSetValuesGenerator {
    [System.String[]] GetValidValues() {

        [System.String[]]$BasePolicyNames = foreach ($Policy in (&'C:\Windows\System32\CiTool.exe' -lp -json | ConvertFrom-Json).Policies) {
            if ($Policy.IsSystemPolicy -ne 'True') {
                if ($Policy.PolicyID -eq $Policy.BasePolicyID) {
                    $Policy.FriendlyName
                }
            }
        }
        return $BasePolicyNames
    }
}

# Define the types to export with type accelerators.
[System.Reflection.TypeInfo[]]$ExportableTypes = @(
    [Items]
    [BasePolicyNames]
)

# Get the non-public TypeAccelerators class for defining new accelerators.
[System.Reflection.TypeInfo]$TypeAcceleratorsClass = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators')

# Add type accelerators for every exportable type.
$ExistingTypeAccelerators = $TypeAcceleratorsClass::Get

foreach ($Type in $ExportableTypes) {

    # !! $TypeAcceleratorsClass::Add() quietly ignores attempts to redefine existing
    # !! accelerators with different target types, so we check explicitly.
    $Existing = $ExistingTypeAccelerators[$Type.FullName]

    if (($null -ne $Existing) -and ($Existing -ne $Type)) {
        throw "Unable to register type accelerator [$($Type.FullName)], because it is already defined with a different type ([$Existing])."
    }
    $TypeAcceleratorsClass::Add($Type.FullName, $Type)
}


More Resources From Microsoft That You Should Check Out