8. Statements

8.1 Statement blocks and lists

Syntax:

Tip

The ~opt~ notation in the syntax definitions indicates that the lexical entity is optional in the syntax.

statement-block:
    new-lines~opt~ { statement-list~opt~ new-lines~opt~ }

statement-list:
    statement
    statement-list statement

statement:
    if-statement
    label~opt~ labeled-statement
    function-statement
    flow-control-statement statement-terminator
    trap-statement
    try-statement
    data-statement
    inlinescript-statement
    parallel-statement
    sequence-statement
    pipeline statement-terminator

statement-terminator:
    ;
    new-line-character

Description:

A statement specifies some sort of action that is to be performed. Unless indicated otherwise within this clause, statements are executed in lexical order.

A statement-block allows a set of statements to be grouped into a single syntactic unit.

8.1.1 Labeled statements

Syntax:

labeled-statement:
    switch-statement
    foreach-statement
    for-statement
    while-statement
    do-statement

Description:

An iteration statement (§8.4) or a switch statement (§8.6) may optionally be preceded immediately by one statement label, label. A statement label is used as the optional target of a break (§8.5.1) or continue (§8.5.2) statement. However, a label does not alter the flow of control.

White space is not permitted between the colon (:) and the token that follows it.

Examples:

:go_here while ($j -le 100) {
    # ...
}

:labelA
for ($i = 1; $i -le 5; ++$i) {
    :labelB
    for ($j = 1; $j -le 3; ++$j) {
        :labelC
        for ($k = 1; $k -le 2; ++$k) {
            # ...
        }
    }
}

8.1.2 Statement values

The value of a statement is the cumulative set of values that it writes to the pipeline. If the statement writes a single scalar value, that is the value of the statement. If the statement writes multiple values, the value of the statement is that set of values stored in elements of an unconstrained 1-dimensional array, in the order in which they were written. Consider the following example:

$v = for ($i = 10; $i -le 5; ++$i) { }

There are no iterations of the loop and nothing is written to the pipeline. The value of the statement is $null.

$v = for ($i = 1; $i -le 5; ++$i) { }

Although the loop iterates five times nothing is written to the pipeline. The value of the statement is $null.

$v = for ($i = 1; $i -le 5; ++$i) { $i }

The loop iterates five times each time writing to the pipeline the int value $i. The value of the statement is object[] of Length 5.

$v = for ($i = 1; $i -le 5; ) { ++$i }

Although the loop iterates five times nothing is written to the pipeline. The value of the statement is $null.

$v = for ($i = 1; $i -le 5; ) { (++$i) }

The loop iterates five times with each value being written to the pipeline. The value of the statement is object[] of Length 5.

$i = 1; $v = while ($i++ -lt 2) { $i }

The loop iterates once. The value of the statement is the int with value 2.

Here are some other examples:

# if $count is not currently defined then define it with int value 10
$count = if ($count -eq $null) { 10 } else { $count }

$i = 1
$v = while ($i -le 5) {
    $i                   # $i is written to the pipeline
    if ($i -band 1) {

        "odd"            # conditionally written to the pipeline

    }

    ++$i                 # not written to the pipeline

}
# $v is object[], Length 8, value 1,"odd",2,3,"odd",4,5,"odd"

8.2 Pipeline statements

Syntax:

pipeline:
    assignment-expression
    expression redirections~opt~ pipeline-tail~opt~
    command verbatim-command-argument~opt~ pipeline-tail~opt~

assignment-expression:
    expression assignment-operator statement

pipeline-tail:
    | new-lines~opt~ command
    | new-lines~opt~ command pipeline-tail

command:
    command-name command-elements~opt~
    command-invocation-operator command-module~opt~ command-name-expr command-elements~opt~

command-invocation-operator: one of
    &   .

command-module:
    primary-expression

command-name:
    generic-token
    generic-token-with-subexpr

generic-token-with-subexpr:
    No whitespace is allowed between ) and command-name.
    generic-token-with-subexpr-start statement-list~opt~ )

command-namecommand-name-expr:
    command-name

primary-expressioncommand-elements:
    command-element
    command-elements command-element

command-element:
    command-parameter
    command-argument
    redirection

command-argument:
    command-name-expr

verbatim-command-argument:
    --% verbatim-command-argument-chars

Description:

redirections is discussed in §7.12; assignment-expression is discussed in §7.11; and the command-invocation-operator dot (.) is discussed in §3.5.5. For a discussion of argument-to-parameter mapping in command invocations, see §8.14.

The first command in a pipeline is an expression or a command invocation. Typically, a command invocation begins with a command-name, which is usually a bare identifier. command-elements represents the argument list to the command. A newline or n unescaped semicolon terminates a pipeline.

A command invocation consists of the command's name followed by zero or more arguments. The rules governing arguments are as follows:

  • An argument that is not an expression, but which contains arbitrary text without unescaped white space, is treated as though it were double quoted. Letter case is preserved.

  • Variable substitution and sub-expression expansion (§2.3.5.2) takes place inside expandable-string-literals and expandable-here-string-literals.

  • Text inside quotes allows leading, trailing, and embedded white space to be included in the argument's value. [Note: The presence of whitespace in a quoted argument does not turn a single argument into multiple arguments. end note]

  • Putting parentheses around an argument causes that expression to be evaluated with the result being passed instead of the text of the original expression.

  • To pass an argument that looks like a switch parameter (§2.3.4) but is not intended as such, enclose that argument in quotes.

  • When specifying an argument that matches a parameter having the [switch] type constraint (§8.10.5), the presence of the argument name on its own causes that parameter to be set to $true. However, the parameter's value can be set explicitly by appending a suffix to the argument. For example, given a type constrained parameter p, an argument of -p:$true sets p to True, while -p:$false sets p to False.

  • An argument of -- indicates that all arguments following it are to be passed in their actual form as though double quotes were placed around them.

  • An argument of --% indicates that all arguments following it are to be passed with minimal parsing and processing. This argument is called the verbatim parameter. Arguments after the verbatim parameter are not PowerShell expressions even if they are syntactically valid PowerShell expressions.

