Skip to content

Latest commit

 

History

History
2286 lines (1726 loc) · 45.5 KB

STYLEGUIDELINES.md

File metadata and controls

2286 lines (1726 loc) · 45.5 KB

Jenkins Module Style Guidelines & Best Practices

In order to provide clean and consistent code, please follow the style guidelines listed below.

It is recommended to also follow the guidance from the best practices section.

Table of Contents

Style Guidelines

Markdown Files

If a paragraph includes more than one sentence, end each sentence with a newline. GitHub will still render the sentences as a single paragraph, but the readability of git diff will be greatly improved.

General

Correct File Encoding

Make sure all files are encoded using UTF-8 (not UTF-8 with BOM).

Descriptive Names

Use descriptive, clear, and full names for all variables, parameters, and functions. All names must be at least more than 2 characters. No abbreviations should be used.

Bad:

$r = Get-RdsHost

Bad:

$frtytw = 42

Bad:

function Get-Thing
{
    ...
}

Bad:

function Set-ServerName
{
    param
    (
        $mySTU
    )
    ...
}

Good:

$remoteDesktopSessionHost = Get-RemoteDesktopSessionHost

Good:

$fileCharacterLimit = 42

Good:

function Get-ArchiveFileHandle
{
    ...
}

Good:

function Set-ServerName
{
    param
    (
        [Parameter()]
        $myServerToUse
    )
    ...
}

Correct Parameter Usage in Function and Cmdlet Calls

Use named parameters for function and cmdlet calls rather than positional parameters. Named parameters help other developers who are unfamiliar with your code to better understand it.

When calling a function with many long parameters, use parameter splatting. If splatting is used, then all the parameters should be in the splat. More help on splatting can be found in the article About Splatting.

Make sure hashtable parameters are still properly formatted with multiple lines and the proper indentation.

Bad:

Not using named parameters.

Get-ChildItem C:\Documents *.md

Bad:

The call is very long and will wrap a lot in the review tool when the code is viewed by the reviewer during the review process of the PR.

$mySuperLongHashtableParameter = @{
    MySuperLongKey1 = 'MySuperLongValue1'
    MySuperLongKey2 = 'MySuperLongValue2'
}

$superLongVariableName = Get-MySuperLongVariablePlease -MySuperLongHashtableParameter  $mySuperLongHashtableParameter -MySuperLongStringParameter '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890' -Verbose

Bad:

Hashtable is not following Correct Format for Hashtables or Objects.

$superLongVariableName = Get-MySuperLongVariablePlease -MySuperLongHashtableParameter @{ MySuperLongKey1 = 'MySuperLongValue1'; MySuperLongKey2 = 'MySuperLongValue2' } -MySuperLongStringParameter '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890' -Verbose

Bad:

Hashtable is not following Correct Format for Hashtables or Objects.

$superLongVariableName = Get-MySuperLongVariablePlease -MySuperLongHashtableParameter @{ MySuperLongKey1 = 'MySuperLongValue1'; MySuperLongKey2 = 'MySuperLongValue2' } `
                                                       -MySuperLongStringParameter '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890' `
                                                       -Verbose

Bad:

Hashtable is not following Correct Format for Hashtables or Objects.

