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.
- Style Guidelines
- Markdown Files
- General
- Whitespace
- Indentation
- No Trailing Whitespace After Backticks
- Newline at End of File
- Newline Character Encoding
- No More Than Two Consecutive Newlines
- One Newline Before Braces
- One Newline After Opening Brace
- Two Newlines After Closing Brace
- One Space Between Type and Variable Name
- One Space on Either Side of Operators
- One Space Between Keyword and Parenthesis
- Functions
- Parameters
- Variables
- Best Practices
- General Best Practices
- Calling Functions
- Writing Functions
- Avoid Default Values for Mandatory Parameters
- Avoid Default Values for Switch Parameters
- Include the Force Parameter in Functions with the ShouldContinue Attribute
- Use ShouldProcess if the ShouldProcess Attribute is Defined
- Define the ShouldProcess Attribute if the Function Calls ShouldProcess
- Avoid Redefining Reserved Parameters
- Use the CmdletBinding Attribute on Every Function
- Define the OutputType Attribute for All Functions With Output
- Return Only One Object From Each Function
- Localization
- Helper Functions for Exceptions
- Pester Tests
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.
Make sure all files are encoded using UTF-8 (not UTF-8 with BOM).
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
)
...
}
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
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
}
)
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
}
}
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
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
}
}
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
}
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.
Backticks should always be directly followed by a newline
All files must end with a newline, see StackOverflow.
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.
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...'
}
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'
}
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
}
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
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
}
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.'
}
}
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
}
}
Function names must use PascalCase. This means that each concatenated word is capitalized.
Bad:
function get-targetresource
{
# ...
}
Good:
function Get-TargetResource
{
# ...
}
All function names must follow the standard PowerShell Verb-Noun format.
Bad:
function TargetResourceGetter
{
# ...
}
Good:
function Get-TargetResource
{
# ...
}
All function names must use approved verbs.
Bad:
function Normalize-String
{
# ...
}
Good:
function ConvertTo-NormalizedString
{
# ...
}
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
}
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'
}
- 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"
}
}
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 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'
)
}
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 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'
)
}
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 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."
}
Although adoping the best practices is optional, doing so will help improve the quality of the module.
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
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
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 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
Don't declare a local or script variable if you're not going to use it. This creates excess code that isn't needed
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
)
}
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
}
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
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
}
Invoke-Expression is vulnerable to string injection attacks.
Bad:
Invoke-Expression -Command "Test-$DSCResourceName"
Good:
& "Test-$DSCResourceName"
Bad:
Good:
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
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.'
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
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
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
)
...
}
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
)
...
}
Bad:
Good:
Bad:
Good:
Bad:
Good:
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.
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
(
...
)
...
}
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
}
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.
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 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'
}
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'
}
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'
}
}
}