If the command type is Application, the parameter --% is not passed to the command. The arguments after --% have any environment variables (strings surrounded by %) expanded. For example:

echoargs.exe --% "%path%" # %path% is replaced with the value $env:path

The order of evaluation of arguments is unspecified.

For information about parameter binding see §8.14. For information about name lookup see §3.8.

Once argument processing has been completed, the command is invoked. If the invoked command terminates normally (§8.5.4), control reverts to the point in the script or function immediately following the command invocation. For a description of the behavior on abnormal termination see break (§8.5.1), continue (§8.5.2), throw (§8.5.3), exit (§8.5.5), try (§8.7), and trap (§8.8).

Ordinarily, a command is invoked by using its name followed by any arguments. However, the command-invocation operator, &, can be used. If the command name contains unescaped white space, it must be quoted and invoked with this operator. As a script block has no name, it too must be invoked with this operator. For example, the following invocations of a command call Get-Factorial are equivalent:

Get-Factorial 5
& Get-Factorial 5
& "Get-Factorial" 5

Direct and indirect recursive function calls are permitted. For example,

function Get-Power([int]$x, [int]$y) {
    if ($y -gt 0) { return $x * (Get-Power $x (--$y)) }
    else { return 1 }
}

Examples:

New-Object 'int[,]' 3,2
New-Object -ArgumentList 3,2 -TypeName 'int[,]'

dir e:\PowerShell\Scripts\*statement*.ps1 | Foreach-Object {$_.Length}

dir e:\PowerShell\Scripts\*.ps1 | Select-String -List "catch" | Format-Table path,linenumber -AutoSize

8.3 The if statement

Syntax:

if-statement:
    if new-lines~opt~ ( new-lines~opt~ pipeline new-lines~opt~ ) statement-block
        elseif-clauses~opt~ else-clause~opt~

elseif-clauses:
    elseif-clause
    elseif-clauses elseif-clause

elseif-clause:
    new-lines~opt~ elseif new-lines~opt~ ( new-lines~opt~ pipeline new-lines~opt~ ) statement-block

else-clause:
    new-lines~opt~ else statement-block

Description:

The pipeline controlling expressions must have type bool or be implicitly convertible to that type. The else-clause is optional. There may be zero or more elseif-clauses.

If the top-level pipeline tests True, then its statement-block is executed and execution of the statement terminates. Otherwise, if an elseif-clause is present, if its pipeline tests True, then its statement-block is executed and execution of the statement terminates. Otherwise, if an else-clause is present, its statement-block is executed.

Examples:

$grade = 92
if ($grade -ge 90) { "Grade A" }
elseif ($grade -ge 80) { "Grade B" }
elseif ($grade -ge 70) { "Grade C" }
elseif ($grade -ge 60) { "Grade D" }
else { "Grade F" }

8.4 Iteration statements

8.4.1 The while statement

Syntax:

while-statement:
    while new-lines~opt~ ( new-lines~opt~ while-condition new-lines~opt~ ) statement-block

while-condition:
    new-lines~opt~ pipeline

Description:

The controlling expression while-condition must have type bool or be implicitly convertible to that type. The loop body, which consists of statement-block, is executed repeatedly until the controlling expression tests False. The controlling expression is evaluated before each execution of the loop body.

Examples:

$i = 1
while ($i -le 5) {                     # loop 5 times
    "{0,1}`t{1,2}" -f $i, ($i*$i)
    ++$i
}

8.4.2 The do statement

Syntax:

do-statement:
    do statement-block new-lines~opt~ while new-lines~opt~ ( while-condition new-lines~opt~ )
    do statement-block new-lines~opt~ until new-lines~opt~ ( while-condition new-lines~opt~ )

while-condition:
    new-lines~opt~ pipeline

Description:

The controlling expression while-condition must have type bool or be implicitly convertible to that type. In the while form, the loop body, which consists of statement-block, is executed repeatedly while the controlling expression tests True. In the until form, the loop body is executed repeatedly until the controlling expression tests True. The controlling expression is evaluated after each execution of the loop body.

Examples:

$i = 1
do {
    "{0,1}`t{1,2}" -f $i, ($i * $i)
}
while (++$i -le 5)                 # loop 5 times

$i = 1
do {
    "{0,1}`t{1,2}" -f $i, ($i * $i)
}
until (++$i -gt 5)                 # loop 5 times

8.4.3 The for statement

Syntax:

for-statement:
    for new-lines~opt~ (
        new-lines~opt~ for-initializer~opt~ statement-terminator
        new-lines~opt~ for-condition~opt~ statement-terminator
        new-lines~opt~ for-iterator~opt~
        new-lines~opt~ ) statement-block

    for new-lines~opt~ (
        new-lines~opt~ for-initializer~opt~ statement-terminator
        new-lines~opt~ for-condition~opt~
        new-lines~opt~ ) statement-block

    for new-lines~opt~ (
        new-lines~opt~ for-initializer~opt~
        new-lines~opt~ ) statement-block

for-initializer:
    pipeline

for-condition:
    pipeline

for-iterator:
    pipeline

Description:

The controlling expression for-condition must have type bool or be implicitly convertible to that type. The loop body, which consists of statement-block, is executed repeatedly while the controlling expression tests True. The controlling expression is evaluated before each execution of the loop body.

Expression for-initializer is evaluated before the first evaluation of the controlling expression. Expression for-initializer is evaluated for its side effects only; any value it produces is discarded and is not written to the pipeline.

Expression for-iterator is evaluated after each execution of the loop body. Expression for-iterator is evaluated for its side effects only; any value it produces is discarded and is not written to the pipeline.

If expression for-condition is omitted, the controlling expression tests True.

Examples:

for ($i = 5; $i -ge 1; --$i) { # loop 5 times
    "{0,1}`t{1,2}" -f $i, ($i * $i)
}

$i = 5
for (; $i -ge 1; ) { # equivalent behavior
    "{0,1}`t{1,2}" -f $i, ($i * $i)
    --$i
}

8.4.4 The foreach statement

Syntax:

foreach-statement:
    foreach new-lines~opt~ foreach-parameter~opt~ new-lines~opt~
        ( new-lines~opt~ variable new-lines~opt~ *in* new-lines~opt~ pipeline
        new-lines~opt~ ) statement-block

foreach-parameter:
    -parallel

Description:

The loop body, which consists of statement-block, is executed for each element designated by the variable variable in the collection designated by pipeline. The scope of variable is not limited to the foreach statement. As such, it retains its final value after the loop body has finished executing. If pipeline designates a scalar (excluding the value $null) instead of a collection, that scalar is treated as a collection of one element. If pipeline designates the value $null, pipeline is treated as a collection of zero elements.

If the foreach-parameter -parallel is specified, the behavior is implementation defined.

The foreach-parameter ‑parallel is only allowed in a workflow (§8.10.2).

Every foreach statement has its own enumerator, $foreach (§2.3.2.2, §4.5.16), which exists only while that loop is executing.

The objects produced by pipeline are collected before statement-block begins to execute. However, with the ForEach-Object cmdlet, statement-block is executed on each object as it is produced.

Examples:

$a = 10, 53, 16, -43
foreach ($e in $a) {
    ...
}
$e # the int value -43

foreach ($e in -5..5) {
    ...
}

foreach ($t in [byte], [int], [long]) {
    $t::MaxValue # get static property
}

foreach ($f in Get-ChildItem *.txt) {
    ...
}

$h1 = @{ FirstName = "James"; LastName = "Anderson"; IDNum = 123 }
foreach ($e in $h1.Keys) {
    "Key is " + $e + ", Value is " + $h1[$e]
}

8.5 Flow control statements

Syntax:

flow-control-statement:
    break label-expression~opt~
    continue label-expression~opt~
    throw pipeline~opt~
    return pipeline~opt~
    exit pipeline~opt~

label-expression:
    simple-name
    unary-expression

Description:

A flow-control statement causes an unconditional transfer of control to some other location.

8.5.1 The break statement

Description:

A break statement with a label-expression is referred to as a labeled break statement. A break statement without a label-expression is referred to as an unlabeled break statement.

Outside a trap statement, an unlabeled break statement directly within an iteration statement (§8.4) terminates execution of that smallest enclosing iteration statement. An unlabeled break statement directly within a switch statement (§8.6) terminates pattern matching for the current switch's switch-condition. See (§8.8) for details of using break from within a trap statement.

An iteration statement or a switch statement may optionally be preceded immediately by one statement label (§8.1.1).Such a statement label may be used as the target of a labeled break statement, in which case, that statement terminates execution of the targeted enclosing iteration statement.

A labeled break need not be resolved in any local scope; the search for a matching label may continue up the calling stack even across script and function-call boundaries. If no matching label is found, the current command invocation is terminated.

The name of the label designated by label-expression need not have a constant value.

If label-expression is a unary-expression, it is converted to a string.

Examples:

$i = 1
while ($true) { # infinite loop
    if ($i * $i -gt 100) {
        break # break out of current while loop
    }
    ++$i
}

$lab = "go_here"
:go_here
for ($i = 1; ; ++$i) {
    if ($i * $i -gt 50) {
        break $lab # use a string value as target
    }
}

:labelA
for ($i = 1; $i -le 2; $i++) {

    :labelB
    for ($j = 1; $j -le 2; $j++) {

        :labelC
        for ($k = 1; $k -le 3; $k++) {
            if (...) { break labelA }
        }
    }
}

8.5.2 The continue statement

Description:

A continue statement with a label-expression is referred to as a labeled continue statement. A continue statement without a label-expression is referred to as an unlabeled continue statement.

The use of continue from within a trap statement is discussed in §8.8.

An unlabeled continue statement within a loop terminates execution of the current loop and transfers control to the closing brace of the smallest enclosing iteration statement (§8.4). An unlabeled continue statement within a switch terminates execution of the current switch iteration and transfers control to the smallest enclosing switch's switch-condition (§8.6).

An iteration statement or a switch statement (§8.6) may optionally be preceded immediately by one statement label (§8.1.1). Such a statement label may be used as the target of an enclosed labeled continue statement, in which case, that statement terminates execution of the current loop or switch iteration, and transfers control to the targeted enclosing iteration or switch statement label.

A labeled continue need not be resolved in any local scope; the search for a matching label may continue up the calling stack even across script and function-call boundaries. If no matching label is found, the current command invocation is terminated.

The name of the label designated by label-expression need not have a constant value.

If label-expression is a unary-expression, it is converted to a string.

Examples:

$i = 1
while (...) {
    ...
    if (...) {
        continue # start next iteration of current loop
    }
    ...
}

$lab = "go_here"
:go_here
for (...; ...; ...) {
    if (...) {
        continue $lab # start next iteration of labeled loop
    }
}

:labelA
for ($i = 1; $i -le 2; $i++) {

    :labelB
    for ($j = 1; $j -le 2; $j++) {

        :labelC
        for ($k = 1; $k -le 3; $k++) {
            if (...) { continue labelB }
        }
    }
}

8.5.3 The throw statement

Description:

An exception is a way of handling a system- or application-level error condition. The throw statement raises an exception. (See §8.7 for a discussion of exception handling.)

If pipeline is omitted and the throw statement is not in a catch-clause, the behavior is implementation defined. If pipeline is present and the throw statement is in a catch-clause, the exception that was caught by that catch-clause is re-thrown after any finally-clause associated with the catch-clause is executed.

If pipeline is present, the type of the exception thrown is implementation defined.