$superLongVariableName = Get-MySuperLongVariablePlease `
    -MySuperLongHashtableParameter @{ MySuperLongKey1 = 'MySuperLongValue1'; MySuperLongKey2 = 'MySuperLongValue2' } `
    -MySuperLongStringParameter '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890' `
    -Verbose

Bad:

Passing parameter (Verbose) outside of the splat.

$getMySuperLongVariablePleaseParameters = @{
    MySuperLongHashtableParameter = @{
        MySuperLongKey1 = 'MySuperLongValue1'
        MySuperLongKey2 = 'MySuperLongValue2'
    }
    MySuperLongStringParameter = '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'
}

$superLongVariableName = Get-MySuperLongVariablePlease @getMySuperLongVariablePleaseParameters -Verbose

Good:

Get-ChildItem -Path C:\Documents -Filter *.md

Good:

$superLongVariableName = Get-MyVariablePlease -MyStringParameter '123456789012349012345678901234567890' -Verbose

Good:

$superLongVariableName = Get-MyVariablePlease -MyString1 '1234567890' -MyString2 '1234567890' -MyString3 '1234567890' -Verbose

Good:

$mySuperLongHashtableParameter = @{
    MySuperLongKey1 = 'MySuperLongValue1'
    MySuperLongKey2 = 'MySuperLongValue2'
}

$superLongVariableName = Get-MySuperLongVariablePlease -MySuperLongHashtableParameter $mySuperLongHashtableParameter -Verbose

Good:

Splatting all parameters.

$getMySuperLongVariablePleaseParameters = @{
    MySuperLongHashtableParameter = @{
        MySuperLongKey1 = 'MySuperLongValue1'
        MySuperLongKey2 = 'MySuperLongValue2'
    }
    MySuperLongStringParameter = '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'
    Verbose = $true
}

$superLongVariableName = Get-MySuperLongVariablePlease @getMySuperLongVariablePleaseParameters

Good:

$superLongVariableName = Get-MySuperLongVariablePlease `
    -MySuperLongHashtableParameter @{
        MySuperLongKey1 = 'MySuperLongValue1'
        MySuperLongKey2 = 'MySuperLongValue2'
    } `
    -MySuperLongStringParameter '123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890' `
    -Verbose

Correct Format for Arrays

Arrays should be written in one of the following formats.

If an array is declared on a single line, then there should be a single space between each element in the array. If arrays written on a single line tend to be long, please consider using one of the alternative ways of writing the array.

Bad:

Array elements are not format consistently.

$array = @( 'one', `
'two', `
'three'
)

Bad:

There are no single space beetween the elements in the array.

$array = @('one','two','three')

Bad:

There are multiple array elements on the same row.

$array = @(
    'one', 'two', `
    'my long string example', `
    'three', 'four'
)

Bad:

Hashtable is not following Correct Format for Hashtables or Objects.

$array = @(
    'one',
    @{MyKey = 'MyValue'},
    'three'
)

Bad:

Hashtables are not following Correct Format for Hashtables or Objects.

$myArray = @(
    @{Key1 = Value1;Key2 = Value2},
    @{Key1 = Value1;Key2 = Value2}
)

Good:

$array = @('one', 'two', 'three')

Good:

$array = @(
    'one',
    'two',
    'three'
)

Good:

$array = @(
    'one'
    'two'
    'three'
)

Good:

$hashtable = @{
    Key = "Value"
}

$array = @( 'one', 'two', 'three', $hashtable )

Good:

$hashtable = @{
    Key = "Value"
}

$array = @(
    'one',
    'two',
    'three',
    $hashtable
)

Good:

$myArray = @(
    @{
        Key1 = Value1
        Key2 = Value2
    },
    @{
        Key1 = Value1
        Key2 = Value2
    }
)

Correct Format for Hashtables or Objects

Hashtables and Objects should be written in the following format. Each property should be on its own line indented once.

Bad:

$hashtable = @{Key1 = 'Value1';Key2 = 2;Key3 = '3'}

Bad:

$hashtable = @{ Key1 = 'Value1'
Key2 = 2
Key3 = '3' }

Good:

$hashtable = @{
    Key1 = 'Value1'
    Key2 = 2
    Key3 = '3'
}

Good:

$hashtable = @{
    Key1 = 'Value1'
    Key2 = 2
    Key3 = @{
        Key3Key1 = 'ExampleText'
        Key3Key2 = 42
    }
}

Correct use of single- and double quotes

Single quotes should always be used to delimit string literals wherever possible. Double quoted string literals may only be used when it contains ($) expressions that need to be evaluated.

Bad:

$string = "String that do not evaluate variable"

Bad:

$string = "String that evaluate variable {0}" -f $SomeObject.SomeProperty

Good:

$string = 'String that do not evaluate variable'

Good:

$string = 'String that evaluate variable {0}' -f $SomeObject.SomeProperty

Good:

$string = "String that evaluate variable $($SomeObject.SomeProperty)"

Good:

$string = 'String that evaluate variable ''{0}''' -f $SomeObject.SomeProperty

Good:

$string = "String that evaluate variable '{0}'" -f $SomeObject.SomeProperty

Correct Format for Comments

There should not be any commented-out code in checked-in files. The first letter of the comment should be capitalized.

Single line comments should be on their own line and start with a single pound-sign followed by a single space. The comment should be indented the same amount as the following line of code.

Comments that are more than one line should use the <# #> format rather than the single pound-sign. The opening and closing brackets should be on their own lines. The comment inside the brackets should be indented once more than the brackets. The brackets should be indented the same amount as the following line of code.

Formatting help-comments for functions has a few more specific rules that can be found here.

Bad:

function Get-MyVariable
{#this is a bad comment
    [CmdletBinding()]
    param ()
#this is a bad comment
    foreach ($example in $examples)
    {
        Write-Verbose -Message $example #this is a bad comment
    }
}

Bad:

function Get-MyVariable
{
    [CmdletBinding()]
    param ()

    # this is a bad comment
    # On multiple lines
    foreach ($example in $examples)
    {
        # No commented-out code!
        # Write-Verbose -Message $example
    }
}

Good:

function Get-MyVariable
{
    # This is a good comment
    [CmdletBinding()]
    param ()

    # This is a good comment
    foreach ($example in $examples)
    {
        # This is a good comment
        Write-Verbose -Message $example
    }
}

Good:

function Get-MyVariable
{
    [CmdletBinding()]
    param ()

    <#
        This is a good comment
        on multiple lines
    #>
    foreach ($example in $examples)
    {
        Write-Verbose -Message $example
    }
}

Correct Format for Keywords

PowerShell reserved Keywords should be in all lower case and should be immediately followed by a space if there is non-whitespace characters following (for example, an open brace).

Some reserved Keywords may also be followed by an open curly brace, for example the catch keyword. These keywords that are followed by a curly brace should also follow the One Newline Before Braces guideline.

The following is the current list of PowerShell reserved keywords in PowerShell 5.1:

begin, break, catch, class, continue, data, define do, dynamicparam, else,
elseif, end, enum, exit, filter, finally, for, foreach, from, function
hidden, if, in, inlinescript, param, process, return, static, switch,
throw, trap, try, until, using, var, while

This list may change in newer versions of PowerShell.

The latest list of PowerShell reserved keywords can also be found on this page.

Bad:

# Missing space after keyword and before open bracket
foreach($item in $list)

Bad:

# Capital letters in keyword
BEGIN

Bad:

# Violates 'One Newline Before Braces' guideline
begin {
    # Do some work
}

Bad:

# Capital letters in 'in' and 'foreach' keyword
ForEach ($item In $list)

Good:

foreach ($item in $list)

Good:

begin
{
    # Do some work
}

Whitespace

Indentation

For all indentation, use 4 spaces instead of tabs. There should be no tab characters in the file unless they are in a here-string.

No Trailing Whitespace After Backticks

Backticks should always be directly followed by a newline

Newline at End of File

All files must end with a newline, see StackOverflow.

Newline Character Encoding

Save newlines using CR+LF instead of CR. For interoperability reasons, we recommend that you follow these instructions when installing Git on Windows so that newlines saved to GitHub are simply CRs.

No More Than Two Consecutive Newlines

Code should not contain more than two consecutive newlines unless they are contained in a here-string.

Bad:

function Get-MyValue
{
    Write-Verbose -Message 'Getting MyValue'


    return $MyValue
}

Bad:

function Get-MyValue
{
    Write-Verbose -Message 'Getting MyValue'
    return $MyValue
}

function Write-Log
{
    Write-Verbose -Message 'Logging...'
}

Good:

function Get-MyValue
{
    Write-Verbose -Message 'Getting MyValue'
    return $MyValue
}

Good:

function Get-MyValue
{
    Write-Verbose -Message 'Getting MyValue'
    return $MyValue
}

function Write-Log
{
    Write-Verbose -Message 'Logging...'
}

One Newline Before Braces

Each curly brace should be preceded by a newline unless assigning to a variable.

