Windows has two PowerShell instances, and many people who have used it for years don't even know it. One is a built-in blue window that appears when you search for "PowerShell" in the Start Menu; the other needs to be installed separately, has a black background, and the icons look similar. Its executable file is called...pwsh.exe Their syntax is almost identical, and most of what you write in one will run in the other, so people assume they are new and old versions of the same thing.
Not entirely. The built-in PowerShell 5.1 is closed-source, tied to the assignment system, and has only been patched since 2016. PowerShell 7 is a separate project, licensed with MIT, cross-platform, and requires separate installation. The job scheduler defaults to calling 5.1. I've encountered the differences between the two several times while writing backup scripts, each time in the middle of the night, and each time I thought there was a problem with my program. Here's a summary.
native program stderr
In 5.1, if you put$ErrorActionPreference Set as'Stop' In native code, every line written to stderr is wrapped in a...NativeCommandError And terminate the script. Even if the program's exit code is 0, even if you have redirected stderr...$null The same thing happened.
I first encountered this in a script waiting for a virtual machine to boot up. When the other machine boots up but before the sshd is running, SSH prints the line "Connection timed out during banner exchange," which is normal diagnostic output, meaning "Not yet, wait a bit." In Android 5.1, it killed my script, and the next step of the script was to check if the service had recovered. So the virtual machine booted up, the service was running, but the script was dead. No one confirmed it, and what I saw in the morning was a failed scheduling record and a perfectly fine machine.
PowerShell 7 by default does not treat native programs' stderr as errors, only their exit codes. The general rule is: in version 5.1,'Stop' You can't naively mix calling native programs with this setting. That setting protects cmdlets but misinterprets native programs. It's like a very conscientious security guard who sounds an alarm at any sound, including the sound of you opening the door yourself.
Script file encoding
5.1 Reading.ps1 In some cases, files without a BOM (Bill of Materials) are treated as the system's ANSI code page (cp950 in Traditional Chinese environments). UTF-8 Chinese characters within these files will appear as gibberish, causing all string comparisons to fail. If your script includes the statement "if the filename contains 'backup'", PowerShell 7 will display a string of gibberish and will never match. PowerShell 7 defaults to UTF-8. To ensure the same script works correctly on both systems, saving it as a UTF-8 file with a BOM is the most reliable method.
The BOM (Bill of Materials) is the first three invisible bytes of a file, used to tell the reader "I am UTF-8". Many editors omit it by default because it can cause problems in the Linux world; in the Windows 5.1 world, its absence causes even more problems. It's one of those things where "both sides are correct, just different".
Exit 0, but did nothing.
This is the most dangerous kind, so dangerous that I need to dedicate an entire section to it.
I encountered this in a constrained execution environment: doing external disksRename-Item ,New-Item ,Set-Content When PowerShell performs a native write operation like this, the process terminates abruptly at that line, without exception or output, but the exit code is 0; whilerobocopy ,cmd /c move The writing of this type of subroutine is normal.
Imagine this scenario: the schedule reports success, the logs look normal, and you assume the backup is complete. Three weeks later, you need that backup, open the disk, and find only a temporary directory; the actual directory has never appeared. This phenomenon may not reproduce in your environment, but its form of failure is worth guarding against because "doing nothing quietly" is a hundred times worse than "failing loudly."
There are two solutions. Important relocations should be done using subroutines.robocopy orcmd /c move After completing the task, explicitly check if the destination actually exists; if not, throw the command. Don't assume that "no errors" equals "done." This principle later extended to all my scripts: after completing a task, check the result, not just the returned value.
Mini-drama: Which one should I use?
"So I should delete version 5.1?"
"You can't delete it; it's part of Windows. And many system tools still use it."
"How do I know which one I'm using?"
"Blue is 5.1, black is 7. Or type..."$PSVersionTable It will tell you.
"I always thought color was just a theme."
"Many people think that way, and then their scripts get killed by stderr in the middle of the night."
Syntax only PowerShell 7 has
&&and|| Pipeline chain, ternary operator,?? Null value merging is only available in version 7. If the script needs to run on both sides, it cannot be used; if it only runs on version 7, it should be placed at the beginning of the file.#requires -Version 7 Let 5.1 reject it outright, rather than failing grammatically halfway through. "Rejecting it from the start" is always better than "failing halfway through," because you may have already made some changes by the time you're halfway through.
$PSScriptRoot in parameter default values
A minor pitfall in section 5.1: Using the default parameter value expression...$PSScriptRoot In some startup methods, it is an empty string.Join-Path It will fail before the first line of log is written. You won't even see it fail because the log hasn't started writing yet. Move the parsing of the preset path to after the parameter link. I spent an entire night finding this bug, and finally added it to every line.Write-Host Only then did I realize that it hadn't even reached the first line.
My current approach
All new scripts are written for PowerShell 7, and the job scheduler explicitly points to...pwsh.exe To prevent it from using the default one, it must run the old script in 5.1. The native program call is wrapped in a layer, first...$ErrorActionPreference Temporarily changed backContinue Then, determine the exit code yourself.
The two generations of PowerShell are not very different, but each one is hidden in the kind of scripts no one reads at 3 a.m. The blue and black versions may seem like just different themes, but they are actually two things that will bite you in different ways.
A little about the origins of PowerShell
In 2002, PowerShell designer Jeffrey Snover wrote a document called the Monad Manifesto explaining why Windows needed a true command-line environment, and why it should pass objects instead of text. Unix shells rely on text pipes...grep Finishawk Each step involves re-parsening the string; PowerShell's pipeline passes .NET objects.Get-Process | Where-Object CPU -gt 100 The CPU in the file is an attribute, not a column number. This idea became PowerShell 1.0 in 2006 and then followed Windows for ten years.
In 2016, Microsoft open-sourced it and began developing a cross-platform version, initially called PowerShell Core 6. In 2020, version 7.0 dropped the "Core" and became PowerShell 7. It can run on Linux and macOS, and its executable file is named...pwsh With built-inpowershell.exe They coexist. That's why there are two versions of Windows today: one is the final version from ten years ago, and the other is an open-source project that is still evolving.
The object pipeline works just as well in both 5.1 and 7. The differences discussed in this article are all in the peripherals, but unattended scripts happen to thrive in the peripherals.
Three common misunderstandings
The first misconception is that "version 5.1 is replaced after installing version 7." No, both coexist. The job scheduler, right-click menu, and many system tools are still defaulted to version 5.1. You need to explicitly specify it.pwsh.exe It will only use 7 if it does that.
The second misconception is that "PowerShell is Windows bash." Their philosophies are different. Bash lacks object pipelines, verb-noun command names, and direct access to .NET. Trying to force your way into bash will only lead to frustration; it's much faster to learn it as something new.
The third misunderstanding is "to put$ErrorActionPreference Set as'Stop' "Making scripts safer." This applies to cmdlets, but has the opposite effect on native programs in version 5.1. Security comes from knowing how each error will be handled, not from a global toggle.
A small exercise you can try over the weekend
Write a script for the Five Elements: The first line...$ErrorActionPreference Set as'Stop' second linessh -o BatchMode=yes nonexistent.invalid (For a non-existent host, you can add a very short connection timeout to avoid waiting too long), third lineWrite-Host "我還活著" Save it as a UTF-8 file with BOM (Bill of Materials).
Run it once using the built-in blue window, then use...pwsh Run it once. You'll see that "I'm still alive" isn't printed in the blue window; the script died on the ssh line. It's printed in the black window. The same script, two results. Then remove the BOM and save it again, run it once on each side, and see how the Chinese sentence changes. These twenty minutes cover the two most common pitfalls discussed in this article, and they're happening on your computer, visible to your eyes.
Postscript
The blue and black windows later became a habit for me: when I turned on the terminal, I would glance at the color first, like checking the weather before leaving the house. It was a small habit, but it prevented many late-night errors. Sometimes, the most effective protection in engineering isn't some sophisticated mechanism, but simply "taking a look." A glance at the color, a glance at the exit code, a glance at whether the file actually appeared at the destination. Those silent failures can only be caught with a glance.
Project page: github.com/PowerShell/PowerShell