When an exception is thrown, control is transferred to the first catch clause in an enclosing try statement that can handle the exception. The location at which the exception is thrown initially is called the throw point. Once an exception is thrown the steps described in §8.7 are followed repeatedly until a catch clause that matches the exception is found or none can be found.

Examples:

throw
throw 100
throw "No such record in file"

If pipeline is omitted and the throw statement is not from within a catch-clause, the text "ScriptHalted" is written to the pipeline, and the type of the exception raised is System.Management.Automation.RuntimeException.

If pipeline is present, the exception raised is wrapped in an object of type System.Management.Automation.RuntimeException, which includes information about the exception as a System.Management.Automation.ErrorRecord object (accessible via $_).

Example 1: throw 123 results in an exception of type RuntimeException. From within the catch block, $_.TargetObject contains the object wrapped inside, in this case, a System.Int32 with value 123.

Example 2: throw "xxx" results in an exception of type RuntimeException. From within the catch block, $_.TargetObject contains the object wrapped inside, in this case, a System.String with value "xxx".

Example 3: throw 10,20 results in an exception of type RuntimeException. From within the catch block, $_.TargetObject contains the object wrapped inside, in this case, a System.Object[], an unconstrained array of two elements with the System.Int32` values 10 and 20.

8.5.4 The return statement

Description:

The return statement writes to the pipeline the value(s) designated by pipeline, if any, and returns control to the function or script's caller. A function or script may have zero or more return statements.

If execution reaches the closing brace of a function an implied return without pipeline is assumed.

The return statement is a bit of "syntactic sugar" to allow programmers to express themselves as they can in other languages; however, the value returned from a function or script is actually all of the values written to the pipeline by that function or script plus any value(s) specified by pipeline. If only a scalar value is written to the pipeline, its type is the type of the value returned; otherwise, the return type is an unconstrained 1-dimensional array containing all the values written to the pipeline.

Examples:

function Get-Factorial ($v) {
    if ($v -eq 1) {
        return 1 # return is not optional
    }

    return $v * (Get-Factorial ($v - 1)) # return is optional
}

The caller to Get-Factorial gets back an int.

function Test {
    "text1" # "text1" is written to the pipeline
    # ...
    "text2" # "text2" is written to the pipeline
    # ...
    return 123 # 123 is written to the pipeline
}

The caller to Test gets back an unconstrained 1-dimensional array of three elements.

8.5.5 The exit statement

Description:

The exit statement terminates the current script and returns control and an exit code to the host environment or the calling script. If pipeline is provided, the value it designates is converted to int, if necessary. If no such conversion exists, or if pipeline is omitted, the int value zero is returned.

Examples:

exit $count # terminate the script with some accumulated count

8.6 The switch statement

Syntax:

switch-statement:
    switch new-lines~opt~ switch-parameters~opt~ switch-condition switch-body

switch-parameters:
    switch-parameter
    switch-parameters switch-parameter

switch-parameter:
    -regex
    -wildcard
    -exact
    -casesensitive
    -parallel

switch-condition:
    ( new-lines~opt~ pipeline new-lines~opt~ )
    -file new-lines~opt~ switch-filename

switch-filename:
    command-argument
    primary-expression

switch-body:
    new-lines~opt~ { new-lines~opt~ switch-clauses }

switch-clauses:
    switch-clause
    switch-clauses switch-clause

switch-clause:
    switch-clause-condition statement-block statement-terimators~opt~

switch-clause-condition:
    command-argument
    primary-expression

Description:

If switch-condition designates a single value, control is passed to one or more matching pattern statement blocks. If no patterns match, some default action can be taken.

A switch must contain one or more switch-clauses, each starting with a pattern (a non-default switch clause), or the keyword default (a default switch clause). A switch must contain zero or one default switch clauses, and zero or more non-default switch clauses. Switch clauses may be written in any order.

Multiple patterns may have the same value. A pattern need not be a literal, and a switch may have patterns with different types.

If the value of switch-condition matches a pattern value, that pattern's statement-block is executed. If multiple pattern values match the value of switch-condition, each matching pattern's statement-block is executed, in lexical order, unless any of those statement-blocks contains a break statement (§8.5.1).

If the value of switch-condition does not match any pattern value, if a default switch clause exists, its statement-block is executed; otherwise, pattern matching for that switch-condition is terminated.

Switches may be nested, with each switch having its own set of switch clauses. In such instances, a switch clause belongs to the innermost switch currently in scope.

On entry to each statement-block, $_ is automatically assigned the value of the switch-condition that caused control to go to that statement-block. $_ is also available in that statement-block's switch-clause-condition.

Matching of non-strings is done by testing for equality (§7.8.1).

If the matching involves strings, by default, the comparison is case-insensitive. The presence of the switch-parameter -casesensitive makes the comparison case-sensitive.

A pattern may contain wildcard characters (§3.15), in which case, wildcard string comparisons are performed, but only if the switch-parameter -wildcard is present. By default, the comparison is case-insensitive.

A pattern may contain a regular expression (§3.16), in which case, regular expression string comparisons are performed, but only if the switch-parameter -regex is present. By default, the comparison is case-insensitive. If -regex is present and a pattern is matched, $matches is defined in the switch-clause statement-block for that pattern.

A switch-parameter may be abbreviated; any distinct leading part of a parameter may be used. For example, ‑regex, ‑rege, ‑reg, ‑re, and ‑r are equivalent.

If conflicting switch-parameters are specified, the lexically final one prevails. The presence of ‑exact disables -regex and -wildcard; it has no affect on ‑case, however.

If the switch-parameter ‑parallel is specified, the behavior is implementation defined.

The switch-parameter ‑parallel is only allowed in a workflow (§8.10.2).

If a pattern is a script-block-expression, that block is evaluated and the result is converted to bool, if necessary. If the result has the value $true, the corresponding statement-block is executed; otherwise, it is not.

If switch-condition designates multiple values, the switch is applied to each value in lexical order using the rules described above for a switch-condition that designates a single value. Every switch statement has its own enumerator, $switch (§2.3.2.2, §4.5.16), which exists only while that switch is executing.

A switch statement may have a label, and it may contain labeled and unlabeled break (§8.5.1) and continue (§8.5.2) statements.

If switch-condition is -file switch-filename, instead of iterating over the values in an expression, the switch iterates over the values in the file designated by switch-filename.The file is read a line at a time with each line comprising a value. Line terminator characters are not included in the values.

Examples:

$s = "ABC def`nghi`tjkl`fmno @#$"
$charCount = 0; $pageCount = 0; $lineCount = 0; $otherCount = 0
for ($i = 0; $i -lt $s.Length; ++$i) {
    ++$charCount
    switch ($s[$i]) {
        "`n" { ++$lineCount }
        "`f" { ++$pageCount }
        "`t" { }
        " " { }
        default { ++$otherCount }
    }
}