Bad:

if ($booleanValue) {
    Write-Verbose -Message "Boolean is $booleanValue"
}

Good:

if ($booleanValue)
{
    Write-Verbose -Message "Boolean is $booleanValue"
}

When assigning to a variable, opening curly braces should be on the same line as the assignment operator.

Bad:

$scriptBlockVariable =
{
    Write-Verbose -Message 'Executing script block'
}

Bad:

$hashtableVariable =
@{
    Key1 = 'Value1'
    Key2 = 'Value2'
}

Good:

$scriptBlockVariable = {
    Write-Verbose -Message 'Executing script block'
}

Good:

$hashtableVariable = @{
    Key1 = 'Value1'
    Key2 = 'Value2'
}

One Newline After Opening Brace

Each opening curly brace should be followed by only one newline.

Bad:

function Get-MyValue
{

    Write-Verbose -Message 'Getting MyValue'

    return $MyValue
}

Bad:

function Get-MyValue
{ Write-Verbose -Message 'Getting MyValue'

    return $MyValue
}

Good:

function Get-MyValue
{
    Write-Verbose -Message 'Getting MyValue'
    return $MyValue
}

Two Newlines After Closing Brace

Each closing curly brace ending a function, conditional block, loop, etc. should be followed by exactly two newlines unless it is directly followed by another closing brace. If the closing brace is followed by another closing brace or continues a conditional or switch block, there should be only one newline after the closing brace.

Bad:

function Get-MyValue
{
    Write-Verbose -Message 'Getting MyValue'
    return $MyValue
} Get-MyValue

Bad:

function Get-MyValue
{ Write-Verbose -Message 'Getting MyValue'

    if ($myBoolean)
    {
        return $MyValue
    }

    else
    {
        return 0
    }

}
Get-MyValue

Good:

function Get-MyValue
{
    Write-Verbose -Message 'Getting MyValue'

    if ($myBoolean)
    {
        return $MyValue
    }
    else
    {
        return 0
    }
}

Get-MyValue

One Space Between Type and Variable Name

If you must declare a variable type, type declarations should be separated from the variable name by a single space.

Bad:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    [Int]$number = 2
}

Good:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    [Int] $number = 2
}

One Space on Either Side of Operators

There should be one blank space on either side of all operators.

Bad:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    $number=2+4-5*9/6
}

Bad:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    if ('example'-eq'example'-or'magic')
    {
        Write-Verbose -Message 'Example found.'
    }
}

Good:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    $number = 2 + 4 - 5 * 9 / 6
}

Good:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    if ('example' -eq 'example' -or 'magic')
    {
        Write-Verbose -Message 'Example found.'
    }
}

One Space Between Keyword and Parenthesis

If a keyword is followed by a parenthesis, there should be single space between the keyword and the parenthesis.

Bad:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    if('example' -eq 'example' -or 'magic')
    {
        Write-Verbose -Message 'Example found.'
    }

    foreach($example in $examples)
    {
        Write-Verbose -Message $example
    }
}

Good:

function Get-TargetResource
{
    [CmdletBinding()]
    param ()

    if ('example' -eq 'example' -or 'magic')
    {
        Write-Verbose -Message 'Example found.'
    }

    foreach ($example in $examples)
    {
        Write-Verbose -Message $example
    }
}

Functions

Function Names Use Pascal Case

Function names must use PascalCase. This means that each concatenated word is capitalized.

Bad:

function get-targetresource
{
    # ...
}

Good:

function Get-TargetResource
{
    # ...
}

Function Names Use Verb-Noun Format

All function names must follow the standard PowerShell Verb-Noun format.

Bad:

function TargetResourceGetter
{
    # ...
}

Good:

function Get-TargetResource
{
    # ...
}

Function Names Use Approved Verbs

All function names must use approved verbs.

Bad:

function Normalize-String
{
    # ...
}

Good:

function ConvertTo-NormalizedString
{
    # ...
}

Functions Have Comment-Based Help

All functions should have comment-based help with the correct syntax directly above the function. Comment-help should include at least the SYNOPSIS section and a PARAMETER section for each parameter.

