Some notes on parameters for scripts or (advanced) functions in Powershell.
Parameter block for a script or an advanced function
At the beginning of the script, add CmdletBinding()
and a Param()
block.
Note that in most cases, this block must be the very first line of code in a script (comments and blank lines don’t matter), so don’t try to declare variables or define functions before it.
[CmdletBinding()] # With that entry, a function turns into an 'advanced function'.
Param
(
[Parameter(Mandatory=$True)] [string] $StringParameter,
[Parameter(Mandatory=$False)] [bool] $BooleanParameter = $False,
[Parameter(Mandatory=$False)] $IntegerParameter = 10,
[Parameter(Mandatory=$False)]
[switch]
$SwitchParameter
)
As usual, typing the variable (like [string]
or [bool]
) is not required, but can prevent some
errors or weird behavior due casting issues.
Common Parameters
CmdletBinding()
is part of an “advanced functions/script cmdlet”. By that, a script or function
automatically supports common parameters
like -Verbose, -Debug, -WhatIf, -Confirm, -ErrorAction etc.
But note that this still needs additional support in the implementation to work right:
There are a few cmdlets like Write-Verbose
or Write-Debug
that do that already, but to do something
yourself, is a tiny bit tricky:
To handle such a common parameter (like -Debug) in the script itself1:
if ($PSCmdlet.MyInvocation.BoundParameters["Debug"]) { <# ... #> }
else { <# ... #> }
And if you want to pass it to another script/function:
Start-Something -Debug:($PSCmdlet.MyInvocation.BoundParameters["Debug"] -eq $true)
Simple Function
For a normal function, parameters can also be provided simpler (and they are not required at all):
No parameters at all:
function Func
{
# ...
}
Parameters in the function head
function Func ($Param1, [int] $Param2)
{
# ...
}
Or parameters in the parameter block in the function body:
function Func
{
param
(
$Param1,
$Param2
)
}
Validate parameter input
Example: Is the argument an existing file? ($_
is a shortcut for the value of the input argument.)
Param
(
[ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
[string] $InputFile
)
Note that only incoming arguments are checked, not the default values!
That means, if you set the default value here to something like $InputFile = "C:\Some\Path\To\File.txt"
,
and that path doesn’t exist, ValidateScript
will not throw an error; one needs to test it explicitly later in the script.
That’s also why it will only work correctly for mandatory parameters, which then in turn also prevent that a supplied default value will be evaluated (see also Why Doesn’t My ValidateScript() work correctly?).
Code examples | Description |
---|---|
[ValidateScript({Test-Path -Path $_ -PathType Leaf})] |
The expression must be valid (return $true). In this example, the file must exist. |
[ValidateRange(0,10)] |
The parameter must be between zero and ten. |
[ValidateRange("Positive")] |
The parameter value must be greater than zero (enums are: “Positive”, “Negative”, “NonPositive”, “NonNegative”). |
[ValidateCount(1,5)] |
The parameter takes one (minimum) to five (maximum) parameter values. |
[ValidateLength(1,10)] |
The parameter value must have one (minimum) to ten (maximum) characters. |
[ValidatePattern("[0-9][0-9][0-9][0-9]")] |
The parameter value must contain a four-digit number, and each digit must be a number zero to nine. |
[ValidateSet("Low", "Medium", "High")] |
Only one of those three specified values will be accepted. Interesting Gotcha: "The validation occurs whenever that variable is assigned even within the script"! |
[ValidateNotNull()] |
The parameter value may not be null. |
[ValidateNotNullOrEmpty()] |
The parameter value may not be null or empty. |
[ValidateDrive("C", "D"] |
The parameter value must be a PSDrive. |
.. and some more… |
And these are allowed for mandatory parameters:
[AllowNull()]
[AllowEmptyString()]
[AllowEmptyCollection()]
Parameter Set
Useful for mutually exclusive arguments (more at Simon Wahlin: PowerShell functions and Parameter Sets).
Example
Parameter Param1 is always available for the combinations of sets that are specified in
its [Parameter()]
attributes(!), but either Enable_A or Enable_B can additionally and optionally be chosen:
With [CmdletBinding(DefaultParameterSetName = "...")]
a default paramter will be set, in cases
where it can’t be determined otherwise.
function Set-Something
{
[CmdletBinding(DefaultParameterSetName = "Default")]
param
(
[Parameter(ParameterSetName="Default", Mandatory=$true)]
[Parameter(ParameterSetName="Set_A")]
[Parameter(ParameterSetName="Set_B")]
[string] $Param1,
[Parameter(ParameterSetName="Default")]
[Parameter(ParameterSetName="Set_A")]
[int] $Param2,
[Parameter(ParameterSetName="Set_A", Mandatory=$true)]
[switch] $Enable_A,
[Parameter(ParameterSetName="Set_A")]
[switch] $Enable_A_optional,
[Parameter(ParameterSetName="Set_B", Mandatory=$true)]
[switch] $Enable_B,
[Parameter(ParameterSetName="Set_B")]
[switch] $Enable_B_optional
)
# ...
}
Set-Something -Param1 "foo"
Set-Something -Param1 "foo" -Enable_A
Set-Something -Param1 "foo" -Enable_B
Set-Something -Param1 "foo" -Enable_A -Enable_B # Will not autocomplete and will throw an error if you enforce it manually!
Set-Something -Param1 "foo" -Param2 100 -Enable_B # Error, since Param2 doesn't list 'Set_B' in its [Parameter()] attribute!
Detect and handle a specific parameter set
Via $PSCmdlet.ParameterSetName
one can determine which set of parameters is currently effective:
if ($PSCmdlet.ParameterSetName -eq 'NameOfTheSet')
{
# ...
}
Another option is to use a switch-case statement:
switch ($PSCmdlet.ParameterSetName)
{
'Set1'
{
write-host "Using parameter set no. 1."
break
}
'Set2'
{
write-host "Using parameter set no. 2."
break
}
}
‘Gotcha’ regarding mandatory parameters
To make parameters mandatory only for a specific set, the right way is to add Mandatory=$true to the Parameter attribute:
[Parameter(ParameterSetName="Set_A", Mandatory=$true)] $A_man
[Parameter(ParameterSetName="Set_A")] $A_opt,
[Parameter(ParameterSetName="Set_B", Mandatory=$true)] $B_man,
[Parameter(ParameterSetName="Set_B")] $B_opt
I had to struggle with it in the beginning, because I did it the wrong way and split the attribute settings.
That way, even when using Set_A parameters, I was asked for a mandatory parameter of Set_B.
Because by using it the as shown below, the parameters will always be mandatory, regardless of the currently active parameter set!
[Parameter(ParameterSetName="Set_A")]
[Parameter(Mandatory=$true)] # !
$A,
[Parameter(ParameterSetName="Set_B")]
[Parameter(Mandatory=$true)] # !
$B
Position
Sometimes, one may want to skip the name of the parameters:
test.ps1 "foo" "bar"
For these cases, one can specify a position for the parameter, so that the correct value will be assigned to the correct parameter:
The following example applies the positional setting to all parameter sets:
[Parameter(Position = 0)] [string] $Param1,
[Parameter(Position = 1)] [string] $Param2
This example defines the positions as only viable for Set_X parameters:
[Parameter(ParameterSetName = 'Set_X', Position = 0)] [string] $ParamX1,
[Parameter(ParameterSetName = 'Set_X', Position = 1)] [string] $ParamX2,
One can then also mix and match named and position-based parameter values:
> test.ps1 -Param1 "foo" -Param2 "bar"
> test.ps1 "foo" "bar"
> test.ps1 -Param1 "foo" "bar"
> test.ps1 "foo" -Param2 "bar"
> test.ps1 -Param2 "bar" "foo"
Pass multiple values to a single parameter
By the way: Originally I used $input
as the name for the parameter, but I soon discovered that it
is a predefined Powershell variable in functions and script blocks; so, better use a different name.
function Func
{
[CmdletBinding()]
param
(
[string[]] $InputStr = @("String One", "String Two"')
)
foreach ($item in $InputStr) { Write-Output $item }
}
Func -InputStr "foo", "bar"
Some notes:
- The parameter definition of
$InputStr
accepts an array of strings, as denoted by the[string[]]
prefix. @()
is called the array operator and is used to create an array of objects. It’s not always neccessary to use it for array creation (normally comma separating parameters is enough), but in this case it is necessary to help the PowerShell parser know what’s going on.
Alias
Another neat feature, that I was only dimly aware of as yet, are parameter aliases (a very helpful tutorial on that topic is The Snazzy Secret of PowerShell Parameter Aliases):
function Func
{
[CmdletBinding()]
param
(
[Alias('Data', 'Text')]
[string[]] $InputStr,
[Alias('Bar')]
[switch] $Foo
)
# ...
}
The two most prominent advantages that come with parameter aliases are these:
- Keeping backward compatibility if a paramter name is changed: The old calls will still work.
- Offering different terms for different users or use cases.
That means that the call to Func -InputStr "Blah" -Foo
or Func -Text "Blah" -Bar
or Func -Data "Blah" -Foo
do all the same.
By the way: Get-Help
won’t let one get the aliases, so one must use Get-Command
to get the details – which is a bit strange, because on the console,
one can easily get to that data, but not from within a script…
> (get-help -name Func).Parameters.parameter | select Name, Aliases
name aliases
---- -------
Foo Bar
InputStr Data, Text
> (get-command -name Func).Parameters.Values | select Name, Aliases
Name Aliases
---- -------
InputStr {Data, Text}
Foo {Bar}
Verbose {vb}
[...]
> (get-command -name Func).Parameters['InputStr']
Name : InputStr
ParameterType : System.String[]
ParameterSets : {[__AllParameterSets, System.Management.Automation.ParameterSetMetadata]}
IsDynamic : False
Aliases : {Data, Text}
Attributes : {, System.Management.Automation.AliasAttribute, System.Management.Automation.ArgumentTypeConverterAttribute}
SwitchParameter : False
> (get-command -name Func).Parameters['InputStr'].Aliases
Data
Text
Dynamic Parameter
Another interesting feature, which I knew about for a while, but for which I only recently had a real necessity, are dynamic (or conditional) parameters: They are cool, but also a bit tricky and cumbersome to set up.
👉 For that reason, I put it on its own page.
-
Sometimes(!?) there is also
IsPresent
(e.g....["Debug"].IsPresent
) to test against, but that value is not always available (no idea, why not); if not, then an exception will be thrown! ↩︎
Film & Television (54)
How To (63)
Journal (17)
Miscellaneous (4)
News & Announcements (21)
On Software (12)
Projects (26)