switch -wildcard ("abc") {
    a* { "a*, $_" }
    ?B? { "?B? , $_" }
    default { "default, $_" }
}

switch -regex -casesensitive ("abc") {
    ^a* { "a*" }
    ^A* { "A*" }
}

switch (0, 1, 19, 20, 21) {
    { $_ -lt 20 } { "-lt 20" }
    { $_ -band 1 } { "Odd" }
    { $_ -eq 19 } { "-eq 19" }
    default { "default" }
}

8.7 The try/finally statement

Syntax:

try-statement:
    try statement-block catch-clauses
    try statement-block finally-clause
    try statement-block catch-clauses finally-clause

catch-clauses:
    catch-clause
    catch-clauses catch-clause

catch-clause:
    new-lines~opt~ catch catch-type-list~opt~
    statement-block

catch-type-list:
    new-lines~opt~ type-literal
    catch-type-list new-lines~opt~ , new-lines~opt~

type-literalfinally-clause:
    new-lines~opt~ finally statement-block

Description:

The try statement provides a mechanism for catching exceptions that occur during execution of a block. The try statement also provides the ability to specify a block of code that is always executed when control leaves the try statement. The process of raising an exception via the throw statement is described in §8.5.3.

A try block is the statement-block associated with the try statement. A catch block is the statement-block associated with a catch-clause. A finally block is the statement-block associated with a finally-clause.

A catch-clause without a catch-type-list is called a general catch clause.

Each catch-clause is an exception handler, and a catch-clause whose catch-type-list contains the type of the raised exception is a matching catch clause. A general catch clause matches all exception types.

Although catch-clauses and finally-clause are optional, at least one of them must be present.

The processing of a thrown exception consists of evaluating the following steps repeatedly until a catch clause that matches the exception is found.

  • In the current scope, each try statement that encloses the throw point is examined. For each try statement S, starting with the innermost try statement and ending with the outermost try statement, the following steps are evaluated:

    • If the try block of S encloses the throw point and if S has one or more catch clauses, the catch clauses are examined in lexical order to locate a suitable handler for the exception. The first catch clause that specifies the exception type or a base type of the exception type is considered a match. A general catch clause is considered a match for any exception type. If a matching catch clause is located, the exception processing is completed by transferring control to the block of that catch clause. Within a matching catch clause, the variable $_ contains a description of the current exception.

    • Otherwise, if the try block or a catch block of S encloses the throw point and if S has a finally block, control is transferred to the finally block. If the finally block throws another exception, processing of the current exception is terminated. Otherwise, when control reaches the end of the finally block, processing of the current exception is continued.

  • If an exception handler was not located in the current scope, the steps above are then repeated for the enclosing scope with a throw point corresponding to the statement from which the current scope was invoked.

  • If the exception processing ends up terminating all scopes, indicating that no handler exists for the exception, then the behavior is unspecified.

To prevent unreachable catch clauses in a try block, a catch clause may not specify an exception type that is equal to or derived from a type that was specified in an earlier catch clause within that same try block.

The statements of a finally block are always executed when control leaves a try statement. This is true whether the control transfer occurs as a result of normal execution, as a result of executing a break, continue, or return statement, or as a result of an exception being thrown out of the try statement.

If an exception is thrown during execution of a finally block, the exception is thrown out to the next enclosing try statement. If another exception was in the process of being handled, that exception is lost. The process of generating an exception is further discussed in the description of the throw statement.

try statements can co-exist with trap statements; see §8.8 for details.

Examples:

$a = new-object 'int[]' 10
$i = 20 # out-of-bounds subscript

while ($true) {
    try {
        $a[$i] = 10
        "Assignment completed without error"
        break
    }

    catch [IndexOutOfRangeException] {
        "Handling out-of-bounds index, >$_<`n"
        $i = 5
    }

    catch {
        "Caught unexpected exception"
    }

    finally {
        # ...
    }
}

Each exception thrown is raised as a System.Management.Automation.RuntimeException. If there are type-specific catch-clauses in the try block, the InnerException property of the exception is inspected to try and find a match, such as with the type System.IndexOutOfRangeException above.

8.8 The trap statement

Syntax:

trap-statement:
    *trap* new-lines~opt~ type-literal~opt~ new-lines~opt~ statement-block

Description:

A trap statement with and without type-literal is analogous to a catch block (§8.7) with and without catch-type-list, respectively, except that a trap statement can trap only one type at a time.

Multiple trap statements can be defined in the same statement-block, and their order of definition is irrelevant. If two trap statements with the same type-literal are defined in the same scope, the lexically first one is used to process an exception of matching type.

Unlike a catch block, a trap statement matches an exception type exactly; no derived type matching is performed.

When an exception occurs, if no matching trap statement is present in the current scope, a matching trap statement is searched for in the enclosing scope, which may involve looking in the calling script, function, or filter, and then in its caller, and so on. If the lookup ends up terminating all scopes, indicating that no handler exists for the exception, then the behavior is unspecified.

A trap statement's statement-body only executes to process the corresponding exception; otherwise, execution passes over it.