Bad:

# Creates an event
function New-Event
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Message,

        [Parameter()]
        [ValidateSet('operational', 'debug', 'analytic')]
        [String]
        $Channel = 'operational'
    )
    # Implementation...
}

Good:

<#
    .SYNOPSIS
        Creates an event

    .PARAMETER Message
        Message to write

    .PARAMETER Channel
        Channel where message should be stored

    .EXAMPLE
        New-Event -Message 'Attempting to connect to server' -Channel 'debug'
#>
function New-Event
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Message,

        [Parameter()]
        [ValidateSet('operational', 'debug', 'analytic')]
        [String]
        $Channel = 'operational'
    )
    # Implementation
}

Parameter Block at Top of Function

There must be a parameter block declared for every function. The parameter block must be at the top of the function and not declared next to the function name. Functions with no parameters should still display an empty parameter block.

Bad:

function Write-Text([Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$Text)
{
    Write-Verbose -Message $Text
}

Bad:

function Write-Nothing
{
    Write-Verbose -Message 'Nothing'
}

Good:

function Write-Text
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Text
    )

    Write-Verbose -Message $Text
}

Good:

function Write-Nothing
{
    param ()

    Write-Verbose -Message 'Nothing'
}

Parameters

Correct Format for Parameter Block

  • An empty parameter block should be displayed on its own line like this: param ().
  • A non-empty parameter block should have the opening and closing parentheses on their own line.
  • All text inside the parameter block should be indented once.
  • Every parameter should include the [Parameter()] attribute, regardless of whether the attribute requires decoration or not.
  • A parameter that is mandatory should contain this decoration: [Parameter(Mandatory = $true)].
  • A parameter that is not mandatory should not contain a Mandatory decoration in the [Parameter()].

Bad:

function Write-Nothing
{
    param
    (

    )

    Write-Verbose -Message 'Nothing'
}

Bad:

function Write-Text
{
    param([Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
                    [String] $Text )

    Write-Verbose -Message $Text
}

Bad:

function Write-Text
{
    param
    (
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Text

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String]
        $PrefixText

        [Boolean]
        $AsWarning = $false
    )

    if ($AsWarning)
    {
        Write-Warning -Message "$PrefixText - $Text"
    }
    else
    {
        Write-Verbose -Message "$PrefixText - $Text"
    }
}

Good:

function Write-Nothing
{
    param ()

    Write-Verbose -Message 'Nothing'
}

Good:

function Write-Text
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Text
    )

    Write-Verbose -Message $Text
}

Good:

function Write-Text
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Text

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [String]
        $PrefixText

        [Parameter()]
        [Boolean]
        $AsWarning = $false
    )

    if ($AsWarning)
    {
        Write-Warning -Message "$PrefixText - $Text"
    }
    else
    {
        Write-Verbose -Message "$PrefixText - $Text"
    }
}

Parameter Names Use Pascal Case

All parameters must use PascalCase. This means that each concatenated word is capitalized.

Bad:

function Get-TargetResource
{
    [CmdletBinding()]
    param
    (
        $SOURCEPATH
    )
}

Bad:

function Get-TargetResource
{
    [CmdletBinding()]
    param
    (
        $sourcepath
    )
}

Good:

function Get-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        $SourcePath
    )
}

Parameters Separated by One Line

Parameters must be separated by a single, blank line.

Bad:

function New-Event
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Message,
        [ValidateSet('operational', 'debug', 'analytic')]
        [String]
        $Channel = 'operational'
    )
}

Good:

function New-Event
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Message,

        [Parameter()]
        [ValidateSet('operational', 'debug', 'analytic')]
        [String]
        $Channel = 'operational'
    )
}

Parameter Type on Line Above

The parameter type must be on its own line above the parameter name. If an attribute needs to follow the type, it should also have its own line between the parameter type and the parameter name.

Bad:

function Get-TargetResource
{
    [CmdletBinding()]
    param
    (
        [String] $SourcePath = 'c:\'
    )
}

Good:

function Get-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [String]
        $SourcePath = 'c:\'
    )
}

Good:

function Get-TargetResource
{
    [CmdletBinding()]
    param
    (
        [Parameter()]
        [PSCredential]
        [Credential()]
        $MyCredential
    )
}

Good:

function New-Event
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Message,

        [Parameter()]
        [ValidateSet('operational', 'debug', 'analytic')]
        [String]
        $Channel = 'operational'
    )
}

Parameter Attributes on Separate Lines

Parameter attributes should each have their own line. All attributes should go above the parameter type, except those that must be between the type and the name.

Bad:

function New-Event
{
    param
    (
        [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]
        $Message,

        [ValidateSet('operational', 'debug', 'analytic')][String]
        $Channel = 'operational'
    )
}

Good:

function New-Event
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Message,

        [Parameter()]
        [ValidateSet('operational', 'debug', 'analytic')]
        [String]
        $Channel = 'operational'
    )
}

Variables

Variable Names Use Camel Case

Variable names should use camelCase.

Bad:

function Write-Log
{
    $VerboseMessage = 'New log message'
    Write-Verbose $VerboseMessage
}

Bad:

function Write-Log
{
    $verbosemessage = 'New log message'
    Write-Verbose $verbosemessage
}

Good:

function Write-Log
{
    $verboseMessage = 'New log message'
    Write-Verbose $verboseMessage
}

Script, Environment and Global Variable Names Include Scope

Script, environment, and global variables must always include their scope in the variable name unless the 'using' scope is needed. The script and global scope specifications should be all in lowercase. Script and global variable names following the scope should use camelCase.

Bad:

$fileCount = 0
$GLOBAL:MYRESOURCENAME = 'MyResource'

function New-File
{
    $fileCount++
    Write-Verbose -Message "Adding file to $MYRESOURCENAME to $ENV:COMPUTERNAME."
}

Good:

$script:fileCount = 0
$global:myResourceName = 'MyResource'

function New-File
{
    $script:fileCount++
    Write-Verbose -Message "Adding file to $global:myResourceName to $env:computerName."
}

Best Practices

Although adoping the best practices is optional, doing so will help improve the quality of the module.

General Best Practices

Avoid Using Hardcoded Computer Name

Using hardcoded computer names exposes sensitive information on your machine. Use a parameter or environment variable instead if a computer name is necessary. This comes from this PS Script Analyzer rule.

Bad:

Invoke-Command -Port 0 -ComputerName 'hardcodedName'

Good:

Invoke-Command -Port 0 -ComputerName $env:computerName

Avoid Empty Catch Blocks

Empty catch blocks are not necessary. Most errors should be thrown or at least acted upon in some way. If you really don't want an error to be thrown or logged at all, use the ErrorAction parameter with the SilentlyContinue value instead.

Bad:

try
{
    Get-Command -Name Invoke-NotACommand
}
catch {}

Good:

Get-Command -Name Invoke-NotACommand -ErrorAction SilentlyContinue

Ensure Null is on Left Side of Comparisons

When comparing a value to $null, $null should be on the left side of the comparison. This is due to an issue in PowerShell. If $null is on the right side of the comparison and the value you are comparing it against happens to be a collection, PowerShell will return true if the collection contains $null rather than if the entire collection actually is $null. Even if you are sure your variable will never be a collection, for consistency, please ensure that $null is on the left side of all comparisons.

Bad:

if ($myArray -eq $null)
{
    Remove-AllItems
}

Good:

if ($null -eq $myArray)
{
    Remove-AllItems
}

Avoid Global Variables

Avoid using global variables whenever possible. These variables can be edited by any other script that ran before your script or is running at the same time as your script. Use them only with extreme caution, and try to use parameters or script/local variables instead.

This rule has a few exceptions:

  • The use of $global:DSCMachineStatus is still recommended to restart a machine from a DSC resource.

Bad:

$global:configurationName = 'MyConfigurationName'
...
Set-MyConfiguration -ConfigurationName $global:configurationName

Good:

$script:configurationName = 'MyConfigurationName'
...
Set-MyConfiguration -ConfigurationName $script:configurationName

Use Declared Local and Script Variables More Than Once

