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()] # Turns it to an 'advanced function/script'.
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 type-casting issues.
Also: [Parameter(Mandatory=$True)] can be shortened to [Parameter(Mandatory)]:
[Parameter(Mandatory)] $Paramis the same as[Parameter(Mandatory=$True)] $Param$Paramis the same as[Parameter(Mandatory=$False)] $Param
Common Parameters
By speifying CmdletBinding(), a script or function automatically supports common parameters,
which are these:
-Confirm-Debug-ErrorAction-ErrorVariable-InformationAction-InformationVariable-OutBuffer-OutVariable-PipelineVariable(read the Microsoft documentation!)-ProgressAction(added in PowerShell 7.4)-Verbose-WarningAction-WarningVariable-WhatIf
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:
Detect the Common Parameter Flags
To handle such a common parameter (like -Debug or Verbose) in the script itself1:
if ($PSCmdlet.MyInvocation.BoundParameters["Debug"]) { <# ... #> }
else { <# ... #> }
… or use this:
$VerboseMode = $null # ... or $false…
$PSBoundParameters.TryGetValue("Verbose", [ref] $VerboseMode) | out-null
if ($VerboseMode) { "Verbose mode is $VerboseMode" }
else { "Verbose mode is $VerboseMode" }
By using TryGetValue(), an unlikely (but valid) variant like -Verbose:$false will also be detected:
> script.ps1
Verbose mode is # Nothing, since the variable was set to $null by default in the example above.
> script.ps1 -Verbose
Verbose mode is True
> script.ps1 -Verbose:$false
Verbose mode is False
> script.ps1 -Verbose:$true
Verbose mode is True
Pass a common parameter 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
See also Parameter and variable validation attributes.
Example: Is the argument an existing file? ($_ is the shortcut for the argument, i.e. the input value).
Param
(
[ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
[string] $InputFile
)
-
Note that only incoming arguments are checked, not the default values!
That means: If a default value is defined (e.g.$InputFile = "C:\Some\Path\To\File.txt"), and that path doesn’t exist,ValidateScriptwill not throw an error!
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?). -
Different validation methods can be combined; for example:
function Start-Foo { Param ([ValidateCount(1, 2)] [ValidateSet('One', 'Two', 'Three')] $p) # ... } Start-Foo -p One, Two, Three # Error: Only 1 (min.) or 2 (max.) of the allowed values would be valid.
| Examples | Description |
|---|---|
[ValidateScript({Test-Path -Path $_ -PathType Leaf})] |
The expression must be valid (return $true): The file must exist. |
[ValidateScript({Test-Path -Path $_})] [System.IO.FileInfo] $x |
Must be a valid path; parameter will be cast to a FileInfo object. |
[ValidateRange(0,10)] |
The parameter must be between zero and ten. |
[ValidateRange("Positive")] [int] $x (since PS 6.1.0) |
The parameter value must be positive (= greater than zero); All enums: “Positive”, “Negative”, “NonPositive”, “NonNegative” Also: One needs to specify a fitting type (e.g. [int]). |
[ValidateCount(2,2)] |
The parameter takes exactly two argument values (minimum, maximum). |
[ValidateLength(1,10)] |
The parameter value must have one (minimum) to ten (maximum) characters. |
[ValidatePattern("^[0-3][0-3]$")] |
Compares the parameter to a regular expression. |
[ValidateSet("A", "B", "C")] |
Only one of the specified values will be accepted. Interesting fact: "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. |
[AllowNull()] |
Accept $null (handy for a mandatory parameter). |
[AllowEmptyString()] |
Accept "" (handy for a mandatory parameter). |
[AllowEmptyCollection()] |
Accept @() (handy for a mandatory parameter). |
List the values of a ValidateSet parameter
If one needs the values of a ValidateSet-limited parameter later in the code:
Param ( [ValidateSet("A", "B", "C")] $ParameterX )
$VS = ((Get-Variable 'ParameterX').Attributes | select ValidValues).ValidValues
The (… | select ValidValues).ValidValues may look weird, but is necessary: Other ways didn’t work (exception) :-/
(Note: A shorter notation would require a lower or unset strict mode)
Accept multiple values (of a ValidateSet) for a parameter
function Start-Foo
{
Param
(
[ValidateSet('A', 'B', 'C')] $x,
[ValidateSet('D', 'E', 'F')] [string[]] $y,
[ValidateSet('G', 'H', 'I')] [string] $z
)
# ...
}
Start-Foo -x A, B -y D, E -z G, H
-x A, Bwill work, since the type of$xis not specified.-y D, Ewill work, since the type of$yis specified as an array of strings.-z G, Hwill not work, since the type of$zis specified as a single string (i.e. it’s not expecting a comma-separated list of values).
Parameter Sets
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
Positional Parameters
Normally, I prefer to type the full cmdlet/function names (thanks to autocomplete) and to used named parameters, instead of using aliases and positional parameters. But that’s also caused by the fact that I write (more complex) scripts/functions for others, which must be understable even months and years later.
On the other hand: Sometimes, when one must execute the same task often as a quick one-liner on the CLI, a short version may be preferable:
Compare Update-FoobarThingy -ParameterX arg1 -ParameterY arg2
with uf arg1 arg2
Default behaviour
By default, all parameters are available by position and the default order is the order the parameters are defined:
Param
(
$ParameterX, # Position 1
$ParameterY # Position 2
)
Write-Host "This is ParameterX: $ParameterX"
Write-Host "This is ParameterY: $ParameterY"
Explicit order
But one can also explicity define a different order:
Param
(
[Parameter(Mandatory, Position=1)] $ParameterX, # Now at position 2!
[Parameter( Position=0)] $ParameterY, # Not at position 1! (Index starts at 0)
)
Write-Host "This is ParameterX: $ParameterX"
Write-Host "This is ParameterY: $ParameterY"
But which can become irritating and troublesome if one isnt’t aware or doesn’t pay attention!
Disallow positional parameters (but still allow some)
One can also define that positional parameters should generally be disallowed, but still allow it for some (ideally only a selected few) parameters.
The remaining arguments must then be provided by named parameters.
Interestingly (and contradicting the official documentation), this is the default behaviour anyways, so PositionalBinding=$False is not even strictly necessary:
[CmdletBinding(PositionalBinding = $False)] # <--
Param
(
[Parameter(Position=0)] $ParameterX
$ParameterY,
)
Write-Host "This is ParameterX: $ParameterX"
Write-Host "This is ParameterY: $ParameterY"
> .\test.ps1 x y
test.ps1 : A positional parameter cannot be found that accepts argument 'y'.
> .\test.ps1 x -ParameterY y
This is ParameterX: x
This is ParameterY: y
This could also be (mis)used in “interesting” ways:
Param
(
[Parameter(Position=0)] $ParameterX,
$ParameterY,
[Parameter(Position=2)] $ParameterZ
)
Write-Host "This is ParameterX: $ParameterX"
Write-Host "This is ParameterY: $ParameterY"
Write-Host "This is ParameterZ: $ParameterZ"
> .\test.ps1 x
This is ParameterX: x
This is ParameterY:
This is ParameterZ:
> .\test.ps1 x y z
test.ps1: A positional parameter cannot be found that accepts argument 'z'.
> .\test.ps1 x -ParameterY y z
This is ParameterX: x
This is ParameterY: y
This is ParameterZ: z
So, in short: Nice, but take care when using it!
Based on:
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
$InputStraccepts 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 (58)
How To (73)
Journal (18)
Miscellaneous (4)
News & Announcements (21)
On Software (12)
Powershell (1)
Projects (26)