If a trap's statement-body exits normally, by default, an error object is written to the error stream, the exception is considered handled, and execution continues with the statement immediately following the one in the scope containing the trap statement that made the exception visible. The cause of the exception might be in a command called by the command containing the trap statement.

If the final statement executed in a trap's statement-body is continue (§8.5.2), the writing of the error object to the error stream is suppressed, and execution continues with the statement immediately following the one in the scope containing the trap statement that made the exception visible. If the final statement executed in a trap's statement-body is break (§8.5.1), the writing of the error object to the error stream is suppressed, and the exception is re-thrown.

Within a trap statement the variable $_ contains a description of the current error.

Consider the case in which an exception raised from within a try block does not have a matching catch block, but a matching trap statement exists at a higher block level. After the try block's finally clause is executed, the trap statement gets control even if any parent scope has a matching catch block. If a trap statement is defined within the try block itself, and that try block has a matching catch block, the trap statement gets control.

Examples:

In the following example, the error object is written and execution continues with the statement immediately following the one that caused the trap; that is, "Done" is written to the pipeline.

$j = 0; $v = 10/$j; "Done"
trap { $j = 2 }

In the following example, the writing of the error object is suppressed and execution continues with the statement immediately following the one that caused the trap; that is, "Done" is written to the pipeline.

$j = 0; $v = 10/$j; "Done"
trap { $j = 2; continue }

In the following example, the writing of the error object is suppressed and the exception is re-thrown.

$j = 0; $v = 10/$j; "Done"
trap { $j = 2; break }

In the following example, the trap and exception-generating statements are in the same scope. After the exception is caught and handled, execution resumes with writing 1 to the pipeline.

&{trap{}; throw '\...'; 1}

In the following example, the trap and exception-generating statements are in different scopes. After the exception is caught and handled, execution resumes with writing 2 (not 1) to the pipeline.

trap{} &{throw '\...'; 1}; 2

8.9 The data statement

Syntax:

data-statement:
    data new-lines~opt~ data-name data-commands-allowed~opt~ statement-block

data-name:
    simple-name

data-commands-allowed:
    new-lines~opt~ -supportedcommand data-commands-list

data-commands-list:
    new-lines~opt~ data-command
    data-commands-list , new-lines~opt~ data-command

data-command:
    command-name-expr

Description:

A data statement creates a data section, keeping that section's data separate from the code. This separation supports facilities like separate string resource files for text, such as error messages and Help strings. It also helps support internationalization by making it easier to isolate, locate, and process strings that will be translated into different languages.

A script or function can have zero or more data sections.

The statement-block of a data section is limited to containing the following PowerShell features only:

  • All operators except -match
  • The if statement
  • The following automatic variables: $PsCulture, $PsUICulture, $true, $false, and $null.
  • Comments
  • Pipelines
  • Statements separated by semicolons (;)
  • Literals
  • Calls to the ConvertFrom-StringData cmdlet
  • Any other cmdlets identified via the supportedcommand parameter

If the ConvertFrom-StringData cmdlet is used, the key/value pairs can be expressed using any form of string literal. However, expandable-string-literals and expandable-here-string-literals must not contain any variable substitutions or sub-expression expansions.

Examples:

The SupportedCommand parameter indicates that the given cmdlets or functions generate data only. For example, the following data section includes a user-written cmdlet, ConvertTo-XML, which formats data in an XML file:

data -supportedCommand ConvertTo-XML {
    Format-XML -strings string1, string2, string3
}

Consider the following example, in which the data section contains a ConvertFrom-StringData command that converts the strings into a hash table, whose value is assigned to $messages.

$messages = data {
    ConvertFrom-StringData -stringdata @'
    Greeting = Hello
    Yes = yes
    No = no
'@
}

The keys and values of the hash table are accessed using $messages.Greeting, $messages.Yes, and $messages.No, respectively.

Now, this can be saved as an English-language resource. German- and Spanish-language resources can be created in separate files, with the following data sections:

$messages = data {
    ConvertFrom-StringData -stringdata @"
    Greeting = Guten Tag
    Yes = ja
    No = nein
"@
}

$messagesS = data {
    ConvertFrom-StringData -stringdata @"
    Greeting = Buenos días
    Yes = sí
    No = no
"@
}

If dataname is present, it names the variable (without using a leading $) into which the value of the data statement is to be stored. Specifically, $name = data { ... } is equivalent to data name { ... }.

8.10 Function definitions

Syntax:

function-statement:
    function new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
    filter new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }
    workflow new-lines~opt~ function-name function-parameter-declaration~opt~ { script-block }

function-name:
    command-argument

command-argument:
    command-name-expr

function-parameter-declaration:
    new-lines~opt~ ( parameter-list new-lines~opt~ )

parameter-list:
    script-parameter
    parameter-list new-lines~opt~ , script-parameter

script-parameter:
    new-lines~opt~ attribute-list~opt~ new-lines~opt~ variable script-parameter-default~opt~

script-block:
    param-block~opt~ statement-terminators~opt~ script-block-body~opt~

param-block:
    new-lines~opt~ attribute-list~opt~ new-lines~opt~ param new-lines~opt~
        ( parameter-list~opt~ new-lines~opt~ )

parameter-list:
    script-parameter
    parameter-list new-lines~opt~ , script-parameter

script-parameter-default:
    new-lines~opt~ = new-lines~opt~ expression

script-block-body:
    named-block-list
    statement-list

named-block-list:
    named-block
    named-block-list named-block

named-block:
    block-name statement-block statement-terminators~opt~

block-name: one of
    dynamicparam   begin   process   end

Description:

A function definition specifies the name of the function, filter, or workflow being defined and the names of its parameters, if any. It also contains zero or more statements that are executed to achieve that function's purpose.

Each function is an instance of the class System.Management.Automation.FunctionInfo.

8.10.1 Filter functions