Don't declare a local or script variable if you're not going to use it. This creates excess code that isn't needed

Use PSCredential for All Credentials

PSCredentials are more secure than using plaintext username and passwords.

Bad:

function Get-Settings
{
    param
    (
        [String]
        $Username

        [String]
        $Password
    )
    ...
}

Good:

function Get-Settings
{
    param
    (
        [PSCredential]
        [Credential()]
        $UserCredential
    )
}

Use Variables Rather Than Extensive Piping

This is a script not a console. Code should be easy to follow. There should be no more than 1 pipe in a line.

Bad:

Get-Objects | Where-Object { $_.Propery -ieq 'Valid' } | Set-ObjectValue `
    -Value 'Invalid' | Foreach-Object { Write-Output $_ }

Good:

$validPropertyObjects = Get-Objects | Where-Object { $_.Property -ieq 'Valid' }

foreach ($validPropertyObject in $validPropertyObjects)
{
    $propertySetResult = Set-ObjectValue $validPropertyObject -Value 'Invalid'
    Write-Output $propertySetResult
}

Avoid Unnecessary Type Declarations

If it is clear what type a variable is then it is not necessary to explicitly declare its type. Extra type declarations can clutter the code.

Bad:

[String] $myString = 'My String'

Bad:

[System.Boolean] $myBoolean = $true

Good:

$myString = 'My String'

Good:

$myBoolean = $true

Calling Functions

Avoid Cmdlet Aliases

When calling a function use the full command not an alias. You can get the full command an alias represents by calling Get-Alias.

Bad:

ls -File $root -Recurse | ? { @('.gitignore', '.mof') -contains $_.Extension }

Good:

Get-ChildItem -File $root -Recurse | Where-Object -Filterscript {
    @('.gitignore', '.mof') -contains $_.Extension
}

Avoid Invoke-Expression

Invoke-Expression is vulnerable to string injection attacks.

Bad:

Invoke-Expression -Command "Test-$DSCResourceName"

Good:

& "Test-$DSCResourceName"

Use the Force Parameter with Calls to ShouldContinue

Bad:

Good:

Avoid the WMI Cmdlets

The WMI cmdlets can all be replaced by CIM cmdlets. Use the CIM cmdlets instead because they align with industry standards.

Bad:

Get-WMIInstance -ClassName Win32_Process

Good:

Get-CIMInstance -ClassName Win32_Process

Avoid Write-Host

Write-Host is harmful. Use alternatives such as Writ-Verbose, Write-Output, Write-Debug, etc.

Bad:

Write-Host 'Setting the variable to a value.'

Good:

Write-Verbose -Message 'Setting the variable to a value.'

Avoid ConvertTo-SecureString with AsPlainText

SecureStrings should be encrypted. When using ConvertTo-SecureString with the AsPlainText parameter specified the SecureString text is not encrypted and thus not secure. This is allowed in tests/examples when needed, but never in the actual resources.

Bad:

ConvertTo-SecureString -String 'mySecret' -AsPlainText -Force

Assign Function Results to Variables Rather Than Extensive Inline Calls

Bad:

Set-Background -Color (Get-Color -Name ((Get-Settings -User (Get-CurrentUser)).ColorName))

Good:

$currentUser = Get-CurrentUser
$userSettings = Get-Settings -User $currentUser
$backgroundColor = Get-Color -Name $userSettings.ColorName

Set-Background -Color $backgroundColor

Writing Functions

Avoid Default Values for Mandatory Parameters

Default values for mandatory parameters will always be overwritten, thus they are never used and can cause confusion.

Bad:

function Get-Something
{
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Name = 'My Name'
    )

    ...
}

Good:

function Get-Something
{
    param
    (
        [Parameter(Mandatory = $true)]
        [String]
        $Name
    )

    ...
}

Avoid Default Values for Switch Parameters

Switch parameters have 2 values - there or not there. The default value is automatically $false so it doesn't need to be declared. If you are tempted to set the default value to $true - don't - refactor your code instead.

Bad:

function Get-Something
{
    param
    (
        [Switch]
        $MySwitch = $true
    )

    ...
}

Good:

function Get-Something
{
    param
    (
        [Switch]
        $MySwitch
    )

    ...
}

Include the Force Parameter in Functions with the ShouldContinue Attribute

Bad:

Good:

Use ShouldProcess if the ShouldProcess Attribute is Defined

Bad:

Good:

Define the ShouldProcess Attribute if the Function Calls ShouldProcess

Bad:

Good:

Avoid Redefining Reserved Parameters

Reserved Parameters such as Verbose, Debug, etc. are already added to the function at runtime so don't redefine them. Add the CmdletBinding attribute to include the reserved parameters in your function.

Use the CmdletBinding Attribute on Every Function

The CmdletBinding attribute adds the reserved parameters to your function which is always preferable.

Bad:

function Get-Property
{
    param
    (
        ...
    )
    ...
}

Good:

function Get-Property
{
    [CmdletBinding()]
    param
    (
        ...
    )
    ...
}

Define the OutputType Attribute for All Functions With Output

The OutputType attribute should be declared if the function has output so that the correct error messages get displayed if the function ever produces an incorrect output type.

Bad:

function Get-MyBoolean
{
    [OutputType([Boolean])]
    param ()

    ...

    return $myBoolean
}

Good:

function Get-MyBoolean
{
    [CmdletBinding()]
    [OutputType([Boolean])]
    param ()

    ...

    return $myBoolean
}

Localization

In the src folder there should be at least one localization folder for english language 'en-US'. Add other localization folders as appropriate, the correct folder name can be found by running Get-UICulture on the node that has a UI culture installed that the strings are being built for. There is also the list of Available Language Packs for Windows.

In each localization folder there should be a PowerShell data (.psd1) file named 'PSAuth.strings.psd1'. Each localized string file should contain the following with the correct localization key and accompanying localization string value (the example uses the friendly resource name of 'Folder').

# culture="en-US"
ConvertFrom-StringData -StringData @'
    ImportingLibFileMessage = Importing function library '{0}'.
    FindResourceTokenInContext = Searching context tokens for resource matching '{0}'.
    FoundResourceTokenInContext = {0} context token(s) with resource '{1}' found.
'@

Example of usage:

See example of localized strings under Localization.

Using localization string in code

This is an example of how to write localized verbose messages.

Write-Verbose -Message `
    ($script:localizedData.RetrievingFolderInformation -f $path)

