Exploring new operators
Operators are symbols or combinations of keystrokes that PowerShell recognizes and assigns some meaning to. PowerShell uses the +
operator to mean addition, either arithmetic addition or string addition/concatenation. Most of the PowerShell operators were defined with Windows PowerShell V1.
PowerShell 7 now implements some new operators, including the following:
- Pipeline chain operators:
||
and&&
- Null-coalescing operator:
??
- Null-coalescing assignment operator:
??=
- Experimental null conditional member access operators:
?.
and?[]
- Background processing operator:
&
- Ternary operator:
? <if-true> : <if-false>
You see examples of these operators in this recipe.
Getting ready
This recipe uses SRV1
, a Windows Server 2020 host. You have installed and configured PowerShell 7 and VS Code. You run this, and all remaining recipes in this book, in either a PowerShell 7 console or VS Code.
How to do it...
- Using PowerShell 7 to check results traditionally
Write-Output 'Something that succeeds' if ($?) {Write-Output 'It worked'}
- Checking results with the pipeline operatorÂ
&&
Write-Output 'Something that succeeds' && Write-Output 'It worked'
- Using the pipeline chain operator ||
Write-Output 'Something that succeeds' || Write-Output 'You do not see this message'
- Defining a simple function
function Install-CascadiaPLFont{   Write-Host 'Installing Cascadia PL font...' }
- Using theÂ
||
 operator$OldErrorAction        = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' Get-ChildItem -Path C:\FOO\CASCADIAPL.TTF ||    Install-CascadiaPLFont $ErrorActionPreference = $OldErrorAction