Whereas an ordinary function runs once in a pipeline and accesses the input collection via $input, a filter is a special kind of function that executes once for each object in the input collection. The object currently being processed is available via the variable $_.

A filter with no named blocks (§8.10.7) is equivalent to a function with a process block, but without any begin block or end block.

Consider the following filter function definition and calls:

filter Get-Square2 { # make the function a filter
    $_ * $_ # access current object from the collection
}

-3..3 | Get-Square2 # collection has 7 elements
6, 10, -3 | Get-Square2 # collection has 3 elements

Each filter is an instance of the class System.Management.Automation.FilterInfo (§4.5.11).

8.10.2 Workflow functions

A workflow function is like an ordinary function with implementation defined semantics. A workflow function is translated to a sequence of Windows Workflow Foundation activities and executed in the Windows Workflow Foundation engine.

8.10.3 Argument processing

Consider the following definition for a function called Get-Power:

function Get-Power ([long]$base, [int]$exponent) {
    $result = 1
    for ($i = 1; $i -le $exponent; ++$i) {
        $result *= $base
    }
    return $result
}

This function has two parameters, $base and $exponent. It also contains a set of statements that, for non-negative exponent values, computes $base^$exponent^ and returns the result to Get-Power's caller.

When a script, function, or filter begins execution, each parameter is initialized to its corresponding argument's value. If there is no corresponding argument and a default value (§8.10.4) is supplied, that value is used; otherwise, the value $null is used. As such, each parameter is a new variable just as if it was initialized by assignment at the start of the script-block.

If a script-parameter contains a type constraint (such as [long] and [int] above), the value of the corresponding argument is converted to that type, if necessary; otherwise, no conversion occurs.

When a script, function, or filter begins execution, variable $args is defined inside it as an unconstrained 1-dimensional array, which contains all arguments not bound by name or position, in lexical order.

Consider the following function definition and calls:

function F ($a, $b, $c, $d) { ... }

F -b 3 -d 5 2 4       # $a is 2, $b is 3, $c is 4, $d is 5, $args Length 0
F -a 2 -d 3 4 5       # $a is 2, $b is 4, $c is 5, $d is 3, $args Length 0
F 2 3 4 5 -c 7 -a 1   # $a is 1, $b is 2, $c is 7, $d is 3, $args Length 2

For more information about parameter binding see §8.14.

8.10.4 Parameter initializers

The declaration of a parameter p may contain an initializer, in which case, that initializer's value is used to initialize p provided p is not bound to any arguments in the call.

Consider the following function definition and calls:

function Find-Str ([string]$str, [int]$start_pos = 0) { ... }

Find-Str "abcabc" # 2nd argument omitted, 0 used for $start_pos
Find-Str "abcabc" 2 # 2nd argument present, so it is used for $start_pos

8.10.5 The [switch] type constraint

When a switch parameter is passed, the corresponding parameter in the command must be constrained by the type switch. Type switch has two values, True and False.

Consider the following function definition and calls:

function Process ([switch]$trace, $p1, $p2) { ... }

Process 10 20                # $trace is False, $p1 is 10, $p2 is 20
Process 10 -trace 20         # $trace is True, $p1 is 10, $p2 is 20
Process 10 20 -trace         # $trace is True, $p1 is 10, $p2 is 20
Process 10 20 -trace:$false  # $trace is False, $p1 is 10, $p2 is 20
Process 10 20 -trace:$true   # $trace is True, $p1 is 10, $p2 is 20

8.10.6 Pipelines and functions

When a script, function, or filter is used in a pipeline, a collection of values is delivered to that script or function. The script, function, or filter gets access to that collection via the enumerator $input (§2.3.2.2, §4.5.16), which is defined on entry to that script, function, or filter.

Consider the following function definition and calls:

function Get-Square1 {
    foreach ($i in $input) {   # iterate over the collection
        $i * $i
    }
}

-3..3 | Get-Square1            # collection has 7 elements
6, 10, -3 | Get-Square1        # collection has 3 elements

8.10.7 Named blocks

The statements within a script-block can belong to one large unnamed block, or they can be distributed into one or more named blocks. Named blocks allow custom processing of collections coming from pipelines; named blocks can be defined in any order.

The statements in a begin block (i.e.; one marked with the keyword begin) are executed once, before the first pipeline object is delivered.

The statements in a process block (i.e.; one marked with the keyword process) are executed for each pipeline object delivered. ($_ provides access to the current object being processed from the input collection coming from the pipeline.) This means that if a collection of zero elements is sent via the pipeline, the process block is not executed at all. However, if the script or function is called outside a pipeline context, this block is executed exactly once, and $_ is set to $null, as there is no current collection object.

The statements in an end block (i.e.; one marked with the keyword end) are executed once, after the last pipeline object has been delivered.

8.10.8 dynamicparam block

The subsections of §8.10 thus far deal with static parameters, which are defined as part of the source code. It is also possible to define dynamic parameters via a dynamicparam block, another form of named block (§8.10.7), which is marked with the keyword dynamicparam. Much of this machinery is implementation defined.

Dynamic parameters are parameters of a cmdlet, function, filter, or script that are available under certain conditions only. One such case is the Encoding parameter of the Set-Item cmdlet.

In the statement-block, use an if statement to specify the conditions under which the parameter is available in the function. Use the New-Object cmdlet to create an object of an implementation-defined type to represent the parameter, and specify its name. Also, use New-Object to create an object of a different implementation-defined type to represent the implementation-defined attributes of the parameter.

The following example shows a function with standard parameters called Name and Path, and an optional dynamic parameter named DP1. The DP1 parameter is in the PSet1 parameter set and has a type of Int32. The DP1 parameter is available in the Sample function only when the value of the Path parameter contains "HKLM:", indicating that it is being used in the HKEY_LOCAL_MACHINE registry drive.

