PowerShell Scripting Guidelines
Lately I have been doing some work related to how we automate the process of how we install, configure and deploy our product into a server environment. Since our pre-requisites are Windows Server 2008 and Windows 7, we can totally go crazy with PowerShell scripts. Next thing to sort out after upgrading to Windows Server 2008 R2 is of course PowerShell Remoting..
However, using quite an amount of PowerShell scripts, we need some guidelines to structure them. This to complement our guidelines covering OO design and C# code style.
So I have started out with the following draft and I’m looking for some tips and comments about this and what you (yes, you!) believe is a good rule of thumb to use when developing and maintaining a bunch of PowerShell scripts.
1 Scripting Guidelines
When there is a need of a script, use your mighty PowerShell skills!
Here is a couple of guidelines which should be followed to speed up development, maintenance and usage of PowerShell scripts.
Apply the above mentioned principles when applicable.
1.1 Function files
Package common functions into function files and dot-source them in the script which needs them.
Example:
. ..\DB\Common.ps1
1.2 File names
Name PS1 files using standard verbs like get, set, remove, new etc., or a verb that fits the purpose or the script. Do not use verbs in file names for files that have generic purpose, like a function library file. Also consider using PowerShell v2 modules when applicable.
Scripts that are to be called from the prompt are easier to understand if they are named in such way that no “usage” or get-help is needed when invoking the script. Like add-user.ps1, remove-database.ps1 and so on.
Avoid complex scripts with multiple purposes which becomes complex with parameter sets (get-help about_functions_advanced_parameters)
1.3 Script and function parameters
Use built-in support to handle function and script parameters. Avoid index-based unnamed parameters using $args.
Reference: get-help about_arguments; get-help about_functions; get-help about_functions_advanced_parameters
MSDN: http://msdn.microsoft.com/en-us/library/ms714433(VS.85).aspx
Example, testme.ps1:
param(
[Switch] $ver = [Switch]::Present
)
Write-Host $args
Write-Host “Testing!”
if( $ver )
{
Write-Host “Testing even more!”
}
Output:
PS > .\testme.ps1 –ver
PS > .\testme.ps1 -ver:$false
1.4 Decorate function parameters appropriately using validation rules
Decorate your parameters with validation rules and help messages to avoid tedious work on custom “script usage” functions. Your script users will be able to use “get-help yourscript.ps1”, or the equivalent “yourscript.ps1 -?” to get script usage information. Users will also be prompted if mandatory parameters are left out blank.
Get-help will also print out common parameters (get-help about_commonparameters).
Example:
param(
[Switch] $ver = [Switch]::Present,
[Parameter(mandatory=$true, helpmessage="Your name")]
[ValidateNotNullOrEmpty()]
[string] $name = “John Doe”
)
Write-Host $args
Write-Host “Hello $name!”
if( $ver )
{
Write-Host “Your name is $($name.Length) characters long”
}
Output:
PS > get-help .\testme.ps1
testme.ps1 [-name] [-ver] [-Verbose] [-Debug] [-ErrorAction ] [-WarningAction ] [-ErrorVariable ] [-WarningVariable ] [-OutVariable ] [-OutBuffer ]
1.5 Document script usage using PowerShell comment based help
When it is necessary to document how a script can be used, or when you want to provide more information about your script’s parameters than just the name, document it using the PowerShell help syntax. This can be compared to using XML comments in languages such as C#/VB or javadoc tags in Java.
See “get-help about_comment_based_help” for more information.
Example:
<#
.Synopsis
Synopsis text here
.Description
Usage description here
.Parameter ver
Enabling this switch prints out the length of the given name
.Parameter name
The name to say hello to
#>
param(
[Switch] $ver = [Switch]::Present,
[Parameter(mandatory=$true, helpmessage="Your name")]
[ValidateNotNullOrEmpty()]
[string] $name = “John Doe”
)
Write-Host $args
Write-Host “Hello $name!”
if( $ver )
{
Write-Host “Your name is $($name.Length) characters long”
}
Output:
PS> get-help -full .\testme.ps1
NAME
C:\temp\testme.ps1
SYNOPSIS
Synopsis text here
SYNTAX
C:\temp\testme.ps1 [-ver] [-name] []
DESCRIPTION
Usage description here
PARAMETERS
-ver []
Enabling this switch prints out the length of the given name
Required? False
Position? Named
Default value
Accept pipeline input? False
Accept wildcard characters?
-name
The name to say hello to
Required? True
Position? 1
Default value
Accept pipeline input? False
Accept wildcard characters?
This cmdlet supports the common parameters: Verbose, Debug,
ErrorAction, ErrorVariable, WarningAction, WarningVariable,
OutBuffer and OutVariable. For more information, type,
“get-help about_commonparameters”.
INPUTS
OUTPUTS
RELATED LINKS
1.6 Use validation rules to perform parameter input validation
PowerShell has several built-in validation rules for input validation. Use them as long as they add more value than doing custom validation using if-statements.
The built-in validation rules are: AllowNull, AllowEmptyString, AllowEmptyCollection, ValidateCount, ValidateLength, ValidatePattern, ValidateRange, ValidateScript, ValidateSet, ValidateNotNull, ValidateNotNullOrEmpty
See “get-help about_functions_advanced_parameters” for further reference.
1.7 Putting the right stuff on the pipeline
· Use write-host for console information with fancy colors
· Use write-output to be able to pipe your script/function to another script/function.
Example pipeline scenario, sending a user from add-user to new-guestbookentry:
. .\guestbooks-functions.ps1;
add-user “foo” “password” | new-guestbookentry “Welcome to our guestbooks.com!”
· Avoid unwanted objects on the pipeline using termination with $null assignments or piping the output to NULL, example:
add-user “foo” “password” # put the added user on the pipeline
$null = add-user “foo” “password” # added user object “terminated”
add-user “foo” “password” | out-null # added user object piped to NULL instead of the output