Function
Parameters are wrapped inside a function block with param(...)
function Foo {
param (
$Foo
)
}2
3
4
5
NOTE
Default type of a parameter is System.Object.
Implicit Parameter
If no parameter name was set, all value passed in will be captured by $args, an object[].
The following example makes an alias for Neovim to mimic the original Vim cli.
function vim {
nvim --clean -c 'source ~/.vimrc' @args
}
vim ./foo.txt2
3
4
5
NOTE
$args is not available when any of ParameterAttribute and CmdletBinding is applied on the function.
Positional Parameter
Positional parameters allows passing values with explicit names.
function Foo {
param (
[string] $Foo,
[string] $Bar
)
Write-Output "$Foo $Bar"
}
Foo -Foo foo -Bar bar
Foo foo bar # it's the same #2
3
4
5
6
7
8
9
10
11
Or use a explicit position argument on attribute.
function Foo {
param (
[Parameter(Position = 1)]
[string] $Bar
[Parameter(Position = 0)]
[string] $Foo,
)
Write-Output "$Foo $Bar"
}
Foo -Foo foo -Bar bar
Foo foo bar # it's the same #2
3
4
5
6
7
8
9
10
11
12
13
PowerShell starts counting the position when there's a value belonging to no explicit parameter name. Assuming -Flag is a switch, -Foo has position 0, the value foo will be assigned to -Foo.
Foo -Flag fooFlags
Defining flags that represents a toggle needs a special type called switch. switch has the same nature of bool, but bool parameter requires explicit assignment when the function being called. While switch will remain $false when unspecified.
function Foo {
param (
[switch]$Foo
[bool]$Bar
)
}
# this is why we should use `switch` instead.
Foo -Foo -Bar $true2
3
4
5
6
7
8
9
Manual assignment is also available:
Foo -f:$false -b $trueDefault Parameter
- Explicitly typed parameters can have implicit default value.
switchandboolis$falseby default.stringis[string]::Emptyby default.- Numeric types are zero value by default.
- Parameters without type annotation are always typed as
objectwhich has the default value$null. - Can override default value by
=in declaration.
& { param([string]$name) $name -eq [string]::Empty } # True
& { param($name) $name -eq $null } # True
& { param([int]$age) $age -eq 0 } # True
& { param([switch]$is) $is -eq $false } # True
function Foo {
param (
[string]$foo = "foo"
)
}2
3
4
5
6
7
8
9
10
11
NOTE
For overriding default parameter outside the function, see $PSDefaultParameterValues
Required Parameter
All parameters are optional by default. Use [Parameter(Mandatory=$true)] to mark it as required.
param (
[Parameter(Mandatory=$true)]
[string]$RequiredName
)2
3
4
NOTE
You can omit assignment for boolean attribute parameter.
param (
[Parameter(Mandatory)] # Mandatory is true now #
[string]$RequiredName
)2
3
4
Parameter Alias
Parameters can have aliases. It's not needed for most of time though since pwsh can distinguish option by leading string.
function Person {
param (
[Alias('n')]
[string]$Name,
[Alias('a', 'yearsold')] # can have multiple aliases! #
[int]$Age
)
Write-Host "Name: $Name, Age: $Age"
}
Person -n "Alice" -a 302
3
4
5
6
7
8
9
10
11
12
Parameter Validation
Pass a validation logic as script block to ValidateScript attribute, $_ represents singular value of the parameter or current item of a collection. Will throw an error if any parameter does not satisfies the condition.
param (
[ValidateScript({ ($_ % 2) -ne 0 })]
[int[]]$Odd
[ValidateScript({ $_.Length < 5 }, ErrorMessage = "{0} is not valid")] # 0 is the input value
[string]$Name
)2
3
4
5
6
ValidateLength(min, max)ValidateCount(min, max)AllowNull()AllowEmptyString()AllowEmptyCollection()ValidatePattern(regex)ValidateRange(min, max)- or any value of enum
ValidateRangeKind:Positive,Negative,NonNegative,NonPositive
ps1param( [ValidateRange("Positive")] [int]$Number )1
2
3
4- or any value of enum
ValidateSet(foo, bar, ...): provides completion for predefined entriesValidateNotNull()ValidateNotNullOr()ValidateNotNullOrEmpty(): not a empty string or collection or nullValidateNotNullOrWhiteSpace()ValidateDrive(drive, ...): check whether specified path is valid from certain PSDriveps1param( [ValidateDrive("C", "D")] [string]$Path )1
2
3
4
Parameter Auto-Conversion
For flexibility, PowerShell has no strict type checking on parameter, but it handles implicit conversion on arguments. Any argument cannot be converted to the target type specified would cease the execution.
Such auto-conversion is quite exhaustive:
- string evaluation by
ToString
& { param([string]$foo) $foo.GetType().Name } -foo 123 # String- number parsing from string such as
[int]::Parse
& { param([int]$foo) $foo.GetType().Name } -foo "123" # Int32- boolean conversion from integer(positive is true, 0 is false)
& { param([boolean]$foo) $foo } -foo 0 # False
& { param([boolean]$foo) $foo } -foo 123 # True2
- argument passed to constructor
# directly passed to ctor since argument type matched with parameter type of ctor
& { param([System.IO.FileInfo]$foo) $foo.FullName } -foo "foo" # ~/foo
# eval 123 to string on ctor
& { param([System.IO.FileInfo]$foo) $foo.FullName } -foo 123 # ~/1232
3
4
Bounded Parameters
Parameters having manually specified value would be recorded into an dictionary $PSBoundParameters. Such key-value pairs only stores initially specified value of those parameters.
Closure & Currying
Closure in PowerShell requires a [scriptblock] with GetNewClosure being called, you can't play it directly with a normal function.
As documentation described:
GetNewClosurereturns a new scriptblock bound to a module. Any local variables in the callers context will be copied into the module.
$bar = 123;
$closure = {
param ($foo)
$bar + $foo
}.GetNewClosure()
& $closure 1 # 1242
3
4
5
6
7
Now we've understand how closure work, we could implement a simple currying like so:
function Currying {
param ($prev)
{ param($final) $prev + $final }.GetNewClosure()
}
$currying = Currying -prev 1
$result = & $currying -final 2 # 32
3
4
5
6
7
Pass By Reference
Parameter passed by reference is implemented by a wrapper System.Management.Automation.PSReference. Value types are passed by value by default, the pass as reference, mark the parameter with [ref]. Casting to [ref] generates a new wrapper containing the value.
function Foo {
param ([ref][int]$foo)
$foo.Value = 250
$foo.Value
}
$bar = 1
Foo ([ref]$bar)
$bar # 250 #2
3
4
5
6
7
8
9
NOTE
[ref] can only be marked before type annotation.
Named Blocks
In a simple function where there's only one series of parameters being taken, we don't have to use any complex logic. But things will explode when we're dealing with a pipeline input which might bring multiple objects.
The pipeline mechanism is essentially based on the Enumerator so if we collect all items into a new collection as parameter value, it can be a huge performance issue. So named blocks are essentially to defined a shared process logic for each object in the pipeline input, and other logic like initialization and finalization.
begin: state initialization for the pipeline iteration.process: logic for each pipeline iteration.end: final action for the completed pipeline iteration.clean: afinallyblock to execute clean up no matter what happens.(PowerShell 7.3+)
function Foo {
begin {}
process {}
end {}
clean {}
}2
3
4
5
6
NOTE
When no named block were specified, end block is used to represent the whole logic of a simple function.
function Foo {
end {
echo hello
}
}
# equivalent to
function Foo {
echo hello
}2
3
4
5
6
7
8
9
Filter Function
Filter is a special kind of function that implicitly accepts pipeline to perform transformation(select) or filtering(where). Filter is useful when you need to reuse the same logic for unknown pipeline manipulations, reducing hard coding.
filter Foo {
"$_" # can transform
}
# equivalent to
function Foo {
process {
"$_"
}
}2
3
4
5
6
7
8
9
10
Mimicking Cmdlet
A function would generally not acting a cmdlet unless it was annotated with CmdletBinding() attribute.
CmdletBinding() can have the following properties:
DefaultParameterSetName: pwsh will prefer this name when there's a ambiguity between syntax provided.HelpURI: link to documentationSupportsPaging: implicitly adds parameters-First,-Skip,-IncludeTotalCount, value accessible by$PSCmdlet.PagingParametersps1function foo { [CmdletBinding(SupportsPaging)] param() $PSCmdlet.PagingParameters.Skip $PSCmdlet.PagingParameters.First $PSCmdlet.PagingParameters.IncludeTotalCount }1
2
3
4
5
6
7SupportsShouldProcess: implicitly adds-Confirmand-WhatIfConfirmImpact: specify impact of-ConfirmPositionalBinding:
Parameter Set
How a same cmdlet manage different syntax for different usages? The trick is Parameter Set. Parameter Set is a classification on parameter to distinguish or limit the use of parameters from scenarios.
- a parameter set must have at least one unique parameter to others to identify the set
- a parameter can be member of multiple parameter sets.
- a parameter can have different roles in different Parameter Set, can be mandatory in one and optional in another
- a parameter without explicit Parameter Set belongs to all other Parameter Set
- at least one parameter in the Parameter Set is mandatory
- only one parameter in set can accept
ValueFromPipeline
Parameter Set Identifier at Runtime
$PSCmdlet.ParameterSetName reflects the Parameter Set been chosen when a cmdlet is executing with certain syntax.
Common Parameters
Any function or cmdlet applied with CmdletBinding() or Parameter() attribute has the following implicit parameters added by PowerShell:
ErrorAction (ea): specify action on error
ErrorVariable (ev): declare inline and store the error on the variable instead of
$Error. Use-ErrorVariable +varto append error to the variableps1gcm foo -ev bar # inline declaration for $bar $bar # contains error gcm baz -ev +bar $bar # contains two errors1
2
3
4NOTE
The inline variable is an
ArrayListInformationAction (infa): similar to
eaInformationVariable (iv): similar to
evWarningAction (wa): similar to
eaWarningVariable (wv): similar to
evProgressAction (proga)
OutVariable (ov): declare inline and store the output to the variable. Similar to
ev. It's interesting that-OutVariablecollects incrementally. It collects new item from pipeline on each iteration.ps11..5 | % { $_ } -OutVariable foo | % { "I am $foo" } # I am 1 # I am 1,2 # I am 1,2,3 # I am 1,2,3,4 # I am 1,2,3,4,51
2
3
4
5
6Debug (db): print verbose debug message, overrides
$DebugPreferenceOutBuffer (ob)
PipelineVariable (pv)
Verbose (vb): whether display the verbose message from
Write-Verbose
Mitigation Parameters
Mitigation parameters were added when CmdletBinding(SupportsShouldProcess) was applied.
- WhatIf (wi): shows explanation for the command without executing it.
- Confirm (cf): ask for confirmation when executing the command.
Return
PowerShell allows implicit return, and multiple implicit returns.
NOTE
Implicit returns are auto-collected as an array or single value. And it does not print out anything.
function Sum {
param([int]$l, [int]$r)
$l + $r # implicit return #
}
# You won't need to declare an array and append it on each loop!
# they're collected automatically as they're implicit returns
function Foo {
for($i = 0; $i -lt 10; $i = $i + 1) {
$i
}
}
(Foo).GetType().Name # object[] #2
3
4
5
6
7
8
9
10
11
12
13
14
Explicit return is surely supported, but more like a necessity to exit inside a flow.
function Sum {
param([int]$l, [int]$r)
return $l + $r # explicit return #
$r + $l # not reachable #
}2
3
4
5
Output Type
OutputTypeAttribute(type: System.Type | System.String, parameterSet?: System.String) matters when you need auto generated help for the function you write. A function can have different return types for different parameter sets. PowerShell never do type checking by this attribute, it's just responsible for help generation, write with carefulness!
[CmdletBinding(DefaultParameterSetName = 'ID')]
[OutputType('System.Int32', ParameterSetName = 'ID')]
[OutputType([string], ParameterSetName = 'Name')]
param (
[Parameter(Mandatory, ParameterSetName = 'ID')]
[int[]]$UserID,
[Parameter(Mandatory, ParameterSetName = 'Name')]
[string[]]$UserName
)2
3
4
5
6
7
8
9