function Sample {
    Param ([String]$Name, [String]$Path)
    dynamicparam {
        if ($path -match "*HKLM*:") {
            $dynParam1 = New-Object System.Management.Automation.RuntimeDefinedParameter("dp1", [Int32], $attributeCollection)

            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.ParameterSetName = 'pset1'
            $attributes.Mandatory = $false

            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection``1[System.Attribute]
            $attributeCollection.Add($attributes)

            $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add("dp1", $dynParam1)
            return $paramDictionary
        }
    }
}

The type used to create an object to represent a dynamic parameter is System.Management.Automation.RuntimeDefinedParameter.

The type used to create an object to represent the attributes of the parameter is System.Management.Automation.ParameterAttribute.

The implementation-defined attributes of the parameter include Mandatory, Position, and ValueFromPipeline.

8.10.9 param block

A param-block provides an alternate way of declaring parameters. For example, the following sets of parameter declarations are equivalent:

function FindStr1 ([string]$str, [int]$start_pos = 0) { ... }
function FindStr2 {
    param ([string]$str, [int]$start_pos = 0) ...
}

A param-block allows an attribute-list on the param-block whereas a function-parameter-declaration does not.

A script may have a param-block but not a function-parameter-declaration. A function or filter definition may have a function-parameter-declaration or a param-block, but not both.

Consider the following example:

param ( [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [string[]] $ComputerName )

The one parameter, $ComputerName, has type string[], it is required, and it takes input from the pipeline.

See §12.3.7 for a discussion of the Parameter attribute and for more examples.

8.11 The parallel statement

Syntax:

parallel-statement:
    *parallel* statement-block

The parallel statement contains zero or more statements that are executed in an implementation defined manner.

A parallel statement is only allowed in a workflow (§8.10.2).

8.12 The sequence statement

Syntax:

sequence-statement:
    *sequence* statement-block

The sequence statement contains zero or more statements that are executed in an implementation defined manner.

A sequence statement is only allowed in a workflow (§8.10.2).

8.13 The inlinescript statement

Syntax:

inlinescript-statement:
    inlinescript statement-block

The inlinescript statement contains zero or more statements that are executed in an implementation defined manner.

A inlinescript statement is only allowed in a workflow (§8.10.2).

8.14 Parameter binding

When a script, function, filter, or cmdlet is invoked, each argument can be bound to the corresponding parameter by position, with the first parameter having position zero.

Consider the following definition fragment for a function called Get-Power, and the calls to it:

function Get-Power ([long]$base, [int]$exponent) { ... }

Get-Power 5 3       # argument 5 is bound to parameter $base in position 0
                    # argument 3 is bound to parameter $exponent in position 1
                    # no conversion is needed, and the result is 5 to the power 3

Get-Power 4.7 3.2   # double argument 4.7 is rounded to int 5, double argument
                    # 3.2 is rounded to int 3, and result is 5 to the power 3

Get-Power 5         # $exponent has value $null, which is converted to int 0

Get-Power           # both parameters have value $null, which is converted to int 0

When a script, function, filter, or cmdlet is invoked, an argument can be bound to the corresponding parameter by name. This is done by using a parameter with argument, which is an argument that is the parameter's name with a leading dash (-), followed by the associated value for that argument. The parameter name used can have any case-insensitive spelling and can use any prefix that uniquely designates the corresponding parameter. When choosing parameter names, avoid using the names of the common parameters.

Consider the following calls to function Get-Power:

Get-Power -base 5 -exponent 3   # -base designates $base, so 5 is
                                # bound to that, exponent designates
                                # $exponent, so 3 is bound to that

Get-Power -Exp 3 -BAs 5         # $base takes on 5 and $exponent takes on 3

Get-Power -e 3 -b 5             # $base takes on 5 and $exponent takes on 3

On the other hand, calls to the following function

function Get-Hypot ([double]$side1, [double]$side2) {
    return [Math]::Sqrt($side1 * $side1 + $side2 * $side2)
}

must use parameters -side1 and -side2, as there is no prefix that uniquely designates the parameter.

The same parameter name cannot be used multiple times with or without different associated argument values.

Parameters can have attributes (§12). For information about the individual attributes see the sections within §12.3. For information about parameter sets see §12.3.7.

A script, function, filter, or cmdlet can receive arguments via the invocation command line, from the pipeline, or from both. Here are the steps, in order, for resolving parameter binding:

  1. Bind all named parameters, then
  2. Bind positional parameters, then
  3. Bind from the pipeline by value (§12.3.7) with exact match, then
  4. Bind from the pipeline by value (§12.3.7) with conversion, then
  5. Bind from the pipeline by name (§12.3.7) with exact match, then
  6. Bind from the pipeline by name (§12.3.7) with conversion

Several of these steps involve conversion, as described in §6. However, the set of conversions used in binding is not exactly the same as that used in language conversions. Specifically,

  • Although the value $null can be cast to bool, $null cannot be bound to bool.
  • When the value $null is passed to a switch parameter for a cmdlet, it is treated as if $true was passed. However, when passed to a switch parameter for a function, it is treated as if $false was passed.
  • Parameters of type bool or switch can only bind to numeric or bool arguments.
  • If the parameter type is not a collection, but the argument is some sort of collection, no conversion is attempted unless the parameter type is object or PsObject. (The main point of this restriction is to disallow converting a collection to a string parameter.) Otherwise, the usual conversions are attempted.

If the parameter type is IList or ICollection<T>, only those conversions via Constructor, op_Implicit, and op_Explicit are attempted. If no such conversions exist, a special conversion for parameters of "collection" type is used, which includes IList, ICollection<T>, and arrays.

Positional parameters prefer to be bound without type conversion, if possible. For example,

function Test {
    [CmdletBinding(DefaultParameterSetname = "SetB")]
    param([Parameter(Position = 0, ParameterSetname = "SetA")]
        [decimal]$dec,
        [Parameter(Position = 0, ParameterSetname = "SetB")]
        [int]$in
    )
    $PsCmdlet.ParameterSetName
}

Test 42d   # outputs "SetA"
Test 42    # outputs "SetB"