💾 Archived View for colincogle.name › blog › powershell-7 captured on 2023-01-29 at 02:28:21. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2022-06-03)
-=-=-=-=-=-=-
"PowerShell 7 for Programmers"
by Colin Cogle
First published Sunday, December 22, 2019. Updated November 16, 2021.
Microsoft's PowerShell Team 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 features and performance enhancements.
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. 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 the runtime that will benefit developers.
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.
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.
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 require 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:
https://devblogs.microsoft.com/powershell/powershell-foreach-object-parallel-feature
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.
If ($null -Eq $x) { Write-Output "nothing" } Else { Write-Output $x }
Write-Output ($x ?? "nothing")
Here's another example.
$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"
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:
$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
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.
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() } } }
${x}?.{Prop}?.Method()
Nice and clean. Here's another.
$filesInFolder = Get-ChildItem Write-Output "The third file in this folder is:" If ($filesInFolder.Count -ge 3) { $filesInFolder[2].FullName }
Write-Output "The third file in this folder is:" ${filesInFolder[2]}?.FullName
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 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 state-
ment. However, PowerShell 7 adds some popular shorthand:
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.
If ($flag -Eq $true) { Write-Output "Yes" } Else { Write-Output "No" }
Write-Output ($flag ? "Yes" : "No")
Here's another.
If ($user.isMember()) { $price += 2 } Else { $price += 5 }
$price += $user.isMember() ? 2 : 5
One more:
$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' : '')."
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
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 without 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 }
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!
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.
https://github.com/astrand/xclip
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:
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 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!
https://github.com/powershell/powershell/releases
=================================================================
Previous Blog: "Replacing OpenDKIM with dkimpy-milter"
Next Blog: "Hacking Haswell ThinkPads' Firmware"
=================================================================
"PowerShell 7 for Programmers" by Colin Cogle is licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC-BY-SA).
https://creativecommons.org/licenses/by-sa/4.0/?ref=chooser-v1