Writing Scripts
This section contains information about writing scripts. Mostly, the snippets below provide hints for the following tasks:
- Passing parameters to strings
- Checking the environment that the script runs on
Default Parameters
PowerShell provides a standard set of script parameters that enable simple tasks, like debugging, increasing verbosity, etc. The nice thing is that those values flow to subsequent calls without any extra effort. For example, if you enable the -Verbose
option in a script A
, and that script calls another script B
, which also understands the -Verbose
option, then A
does not need to pass the parameter to B
. To enable the use of the standard parameters use the CmdletBinding
attribute:
[CmdletBinding()]
param(
…
)
Better is to also use the SupportsShouldProcess
attribute that allows the -WhatIf
option:
[CmdletBinding(SupportsShouldProcess=$true)]
Parameter Validation
PowerShell allows to validate parameters at the definition. This brings together the parameter definition and its validation code. As a result it also simplifies the body of the function/cmdlet.
Some examples of parameter validation:
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]
$OutputPath,
[parameter(Mandatory=$false)]
[ValidateScript({$_ -ge 1 })]
[int]
$Throttle = 20
The first example above is a simple validation for a string: it rejects empty strings (either null or empty); this is similar to using the method IsNullOrWhiteSpace()
from System.String
. It also specifies that the parameter is mandatory.
The second example uses a script to validate the parameter. Observe that you need to use curly brackets for any non-trivial code, and that the value of the parameter is retrieved with $_
. (This parameter is not mandatory, and it gets a default value of 20.)
Parameters with a set of possible values
You can restrict the set of allowable values using the ValidateSet
attribute:
[ValidateNotNullOrEmpty()]
[ValidateSet("NFP3240","82599ES","82579LM")]
[string]
$SenderCard = "NFP3240"
In addition to the use of the ValidateSet
attribute, observe that we use two validate attributes: ValidateNotNullOrEmpty
and ValidateSet
.
Using the pipeline
Often it is useful to feed a parameter using the pipeline. PowerShell has a nice support for doing so. Observe that this is different than passing an array or list of values, as in this case the items need to exist before calling the script. With the pipeline, the items can be generated on the fly.
[CmdletBinding(SupportsShouldProcess=$true )]
param(
[parameter(ValueFromPipeline=$True)]
[Alias('IPAddress','__Server','CN')]
[ValidateNotNullOrEmpty()]
[string[]]
$ComputerName
)
BEGIN {
}
PROCESS {
Write-Verbose -Message "Processing $ComputerName"
}
END {
}
In addition to the use of the pipeline above, do also notice the use of the Alias
attribute to provide different names to the parameter. Also, notice the break down of the processing in BEGIN
, PROCESS
, and END
blocks.
Parameter Sets
PowerShell allows parameter sets. This is a useful feature when you need to make sure that pairs of parameters cannot be used together.
[CmdletBinding(DefaultParametersetName="Selective")]
param(
[ValidateNotNullOrEmpty()]
[string]
$LogFile = "results.txt",
[Parameter(ParameterSetName='All', Position=1)]
[Switch]
$ShowAll,
[Parameter(ParameterSetName='Selective', Position=1)]
[string[]]
$Columns = @("Job","DllTimestamp","BatchStartTimestamp","Format")
)
In the example above observe that the default parameter set is Selective
. There is also the All
parameter set.
Gotcha: Passing arrays as parameters
If you pass an array as a parameter, PowerShell will expand the array. This is convenient some times, but very confusing quite often. To guarantee that the parameter is passed as an array using the (,$array)
notation:
$guid = New-Object System.Guid -ArgumentList (,$id)
(In the example above $id
is an array.)
Splatting
An alternative way to pass parameters is to create a hashtable that contains a list of parameter name and values, and then pass the hashtable to the command. E.g.
$arguments = @{
'Class' = 'win32_userprofile'
'ComputerName' = 'localhost'
}
Get-WmiObject @arguments
Splatting allows the construction of the argument list programmatically. It may also be convenient when the number of arguments is very long (which should be avoided).
Read more in:
Dynamic Parameter Sets
A powerful feature of PowerShell is that it allows for dynamic parameter validation. For example, you may want to use a parameter set (see the note above), but you do not know in advance the valid values. One option is to validate the argument in the beginning of the script itself; this is indeed the only option for other languages --- and in some ways it is also cleaner code. However, by using the standard approach you cannot use auto-completion. A better way is to use a DynamicParam
. Take a look at the Dynamic ValidateSet in a Dynamic Parameter blog post. The following example is from that article:
DynamicParam {
# Set the dynamic parameters' name
$ParameterName = 'Path'
# Create the dictionary
$RuntimeParameterDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
# Create the collection of attributes
$AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
# Create and set the parameters' attributes
$ParameterAttribute = New-Object System.Management.Automation.ParameterAttribute
$ParameterAttribute.Mandatory = $true
$ParameterAttribute.Position = 1
# Add the attributes to the attributes collection
$AttributeCollection.Add($ParameterAttribute)
# Generate and set the ValidateSet
$arrSet = Get-ChildItem -Path .\ -Directory | Select-Object -ExpandProperty FullName
$ValidateSetAttribute = New-Object System.Management.Automation.ValidateSetAttribute($arrSet)
# Add the ValidateSet to the attributes collection
$AttributeCollection.Add($ValidateSetAttribute)
# Create and return the dynamic parameter
$RuntimeParameter = New-Object System.Management.Automation.RuntimeDefinedParameter($ParameterName, [string], $AttributeCollection)
$RuntimeParameterDictionary.Add($ParameterName, $RuntimeParameter)
return $RuntimeParameterDictionary
}
begin {
# Bind the parameter to a friendly variable
$Path = $PsBoundParameters[$ParameterName]
}
Even though the code is rather lengthy, most of it is boilerplate. The actual changes you need to do to adapt to your case are rather limited (often just construct a valid $arrSet
variable), and change the name of the parameter in the first line.
You also need to assign the dynamic parameter to a local variable using the $PsBoundParameters
variable.
Invoking functions dynamically
When the name of the function to be called is dynamic, i.e. not known in advanced, you can pass the name in a string:
$myfunc = "Get-Date"
&"$myfunc"
Of course, you can also pass arguments in the usual way, or with splatting.
Script Requirements
Often you need to guarantee that a set of modules are necessary to run a script, or that the PowerShell host is of a specific version. To do use use the #Requires
directives at the beginning of your script:
#Requires -RunAsAdministrator
#Requires –Version 3
#Requires –Modules PSWorkflow
See also the MSDN help page here.
Checking for elevated permissions
See note.
Variable Names
It is possible to use arbitrary characters for variable names, e.g.
${my long name} = 3
However, in order to do that, you need to enclose the name in curly braces. If you just use normal characters, without spaces or anything fancy, the curly braces are not needed. Often, automatically generated code includes curly braces even for normal variable names (this also happens in other places); this is probably an extra protection, and has no other significance.
Functions
Conditional function definition
Sometimes it is desirable to define a function conditionally. One simple approach is to define it in a separate file, and include that file when the condition is met. A better approach, especially for short functions, is to use the Set-Item
command:
if ($condition) {
Set-Item -Path function:FunctionName -Value {
# Function body
}
}
Read-only and constant functions
In PowerShell it is possible to redefine functions. If this is not desired, you can make a function constant:
Set-Item -Path function:FunctionName -Options ReadOnly
The user will get an error if she tries to redefine function FunctionName
. However, redefining the function is still possible using the -Force
switch in a Set-Item
definition, or by removing the function first (again with the -Force
switch).
A more aggressive option is to use the Constant
option (-Options Constant
). Then the function will persist until the end of the session; it cannot be removed or redefined.