PowerShell 7 for Programmers

First published . Updated .

PowerShell Hero Comic Wallpaper

Microsoft's PowerShell Team and countless members of the community have worked hard on crafting and coding the seventh major version of PowerShell. Its predecessor, PowerShell Core 6, was a massive rewrite of Windows PowerShell 5.1 that saw (most of) the codebase changed to run on the cross-platform .NET Core framework, which brought PowerShell to and Linux for the first time, along with new features and performance enhancements.

, work had all but completed on PowerShell 7, and the final version is available for download for supported Windows, macOS, and systems. In addition to an optional long-term-servicing support model, Docker and NuGet inclusion, Desired State Configuration improvements, and good compatibility with modules that only claim to support Windows PowerShell, there are many improvements to the language and runtime that will benefit developers.

pwsh is now supported as a login shell

While this isn't a language change, it is great news for many developers who don't use Windows full-time or at all. I tried to learn Bash shell scripting, but I didn't get far and I was never very good at it. Luckily for me, as of PowerShell 7.0.0-preview3, pwsh is now fully supported as a login shell on macOS and Linux.

Parallel execution for for-each loops

Up until now, if you used the ForEach-Object cmdlet, it would operate on each item sequentially, one at a time. New in PowerShell 7 is the -Parallel switch and parameter set, which will operate on multiple items at once. You can also use the -ThrottleLimit parameter to tell PowerShell how many statement blocks should be run in parallel at any given time; it defaults to five.

While this will usually increase performance, using a parallel for-each loop means that your items will no longer be processed in order. If order is important, use a normal for or for-each loop.


