Last Update: Sep 04, 2024 | Published: Feb 24, 2020
PowerShell 7 has introduced a number of new abilities for the language. One of those abilities is the oft-requested pipeline chain operator. The PowerShell pipeline is a cornerstone of the language. Easily passing entire objects from one command to another is exceptionally useful.
What has been lacking is an easy way to control the output of a command as it passes down the pipeline, depending on its success or failure. You can easily do this with variables and other control statements. To do this in a concise and pipeline friendly manner though has not been as easy to do. With that in mind, this article will explore this new feature of PowerShell 7 and how you can use this in your scripts and on the command line.
There are two pipeline chain operators that are introduced and they control the passing of data depending on the success or failure of the command’s output.
&&
will execute the right-hand pipeline if the left-hand pipeline succeeded||
will execute the right-hand pipeline if the left-hand pipeline failedSometimes it can be difficult to realize where this type of utility can be useful. With that in mind, below are a few examples that illustrate several uses cases that showcase what instances make sense to use the pipeline chain operators.
For the following example, let’s say you need to verify that a log file exists. If it does not, then create the file, otherwise leave it alone. Traditionally, this would be written in the following PowerShell.
If (-Not [Bool](Get-ChildItem -Path "application.log")) {
New-Item -Path "application.log"
}
Using the new pipeline chain operators, this can be simplified to the following. This pipeline chain states that if the file does not exist, then create the file.
Get-ChildItem -Path "application.log" || New-Item -Path "application.log"
Taking this command one step further, what if we wanted to retrieve our file application.log
but if it doesn’t exist, create the file and add the text of Created
. Finally, if the file does exist, we can just return the content, which should simply be Created
.
Get-ChildItem -Path "application.log" -ErrorAction SilentlyContinue || $(New-Item -Path "application.log"; Add-Content -Value "Created" -Path "application.log") && Get-Content -Path "application.log"
Though the above may look complicated, let’s break down what we are doing.
Get-ChildItem -Path "application.log" -ErrorAction SilentlyContinue
Attempt to retrieve the application.log
and if it does not exist, avoid throwing an error.
$(New-Item -Path "application.log"; Add-Content -Value "Created" -Path "application.log")
Separated by the ||
pipeline chain operator, this sub-expression, as contained within $(...)
, will only evaluate if the file is not found. If this does evaluate, create the file and add the text of Created
.
Get-Content -Path "application.log"
Finally, retrieve the text no matter what, as evidenced by the &&
pipeline chain operator. Whether or not the file exists, provided the file can be created (barring file permissions or the like), then this should retrieve the text from the file.
Previously, we mentioned the $?
and $LASTEXITCODE
automatic variables. You may wonder how this comes into play with the pipeline chain operators.
$?
returns a simple true
or false
depending on the result of the previous command. This makes it easy to determine the resulting state of the previous command. What’s important to know about this automatic variable is that it stores the status of both Win32 and PowerShell commands. It won’t store the return code but it does store the state, which can be used to control the flow of the pipeline.
$LASTEXITCODE
, is equivalent to %errorlevel%
from within the Win32 console. Generally, a return code of 0
is considered a success while any non-0
value is considered a failure. Unlike the $?
automatic variable, $LASTEXITCODE
does not operate on PowerShell cmdlets.
Both the piping, |
, and redirection, >
, operators have higher precedence than the pipeline chain operators. The job operator, &
, assignment operator, =
, and semicolon, ;
have lower precedence than the pipeline chain operators.
In practice, that means that individual pipelines within a pipeline chain can be redirected, or the entire pipeline chain can be used in the background, using the job operator, assigned to a variable, or separated by statements.
One way to control the precedence within a pipeline chain is to use parentheses to group commands or to use a subexpression, $(...)
.
If an error is non-terminating then the result of $?
is respected and the pipeline will still be considered a success. If the result is a terminating error, then the whole pipeline will stop, even if you use a try-catch
construct on the pipeline.
The following is an example of how the pipeline chain operators respond with a non-terminating error. In fact, our original example shows how Get-ChildItem
throws a non-terminating error by default.
Get-ChildItem -Path "missingfile.log" || Write-Host "This will show"
As you can tell from the output below, our text of This will show
is output to the console. This is because the execution continues even though the file is missing. Using the ||
pipeline chain operator and knowing that the result of the first command is false, the Write-Host
command evaluates.
To demonstrate the behavior of a terminating error, we can add -ErrorAction Stop
.
Get-ChildItem -Path "missingfile.log" -ErrorAction Stop || Write-Host "This will show"
As you can tell from the output below, this does not actually show the final command despite it failing. This is because a terminating error will stop the entire pipeline chain.
PowerShell has quickly been adding language features for the PowerShell 7 release. This not only makes for more robust error handling but also for concise and understandable pipelines. Many of these features have been requested by the community. With much deliberation and care, the new features have been added and make coding in PowerShell that much more enjoyable and useful, especially since all of this works well cross-platform!