Post

My favorite new features in PowerShell 7

My favorite new features in PowerShell 7

The latest reincarnation of PowerShell, version 7.x, has been around for a while now.

PowerShell 7 is based on .NET (previously known as .NET Core) instead of .NET Framework which means that MacOS and Linux users now get to enjoy PowerShell too 😍. It’s also packed with lot of new features and improvements. Unlike the proprietary Windows Powershell 5.1, PowerShell 7 is open-source and hosted on GitHub.

In this article I’ll walk you through some of my favorite new features in PowerShell 7. So let’s dive right in!

Null-coalescing operators

The first new feature on my list is the null-coalescing operator. It’s a useful operator that allows you to select the left-most operand that is not null using a concise notation. Null-coalescing has long been available in other programming and scripting languages such as C# and PHP, and finally it has also found its way to PowerShell!

The syntax for null-coalescing in PowerShell 7 is

1
<expression> ?? <expression> [?? <expression> ..]

The expressions are evaluated from left to right until the first non-null operand is found. Rest of the operands will not get evaluated.

The following example demonstrates the use of the null-coalescing operator:

1
2
3
4
$x = $null
$x ?? "Hello World"

# -> Returns "Hello World"

The real power of the null-coalescing operator is its conciseness. Let’s compare these two code snippets that do the same thing:

1
2
3
4
5
6
7
If ($Configuration.Location) {
    $Location = $Configuration.Location
} else {
    $Location = "C:\Temp"
}

Get-ChildItem -Path $Location
1
Get-ChildItem -Path ($Configuration.Location ?? "C:\Temp")

The difference is even more obvious when you have multiple fallback values, but I’ll leave that as an exercise for the reader :)

The other new null-coalescing operator in PowerShell 7 is the null-coalescing assignment operator ??=. It only sets the value of a variable if the variable evaluates to null. If the variable already has a non-null value, the right-hand operand is not evaluated at all.

Let’s illustrate this with an example:

1
2
3
4
5
6
7
8
$x = $null
$x ??= "Hello World"

# -> the value of $x is now "Hello World"

$x ??= "Goodbye World"

# -> the value of $x is still "Hello World"

The use-cases for the null-coalescing assignment operator are similar to the null-coalescing operator e.g. setting default values for variables.

Ternary operator

The second item on my list is the ternary operator which is basicly like a simplified if-else statement. The syntax in PowerShell 7 follows the same pattern as in C#:

1
<condition> ? <if-true expression> : <if-false expression>

Like the null-coalescing operator, the ternary operator’s superpower is its concise syntax. Let’s compare the two versions of the same code:

1
2
3
4
5
If ((Get-Date).DayOfWeek -eq "Saturday") {
    "It's weekend!"
} else {
    "It's not weekend yet."
}
1
((Get-Date).DayOfWeek -eq "Saturday") ? "It's weekend!" : "It's not weekend yet."

It is worth noting that using the ternary operator can make your code harder to read. So if you are not intentionally trying to obfuscate your code, you might want to stick with regular if-else statements whenever a concise syntax is not necessary.

Foreach-Object -Parallel

This is a big one.

PowerShell 7 adds a built-in support for parallel processing for the ForEach-Object cmdlet. This means that you can now run a block of code in parallel without having to use Start-Job or manually manage runspaces which really can’t be described as easy or user-friendly.

Foreach-Object -Parallel uses runspaces under the hood. It runs each iteration in a separate runspace and the maximum number of runspaces can be controlled with the -ThrottleLimit parameter.

The basic usage for Foreach-Object -Parallel is as follows:

1
2
3
4
5
$MaxEvents = 10000
$LogsToSearch = 'Security', 'Application', 'System'
$Events = $LogsToSearch | ForEach-Object -Parallel {
    Get-WinEvent -LogName $_ -MaxEvents $using:MaxEvents
} -ThrottleLimit 2

The above code gets the newest 10 000 events from each of the specified logs using two parallel runspaces. The $_ variable refers to the current item. Since each iteration is run in a separate runspace, you need to use the $using: scope modifier to access variables outside the running script block.

Some things to consider:

  • Because of the runspaces there is a significant overhead involved when compared to running the same code sequentially. You can only expect to see performance improvements when the code inside the loop is computationally expensive or when you are waiting for external resources.
  • Parallel scriptblock execution order is non-deterministic meaning that the iterations may not complete in the order they were started. If you need to maintain the order, you need to implement a mechanism to sort the results afterwards.

For other important details using Foreach-Object -Parallel, see the official documentation.

Invoke-RestMethod with retry

Last but not least, the final feature on my list of favorites is a small but a very useful one.

Invoke-RestMethod has been given a built-in support for retrying a connection when a failure code between 400 and 599, inclusive or 304 is received. You can control this behavior by specifying the maximum retry count and the interval between retries.

The new syntax is as follows:

1
2
# Call an API with maximum of 3 retries 5 seconds apart.
Invoke-RestMethod -Uri 'https://example.api.fi/data' -MaximumRetryCount 3 -RetryIntervalSec 5

Achieving similar results with PowerShell 5.1 requires much more effort. Here’s one way to do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$MaximumRetryCount = 3
$RetryIntervalSec = 1
$Iteration = 0
while ($true) {
    try {
        Invoke-RestMethod -Uri 'https://example.api.fi/data' -ErrorAction Stop
        break
    } catch {
        if ($Iteration -ge $MaximumRetryCount) {
            throw $_
        }
        $Iteration++
        Start-Sleep -Seconds $RetryIntervalSec
    }
}

This was actually included already in PowerShell 6 (“the Windows Vista of PowerShells”), but I decided to list it here anyways.

Conclusion

If you haven’t tried PowerShell 7 yet, you now have at least four good reasons to give it a go. The newest stable version on the time of writing this article is 7.4 which is a LTS version supported until November 2026.

This brings us to the one major drawback of PowerShell 7. You do need to install it separately, even for Windows OSs. Windows users have multiple ways to install it with winget being the recommended one. Microsoft’s documentation also includes instructions for macOS and Linux.

If you only write scripts for your own needs or to be run on a platform with a native support (like Azure), this may not be a problem. But for scripts deployed using Intune or shared with others, this might be a deal breaker.

To ensure compatibility and to minimize administrative overhead, you typically want to consider the lowest common denominator which for PowerShell is still the version 5.1 (or even older).

I really hope that Microsoft will soon realize that to be fully embraced by the wider audience, PowerShell 7 needs to ship with Windows by default. If you want to speed up the process, you can give your opinion on the matter in a GitHub discussion here.

This post is licensed under CC BY 4.0 by the author.