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 Set-Thingy
{
# ...
}
Parameters in the function head
function Set-Thingy ($Param1, [int] $Param2)
{
# ...
}
Or parameters in the parameter block in the function body:
function Set-Thingy
{
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 parameter 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 Set-Thingy
{
[CmdletBinding()]
Param
(
[string[]] $InputStr = @("Example/Default One", "Example/Default Two"')
)
foreach ($item in $InputStr) { Write-Output $item }
}
Set-Thingy -InputStr "foo", "bar"
Some notes:
- A better, more flexible approach is to handle input from the pipeline, which includes support for arrays “for free” (as described here).
- 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
For Parameters
function Set-Thingy
{
[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 parameter name is changed: The old calls will still work.
- Offering different terms for different users or use cases.
That means that the call to Set-Thingy -InputStr "Blah" -Foo or Set-Thingy -Text "Blah" -Bar or Set-Thingy -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 Set-Thingy).Parameters.parameter | select Name, Aliases
name aliases
---- -------
Foo Bar
InputStr Data, Text
> (get-command -name Set-Thingy).Parameters.Values | select Name, Aliases
Name Aliases
---- -------
InputStr {Data, Text}
Foo {Bar}
Verbose {vb}
[...]
> (get-command -name Set-Thingy).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 Set-Thingy).Parameters['InputStr'].Aliases
Data
Text
See also The Snazzy Secret of PowerShell Parameter Aliases, a very helpful tutorial on that topic.
For (Module) Functions
function Set-Thingy
{
[CmdletBinding()]
[Alias('st1', 'st2')] # <--- !
Param
(
[Alias('Data', 'Text')]
[string[]] $InputStr
)
# ...
}
Things to keep in mind:
-
This won’t work for standalone scripts or functions; it must be in a function body in a module (from which its being exported)!
-
If you define a function alias in a module and want to use them in a normal PS session, they must also be exported from the module:
Either in the manifest (*.psd1) withAliasesToExport = "*", in the script (*.psm1) with a call toExport-ModuleMember -Alias "*".Instead all (
"*"), one could also be specific and list alias individually. But that would require to know the alias before, not the function name. And since I usually auto-generate my modules, I don’t have that information while collecting the data (maybe it’s doable, but currently I don’t need it).Important GOTCHA (Bug?): The
AliasesToExport = ...in the manifest will be ignored/reset, if a later call toUpdate-ModuleManifesttweaks certain/any other settings in the manifest!
Workaround:Update-ModuleManifest -AliasesToExport "*" <other stuff>! -
If you use a Powershell profile, consider that the first assignment wins!
Example:- Module is imported (and the alias is defined there, as shown above)
- If
New-Aliasis used later in the profile to assign the same alias again, it will result in a an error: “The alias is not allowed, because an alias with the name ‘st1’ already exists” (Set-Aliaswouldn’t complain, but simply overwrite it)!
-
Get-Aliascan be used to get the information later (it seems, at first glance, thatGet-HelporGet-Commanddon’t offer that data; but maybe it just needs more work [as above, with the parameters…]):CommandType Name Version Source ----------- ---- ------- ------ Alias st1 -> Set-Thingy 0.0.1 ModuleX
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! ↩︎
DevLog (19)
Film & Television (58)
Hardware (3)
How-To (74)
News & Announcements (20)
On Software (12)
Projects (26)