The GA release of PowerShell 7 is just around the corner. With this release comes several new features that continue to build upon the previous versions. One of these new features being introduced are two new operators, && and ||, referred to as pipeline chain operators. They are intended to work like AND-OR lists in POSIX like shells (and to my surprise to learn, like conditional processing symbols in the Command Shell in Windows).
# Clone a Git repo and if successful display its README.md file
C:> $repo = "https://github.com/originaluko/haveibeenpwned.git"
C:> git clone $repo && Get-Content haveibeenpwned/README.md
Use of pipeline chain operators in PowerShell is fairly straight-forward. The left-hand side of the pipeline will always run when using either of the operators. The && operator will only execute the right-hand side of the operator if the left side of the pipeline was successfully executed. Conversely the || operator it will only execute the right side of the pipeline if the left side fails to execute.
C:> Write-Output 'Left' && Write-Output 'Right'
Left
Right
C:> Write-Output 'Left' || Write-Output 'Right'
Left
Previously to achieve a similar outcome you might create a script block using an If statement.
C:> Write-Output 'Left'; if ($?) {Write-Output 'Right'}
Left
Right
You can also place multiple operators in the one pipeline. These operators will be processed left-associative, meaning they are processed from left to right.
C:> Get-ChildItem C:\temp\test.txt || Write-Error 'File does not exist' && Get-Content c:\temp\test.txt
In the above example || is processed before &&. So Get-ChildItem and Write-Error can be seen as grouped first and processed before Get-Content.
[Get-ChildItem C:\temp\test.txt || Write-Error “File does not exist”] && Get-Content c:\temp\test.txt
To achieve something similar without pipeline chain operators, and this is where things get a little more interesting with the additional work involved, you might perform the below.
C:> Get-ChildItem C:\temp\test.txt ; if (-not $?) {Write-Error "File does not exist"} ; if ($?) {Get-Content c:\temp\test.txt}
Care should be taken with commands that return a True / False boolean response. They should not be confused as successful and unsuccessful executions. For example something like Test-Path.
C:> Test-Path C:\temp\test.txt && Write-Output 'File exists'
True
Path exists
C:> Test-Path C:\temp\test.txt || Write-Output 'File exists'
False
Path exists
In both cases Test-Path successfully ran and in turn the right-hand side of the pipeline executes. Pipeline chain operators work by checking the value of the $? variable. This is an automatic variable which is set to either True or False based on the success of the last command.
Pipeline chain operators can be used with Try / Catch blocks. It should be noted that script-terminating errors take precedence over chaining.
try
{
$(throw) || Write-Output 'Failed'
}
catch
{
Write-Output "Caught: "
}
In the above, while not elegant, a script-terminating error is caught and Caught: ScriptHalted is printed.
try
{
Get-ChildItem C:\nonExistignFile.txt || Write-Output 'No File'
}
catch
{
Write-Output "Caught: $_"
}
In the following example a non-terminating error happens when no file is found and ‘No File’ is printed.
One of the goals of pipeline chain operators is to be able to control your pipeline and actions based on command outcome rather than command output. This is a slightly different approach than we might be use to in PowerShell. Rather than having to validate output and then take an action we can immediately take that action based on the outcome of the previous command.
It’s going to be interesting to see how widely accept these new operators become. Many of us have made do without them since the beginning of PowerShell. Though we now have access to something that has been available in many of shells like bash and cmd.exe for a long time.
The pipeline chain operator RFC has a lot of good information and further explanation on its use.
References
Pipeline Chain Operators
A “command terminating” error will also take precedence.
try
{
1/0 || Write-Output Failed
}
catch
{
Write-Output Caught:
}
Caught:
Note that this is two statements:
$false || $false && $true | measure | % count
False
1
unless you put the whole thing in $( )
$($false || $false && $true) | measure | % count
2
Good points to note, thanks.