This is an example of how to write localized warning messages.

Write-Warning -Message `
    ($script:localizedData.ProblemAccessFolder -f $path)

Pester Tests

Capitalized Pester Assertions

Pester assertions should all start with capital letters. This makes code easier to read.

Bad:

it 'Should return something' {
    get-targetresource @testParameters | should -be 'something'
}

Good:

It 'Should return something' {
    Get-TargetResource @testParameters | Should -Be 'something'
}

Assertion Messages Start with Should

Pester assertions should always start with the word 'Should'. This is to ensure the test results read more naturally as well as helping to indentify assertion messages that aren't making assertions.

Bad:

# This is not an assertive message
It 'When calling Get-TargetResource' {
    Get-TargetResource @testParameters | Should -Be 'something'
}

Bad:

It 'Something is returned' {
    Get-TargetResource @testParameters | Should -Be 'something'
}

Good:

It 'Should return something' {
    Get-TargetResource @testParameters | Should -Be 'something'
}

Outer Context Block Messages Start with When

Pester test outer Context block messages should always start with the word 'When'. This is to ensure the test results read more naturally as well as helping to indentify context messages that aren't defining context. This only applies to the an outer Context block if they are being nested.

Bad:

Context 'Calling Get-TargetResource with default parameters'
    It 'Should return something' {
        Get-TargetResource @testParameters | Should -Be 'something'
    }
}

Good:

Context 'When Get-TargetResource is called with default parameters'
    It 'Should return something' {
        Get-TargetResource @testParameters | Should -Be 'something'
    }
}

Good:

Context 'When Get-TargetResource is called'
    Context 'With default parameters'
        It 'Should return something' {
            Get-TargetResource @testParameters | Should -Be 'something'
        }
    }
}