-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512


"PowerShell 7 for Programmers"
by Colin Cogle

First published Sunday, December 22, 2019.  Updated November 16, 2021.
Available online at https://colincogle.name/ps7


INTRODUCTION
Microsoft's PowerShell Team [link] and countless members of the open-
source 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 macOS and Linux for the first time, along with new fea-
tures and performance enhancements.
   [Link: https://github.com/powershell]

By December 2019,  work had all but completed on PowerShell 7, and the
final version is now available for supported Windows, macOS, and Linux
systems.[link]  In addition to an optional long-term-servicing support
model, Docker and NuGet inclusion, Desired State Configuration improve-
ments,  and good compatibility with modules that only claim to support
Windows PowerShell, there are many improvements to the language and the
runtime that will benefit developers.
   [Link: https://github.com/powershell/powershell/releases]


POWERSHELL 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.
   [See also: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pwsh?view=powershell-7#-login---l]


PARALLEL EXECUTION OF 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 or-
der is important, use a normal for or for-each loop.

Example:

   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 PowerShell.  If you don't want to force your script or module to re-
quire PowerShell 7, you'll have to wrap it in a `Try`-`Catch` block, or
do an ugly version and edition check:

   Function Out-Names {
       Param(
           [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
       } Else {
           $items | ForEach-Object -ScriptBlock $code
       }
   }

As far as I know, neither the `.ForEach()` method nor the `ForEach`
loop or statement 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:
   [Link: https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature]



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 re-
turned; otherwise, the variable itself is returned.

Let's see it in action.

   EXAMPLE 1
       BEFORE POWERSHELL 7
       If ($null -Eq $x) {
           Write-Output "nothing"
       } Else {
           Write-Output $x
       }

       IN POWERSHELL 7
       Write-Output ($x ?? "nothing")


   EXAMPLE 2
       BEFORE POWERSHELL 7
       $name = Read-Host -Prompt "File name"
       $file = Get-Item $name
       If ($null -Eq $file) {
           $file = Get-Item "default.txt"
       }

       IN POWERSHELL 7
       $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

In scripting:

   BEFORE POWERSHELL 7
       $name = Read-Host -Prompt "Enter a file name"
       If ($null -Eq $name) {
           $name = "default.txt"
       }
       Get-Item $name

   IN POWERSHELL 7
       $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 vari-
able 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 opt-
ional 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"})
               {
                   $x.Prop.Method()
               }
           }
       }

   IN POWERSHELL 7.1
       ${x}?.{Prop}?.Method()

Nice and clean.  Here's another.

   BEFORE POWERSHELL 7.1
       $filesInFolder = Get-ChildItem
       Write-Output "The third file in this folder is:"
       If ($filesInFolder.Count -ge 3) {
           $filesInFolder[2].FullName
       }

   IN POWERSHELL 7.1
       Write-Output "The third file in this folder is:"
       ${filesInFolder[2]}?.FullName


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, or use `||` to run a second com-
mand 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 state-
ment. 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
       If ($flag -Eq $true) {
           Write-Output "Yes"
       } Else {
           Write-Output "No"
       }

   IN POWERSHELL 7
       Write-Output ($flag ? "Yes" : "No")

Here's another.
   BEFORE POWERSHELL 7
       If ($user.isMember()) {
           $price += 2
       }
       Else {
           $price += 5
       }

   IN POWERSHELL 7
       $price += $user.isMember() ? 2 : 5

One more:
   BEFORE POWERSHELL 7
       $x = $items.Count
       If ($x -Eq 1) {
           $msg = "Found $x item."
       } Else {
           $msg = "Found $x items."
       }

   IN POWERSHELL 7
       $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
   split1
   split2
   split3 split4

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


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`
(alias: `iwr`)  and `Invoke-RestMethod` (`irm`) 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 with-
out cumbersome try-catch blocks, by reading the response yourself.

Those cmdlets also include 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>>

Neat!


THE RETURN OF `Get-Clipboard` AND `Set-Clipboard`
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.
   [Link: https://github.com/astrand/xclip]


MISCELLANEOUS
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 Windows users only:
   - `Clear-RecycleBin`
   - `Get-Help -ShowWindow`
   - `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 identical-
ly on Windows, macOS, and Linux.

There are 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 March 4, 2020, so
download it, get coding, and happy developing!

***

-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQQ7NZ6ap/Bjr/sGU4FSrfh98PoTfwUCYjNCDgAKCRBSrfh98PoT
f108AQCTkqPmQuffjxFG3+AhREovy0xc43pZMou0yZ5F8FLWEwD/ZSUQTPMX3m9M
yMLVzotdRJ3tlos/7Ovqzs1VQ5uUSQ4=
=uYgp
-----END PGP SIGNATURE-----