In my previous article in the Practical PowerShell series, I discussed looping as part of flow control. In this article, I cover another essential part of flow control: branching. Branching defines multiple paths for your code to follow, depending on conditions.

If-Then-Else

Next to loop constructs, there are decision-making cmdlets to control branching in code. It is an essential construct and allows you to branch code based on conditions (if) that are met (then) and optionally are not met (else). This construct is easy to understand and use because it follows natural language. You also see it in other environments, such as home automation or PowerAutomate. In the example below, we will process mailboxes disabling the accounts of Shared Mailboxes that are not already disabled:

$Mailboxes= Get-ExoMailbox -ResultSize Unlimited -Properties AccountDisabled

ForEach( $Mailbox in $Mailboxes) {
    If( $Mailbox.RecipientTypeDetails -ieq 'SharedMailbox') {
        If(-not $Mailbox.AccountDisabled) {
            Write-Host( 'Disabling account of shared mailbox {0}' -f $Mailbox.userPrincipalName)
        }
        Else {
            Write-Host( 'Account of shared mailbox {0} already disabled' -f $Mailbox.userPrincipalName)
        }
    }
    Else {
        Write-Host ('{0} is not a shared mailbox' -f $Mailbox.userPrincipalName)
    }
}

Note that the comparison operator ieq stands for case-insensitive equal. This is the default mode for eq, but I habitually mention it for clarity.

As shown in the example, you can nest If cmdlets, and the Else part is optional but often added as a placeholder with a descriptive comment for reference. When conditions get complex, keeping an overview becomes a challenge, and you might mismatch brackets or quotes. This is where editors such as Visual Studio Code or even the PowerShell ISE are helpful, indicating where the (current) matching bracket is when hovering over one, making missing opening or closing brackets or quotes easier to spot. Visual Studio Code also allows you to fold (or expand) the if or else branch, as shown in Figure 1.

Practical PowerShell: Branching
Figure 1: Expanding an If branch in Visual Studio Code

Switch

As you saw in the If-Then-Else example, code can become quite complex and also unreadable when using multiple conditions or nesting. This is where the Switch construct comes in handy. The Switch construct allows you to turn this:

If( $Mailbox.recipientTypeDetails -ieq 'UserMailbox') {
    # code for UserMailbox
ElseIf( $Mailbox.recipientTypeDetails -ieq 'SharedMailbox') {
    # code for SharedMailbox
Else {
    # code for not UserMailbox nor SharedMailbox
}

Into this:

Switch( $Mailbox.recipientTypeDetails) {
    'UserMailbox' {
        # code for UserMailbox
    }
    'SharedMailbox' {
        # code for SharedMailbox
    }
    default {
        # code for not UserMailbox nor SharedMailbox
        Write-Warning ('Unsupported type: {0}' -f $_)
    }
}

The $Mailbox.recipientTypeDetails value is checked against ‘UserMailbox’ and ‘SharedMailbox’. When a match is found, the corresponding script block is run. If no match is found, the optional default code is executed. The variable $_ is assigned the value to match within the switch, which we use to report that we’ve encountered an unsupported value for the recipientTypeDetails property.

Switch also has some operation modifiers, such as the switches -regex and -wildcard. These allow Switch to check the evaluated object against a regular expression or a wildcard (similar to -like). Be advised that in principle every condition will get evaluated, which could result in more than one script block getting executed. For example, in the following segment showing Switch in wildcard mode, both the code for UserMailbox and *Mailbox will run when recipientTypeDetails is UserMailbox:

Switch -wildcard ( $Mailbox.recipientTypeDetails) {
    'UserMailbox' {
        # code for UserMailbox
    }
    '*Mailbox' {
        # code for any type ending in Mailbox
    }
}

This can be avoided by adding break at the end of code blocks, preventing further evaluation, e.g.

Switch -wildcard ( $Mailbox.recipientTypeDetails) {
    'UserMailbox' {
        # code for UserMailbox
        break
    }
    '*Mailbox' {
        # code for any type ending in Mailbox
        break
    }
}

The Switch cmdlet is powerful and can prevent code fromending up in overly complex if-then-else branching while keeping things readable. It also allows you to match on multiple values, for example. For more information on switch and additional usage, see here.

Stop Cmdlets

So far, we have seen constructs that execute code in a sequential order. There might be situations when you want to cut branching short or skip an iteration. For example, when you are looping through elements to locate a matching item, there is no need to continue the remainder of the loop when you find one. This is when Break can be used. On the other hand, if you do not want to process a certain element in the loop, you can use Continue.

Like Break, stop cmdlets are also available at the level of functions and scripts, in the form of Exit and Return. Exit terminates the function or script in the scope of where the function or script was called. Return exits the script or function, optionally returning an object to the scope of where it was called, after which execution will be continued. To understand the different effects of these cmdlets, have a look at the following example:

Function Test {
    For( $i=0; $i -lt 4; $i++) {
        Write-Host ('Start {0}' -f $i)
        If( $i -eq 2) {

            # Aborts the loop
            Break

            # Skips iteration, continues with next iteration
            Continue

            # Aborts execution, continues at caller scope
            Return

            # Exits and at scope of caller
            Exit
        }
        Write-Host ('End {0}' -f $i)
    }
    Write-Host ('End Function.')
}

Test
Write-Host ('End Script.')

The effect of using each stopping cmdlet can be demonstrated by commenting on the ones you are not testing in the script above. The output for each uncommented stopping cmdlet is shown in the table below.

BreakContinueReturnExit
Start 0
End 0
Start 1
End 1
Start 2
End Function.
End Script.
Start 0
End 0
Start 1
End 1
Start 2
Start 3
End 3
End Function.
End Script.
Start 0
End 0
Start 1
End 1
Start 2
End Script.
Start 0
End 0
Start 1
End 1
Start 2

Now after all that, I highly recommend avoiding stopping cmdlets and instead using a single point of exit for your loop or function. Those in favor of using stopping cmdlets usually claim they increase efficiency and performance. While that might be true for unstructured code, when you properly structure code, there is no real need to add stopping cmdlets. In addition, stopping cmdlets makes code harder to read and understand. Many organizations use code style guides that make single exit points compulsory.

Unary Operators

In some of the examples, I use unary operators. Unary operators let you write $i++ instead of $i=$i+1. You can also add an unary operator before the variable is evaluated by writing ++$i, in which case the 1 gets added to $i before it gets evaluated. You can use ‘–‘ for subtractions, e.g., $i–. You can also use unary operators to perform other arithmetic operations, e.g. $i+=2 will add 2 to $i, and $i*=4 will multiply the value stored in $i by 4. For more on unary operators, see here.

A common usage of unary operators is with string concatenation or when “adding” items to an array or object collection, e.g., $string+= $field or $array+= $item. However, behind the scenes, this code is executed as $array= $array + $item, which makes a copy of the $array object and adds $item. This might not only be slow for larger complex objects. It also explains why this works for arrays, which, by definition, have a fixed number of elements (and why I mentioned “adding” using quotes).

Object types such as strings or arrays have better alternative (.NET) object types that are more suited for intensive data manipulations, such as [System.Collections.ArrayList] instead of Array, or [System.Text.StringBuilder] for strings. Here is a quick comparison so that you get an idea:

# Add 10.000 numbers to an array
PS> $Array= @()
PS> Measure-Command {
        1..10000 | ForEach{ $Array+= $_ }
    } | Select TotalMilliseconds

    TotalMilliseconds
    -----------------
             21746.56

# Add 10.000 numbers to arraylist
PS> $ArrayList= [System.Collections.ArrayList]::new()
PS> Measure-Command {
       1..10000 | ForEach{ $ArrayList.Add( $_)| Out-Null }
    } | Select TotalMilliseconds

    TotalMilliseconds
    -----------------
                77.69

Running the above examples, you will notice that ArrayList is significantly faster than using Array when using its built-in Add method. Note that ArrayList’s Add returns the number of the element just added, so I suppress that in the example by piping output to Out-Null. Now, I don’t say you need to change all your code, but for scripts chewing away on larger data sets, the performance benefit is considerable.

Branching for Dynamic Execution

Like looping, branching is one of the elementary programming constructs. It adds dynamics to your code, letting your code make decisions on what to execute based on conditions. Also, getting familiar with the switch cmdlet will help when nested if-then-else statements become too complex, and lets you maintain readability and maintainability.

Feel free to reach out in the comments if you have questions or comments. If not, until the next article!

About the Author

Michel De Rooij

Michel de Rooij is a consultant and Microsoft MVP since 2013. Michel lives in The Netherlands, and has been working in the IT industry for over 25 years. Starting out as a developer, Michel nowadays assists customers in their journey to or usage of Microsoft 365. His main focus is on Exchange On-Premises & Exchange Online, including related technologies such as (Azure) Active Directory. He loves to automate processes and procedures using platforms such as PowerShell. Michel is also active online in communities such as Microsoft's TechCommunity, or on socials networks such as Twitter (@mderooij). Michel runs a blog at eightwone.com, is a guest author for several other websites, a published author, and occasional speaker at international events.

Comments

  1. Edhellas

    Very helpful, thanks

    Had no idea about the Net object types having better performance. Will make my scripts much faster

Leave a Reply