Get-ChildItem | ForEach-Object -ThrottleLimit 4 -Parallel {
	Write-Output "Found the item $($_.Name)."

However, there is no way for this to fail gracefully on earlier versions of . If you cannot guarantee your code will run on PowerShell 7, you'll have to wrap it in a try-catch block, or better yet, do an ugly version and edition check:

Function Out-Names {
		[PSObject[]] $items

	$code = {Write-Output "$($_.Name)."}

	# Technically, this would fail on 7.0.0-preview1 and -preview2, but you
	# shouldn't concern yourselves with those obsolete beta versions.

	If ($PSVersionTable.PSVersion -ge 7.0.0) {
		$items | ForEach-Object -Parallel $code -ThrottleLimit 4
	} Else {
		$items | ForEach-Object -ScriptBlock $code

As far as I know, neither the .ForEach() method nor the ForEach statement/loop offer parallel execution.

ForEach-Object -Parallel is not enabled by default, nor will it be. There are some times to use this, and some times to avoid it. For more information and some use cases, I'll refer you to Paul Higinbotham's blog post introducing it.

An earlier version of this article said that ForEach-Object -Parallel was only supported on Windows. As of version 7.0.0-rc.2, it is indeed available on macOS and Linux.

Null coalescing operator

As someone who finds himself having to write a lot of checks for null values, this new operator is a major improvement, in my opinion. If the variable before the operator is $null, the variable afterwards is returned; otherwise, the variable itself is returned. Let's see it in action.

Before PowerShell 7 In PowerShell 7
If ($null -Eq $x) {
	Write-Output 'nothing'
} Else {
	Write-Output $x
Write-Output ($x ?? 'nothing')
$name = Read-Host -Prompt "File name"
$file = Get-Item $name
If ($null -Eq $file) {
	$file = Get-Item "default.txt"
$name = Read-Host -Prompt "File name"
$file = Get-Item $name ?? Get-Item "default.txt"

Null assignment operator

Similar to the above, the new null assignment operator will assign a value to a variable if and only if it is equal to $null.

PS /Users/colin> $stuff ??= "sane value"
PS /Users/colin> $stuff
sane value

Before PowerShell 7 In PowerShell 7
$name = Read-Host -Prompt "Enter a file name"
If ($null -Eq $name) {
	$name = "default.txt"
Get-Item $name
$name = Read-Host -Prompt "Enter a file name"
$name ??= "default.txt"
Get-Item $name

Null conditional member property and method access (PowerShell 7.1)

In previous versions of PowerShell, you would have to check if a variable is $null before attempting to access its members, or wrap your code in try-catch blocks; if you failed to do so, your script might terminate unexpectedly when -ErrorAction Stop is set. Now, put a question mark after your variable to silently continue and return $null if something does not exist.

Unfortunately, because PowerShell allows question marks in variable names, using this operator means that you do have to include the optional braces around your variable name, so PowerShell knows where your variable name begins and ends.

Before PowerShell 7.1

If ($null -ne $x) {
	If ($x | Get-Member | Where-Object {$_.Name -eq "Prop"}) {
		If ($x.Prop | Get-Member | Where-Object {$_.Name -eq "Method"}) {

In PowerShell 7.1


Before PowerShell 7.1

$filesInFolder = Get-ChildItem
Write-Output "The third file in this folder is:"
If ($filesInFolder.Count -ge 3) {

In PowerShell 7.1

Write-Output "The third file in this folder is:"

Pipeline chaining operators

PowerShell 7 implements the pipeline chaining operators made famous by Bash. Previously, to run a command based on if the previous operation succeeded or failed, you would have to check the return code or the automatic variable $?. Now, you can use && to run a second command if and only if the first one succeeds, and use || to run a second command if and only if the first one fails. For example:

Invoke-Thing1 && Write-Output "Thing1 worked!"
Invoke-Thing2 || Write-Error  "Thing2 didn't work."

No, you can't use && and || together to emulate an if-else statement. However, PowerShell 7 adds some popular shorthand:

Ternary operator

Many programming languages have what's called the ternary operator (sometimes called the conditional operator), which is just a shorter method of writing an if-else statement. While I believe it can lead people to write messy code, sometimes it's much cleaner-looking and easier for a human to read.

Before PowerShell 7 In PowerShell 7
If ($flag -Eq $true) {
	Write-Output "Yes"
} Else {
	Write-Output "No"
Write-Output ($flag ? "Yes" : "No")
If ($user.isMember()) {
	$price += 2
Else {
	$price += 5
$price += $user.isMember() ? 2 : 5
$x = $items.Count
If ($x -Eq 1) {
	$msg = "Found $x item."
} Else {
	$msg = "Found $x items."
$x   = $items.Count
$msg = "Found $x item$($x -Eq 1 ? 's' : '')."

Reverse operation for the -Split operator

When you provide a matching limit to the -Split operator, it normally works left-to-right. Now, it can operate right-to-left instead.

PS /Users/colin> "split1 split2 split3 split4" -Split " ",3
split3 split4

PS /Users/colin> "split1 split2 split3 split4" -Split " ",-3
split1 split2

Skip error handling for web cmdlets

PowerShell does a pretty decent job of handling errors for you, but sometimes, you might want to do it yourself. The Invoke-WebRequest and Invoke-RestMethod cmdlets now support a new switch, -SkipHttpErrorCheck.

Before this, the raw HTML code of the request would be returned, and you would have to parse the error object yourself. Use this switch, and those two cmdlets will return a "success" even when an HTTP error occurred. You, as the programmer, can now handle errors yourself without cumbersome try-catch blocks, by reading the response yourself.

Invoke-RestMethod also includes a new parameter, -StatusCodeVariable, to which you pass the name of a variable (confusingly, a string with the variable name, not the variable itself) that will contain the HTTP response code.

Invoke-RestMethod -Uri "https://example.com/pagenotfound" `
	  -SkipHttpErrorCheck -StatusCodeVariable scv

If ($scv -ne 200) {
	# error handling goes here
} Else {
	# your code continues here

New $ErrorActionPreference, Break

If you change your $ErrorActionPreference preference variable to the new value "Break", you can then drop into a debugger as soon as an error happens. For example, let's try dividing by zero.

PS /Users/colin> $ErrorActionPreference = "Stop"
PS /Users/colin> 1/0
ParentContainsErrorRecordException: Attempted to divide by zero.

PS /Users/colin> $ErrorActionPreference = "Break"
PS /Users/colin> 1/0
Entering debug mode. Use h or ? for help.
At line:1 char:1
+ 1/0
+ ~~~
[DBG]: PS /Users/colin>>


Get-Clipboard and Set-Clipboard are back

After a hiatus in PowerShell Core 6, the two cmdlets, Get-Clipboard and Set-Clipboard, return. Though they can only manipulate plain text at this time, unlike their Windows PowerShell implementations, they are available for use on all platforms.

Linux users must make sure xclip is installed.


There are many more little features you might find useful.

  • After taking some time off during PowerShell Core 6, the following Windows PowerShell things return -- for users only:
    • Clear-RecycleBin
    • Get-Help's -ShowWindow parameter
    • Out-GridView
    • Out-Printer
    • Show-Command
  • Enumerating files in OneDrive works, and when using files on demand, doing so won't trigger an automatic download.
  • Format-Hex better handles custom types.
  • Get-ChildItem, when used on macOS or Linux, now returns the properties UnixMode, User, and Group.
  • Send-MailMessage is now deprecated. Unfortunately, there is no secure fix or replacement available.
  • Test-Connection never really went away, but it now behaves identically on Windows, macOS, and Linux.

There are also many improvements to Desired State Configuration. There is also a compatibility layer for loading modules not marked as compatible with Core editions; however, if you're a module developer, you should have already set CompatiblePSEditions appropriately!

The final release of PowerShell 7 was released on , so download it, get coding, and happy developing!