- Creating a function to test null handling
Function Test-NCO {   if ($args -eq '42') {     Return 'Test-NCO returned a result'   } }
- Testing null results traditionally
$Result1 = Test-NCO    # no parameter if ($null -eq $Result1) {     'Function returned no value' } else {     $Result1 } $Result2 = Test-NCO 42  # using a parameter if ($null -eq $Result2) {     'Function returned no value' } else {     $Result2 }
- Testing using the null-coalescing operatorÂ
??
$Result3 =  Test-NCO $Result3 ?? 'Function returned no value' $Result4 =  Test-NCO 42 $Result4 ?? 'This is not output, but result is'
- Demonstrating the null conditional assignment operator
$Result5 = Test-NCO $Result5 ?? 'Result is null' $Result5 ??= Test-NCO 42 $Result5
- Running a method on a null object traditionally
$BitService.Stop()
- Using the null conditional operator for a method
${BitService}?.Stop()
- Testing null property name access
$x = $null ${x}?.Propname $x = @{Propname=42} ${x}?.Propname
- Testing array member access of a null object
$y = $null ${y}?[0] $y = 1,2,3 ${y}?[0]
- Using the background processing operatorÂ
&
Get-CimClass -ClassName Win32_Bios &
- Waiting for the job to complete
$JobId = (Get-Job | Select-Object -Last 1).Id Wait-Job -id $JobId
- Viewing the output
$Results = Receive-Job -Id $JobId $Results | Format-Table
- Creating an object without using the ternary operator
$A = 42; $B = (42,4242) | Get-Random $RandomTest = ($true, $false) | Get-Random if ($A -eq $B) {   $Property1 = $true } else {   $Property1 = $false } if ($RandomTest) {   $Property2 = "Hello" } else {   $Property2 = "Goodbye" } [PSCustomObject]@{   "Property1" = $Property1   "Property2" = $Property2 }
- Creating an object using the ternary operator
[PSCustomObject]@{     "Property1" = (($A -eq $B) ? $true : $false)     "Property2" = (($RandomTest) ? "Hello" : "Goodbye")    }
How it works...
In step 1, you write output, which succeeds. Then you test the value of $?
to determine whether that previous step did, in fact, succeed. The output is as follows:
Figure 2.1: Checking results traditionally
In step 2, you use the &&
operator to check that a preceding command finished without an error. The output looks like this:
Figure 2.2: Checking results with the pipeline operator
The pipeline chain operator, ||
, tells PowerShell to run the commands after the operator if the preceding command fails (in effect, the opposite to &&
). In step 3, you see the operator in use, with output like this:
Figure 2.3: Using the pipeline chain operator
In step 4, you define a function. Defining the function produces no output. This function writes output to simulate the installation of the Cascadia Code PL font.
In step 5, you check to see whether the TTF file exists, and if not, you call the Install-CascadiaPLFont
function to simulate installing the font. By piping the output from Get-ChildItem
to Out-Null
, you avoid the actual output from Get-ChildItem
, and if the file does not exist, you call the Install-CascadiaPLFont
function. The output of this snippet looks like this:
Figure 2.4: Using the || operator and installing the Cascadia font
To illustrate the handling of null results from a function, in step 6, you create a function that either returns nothing (if you call the function with no parameters) or a string value (if you call it specifying a parameter). This function illustrates how you can handle a function that returns null. This step produces no output.
In step 7, you illustrate the traditional handling of a function that returns null. You call the function, first without a parameter, that returns no result and then with a value that does return a value. You then test to see whether the function returned an actual value in each case, which looks like this:
Figure 2.5: Testing null results traditionally
When you use the null-coalescing operator (??
) between two operands, the operator returns the value of its left-hand operand if it isn't null
; otherwise, it evaluates the right-hand operand and returns the results. In step 8, you call the Test-NCO
function and check whether the function returns a value, which looks like this:
Figure 2.6: Testing using the null-coalescing operator
You use the null conditional assignment operator, ??=
, to assign a value to a variable if that variable is currently null, as you can see in step 9, the output from which looks like this:
Figure 2.7: Using the null conditional assignment operator
One common issue often seen in the various PowerShell support forums arises when you attempt to invoke a method on an object that is null. You might have used an expression or a command to attempt to return a value (for example, all AD users in the Marin County office) and that returns a null values. In step 10, you attempt to invoke the Stop()
method on the $BitService
object. Since you have not assigned a value to $BitService
, you see the result (an error, You cannot call a method on a null-valued expression
). The traditional method of displaying errors looks like this:
Figure 2.8: Running a method on a null object traditionally
By using the null conditional operator, you can run the Stop()
method if the $BitService
variable is non-null, but skip calling the method if the variable is null. In effect, what you are doing in step 11 is calling the Stop()
method if the variable is non-null, and doing nothing otherwise. Because the variable does not have a value, this step does nothing (and produces no output).
When a variable is null, whether due to an error in your scripts or because a command returns a null instead of an actual value, accessing property names can also cause errors. The output of step 12 looks like this:
You can also encounter issues with null objects when you attempt to access an array member of an object that may or may not exist. In step 13, you attempt to access an array member of an array that does not exist, followed by one that does exist. The output from this step looks like this:
Figure 2.10: Testing array member access of a null object
In step 14, you investigate the use of the background processing operator, &
. The idea is that you append this character to the end of a command or script, and PowerShell runs that code in the background. The output from this step looks like this:
Figure 2.11: Using the background processing operator
In step 15, you wait for the job you created in step 14 to complete, which looks like this:
Figure 2.12: Waiting for the job to complete
After the job has completed, in step 16, you receive and display the job's output, which looks like this:
Figure 2.13: Displaying the job's output
In step 17, you create an object using a more traditional approach. This step creates a random value for two properties. Then, you create an object using the values of these two properties. The output from this step looks like this:
Figure 2.14: Creating an object without using the ternary operator
In step 18, you use the new PowerShell 7 ternary operator. This operator tests a condition and runs different code depending on whether the result is true or false. This is very similar to what you saw in step 17, but in a lot fewer lines of code. The output of this step looks like this:
Figure 2.15: Creating an object using the ternary operator
There's more...
In step 4, the function you create simulates the installation of a font if the font does not exist. The font file, CASCADIAPL.TTF
, is a TrueType font file for the Cascadia Code Powerline font. This font is the Cascadia Code font you installed in Chapter 1, Installing and Configuring PowerShell 7.1, with the addition of symbols for Powerline. For more details on this font, see https://www.hanselman.com/blog/PatchingTheNewCascadiaCodeToIncludePowerlineGlyphsAndOtherNerdFontsForTheWindowsTerminal.aspx.
In step 5, you simulate the installation of the font if it does not already exist. When you check to test whether the TTF file currently exists, the default setting for $ErrorActionPreference
(Continue
) means you see an error message if the file does not exist. By default, when Get-ChildItem
checks to see whether the file exists, it generates an error message if the file does not exist. One approach to avoiding this error message is to set the value of $ErrorActionPreference
to SilentlyContinue
and, after ensuring that the font file now exists, set it back to the default value. The syntax is a bit convoluted unless you are familiar with it; this may be another case where not using these operators, and using the Windows PowerShell approach instead, might make the script easier to read and understand.
In steps 11 and 12, you attempt to access a property from an object. The assumption here is that you only want to invoke the method or access a property value if the object exists and you do not care otherwise. Thus, if $BitService
has a value, you call the Stop()
method, otherwise the code carries on without the script generating errors. This approach is great from the command line, but in production scripts, the approach could mask other underlying issues. As with all PowerShell features, you have to use null handling with due care and attention.
With step 14, you tell PowerShell to run a command as a background job by appending the &
character to the command. Using this operator is a more straightforward way to invoke the command as a job than by calling Invoke-Command
and specifying the command using the -ScriptBlock
or -Script
parameters.
In steps 15 and 16, you use Get-Job
, Wait-Job
, and Receive-Job
to wait for the last job run and get the output. One downside to not using Start-Job
to create a background job is that you cannot specify a job name. That means using the technique shown in step 15 to obtain the job and job results for the job created in step 14. Using that technique is thus more useful from the command line than in a production script.
In step 17, you create an object using older Windows PowerShell syntax, whereas, in step 18, you use the ternary operator. As with other operators, use this with care, and if you use these operators in production code, make sure you document what you are doing.
In this recipe, you have seen the new operators added to PowerShell 7. Most of them provide a shortcut way to perform some operation or other, particularly at the command line.