The native Windows command line, the one derived from DOS, is objectively painful.
On the one hand, the batch language is full of hacks that have cropped up over the years. These hacks exist to offer new features while maintaining strict backwards compatibility, a heroic effort with nasty consequences. On the other hand, the interactive editing features of cmd.exe
are rudimentary1.
Fortunately, PowerShell exists as a first-party, built-into-Windows alternative to cmd.exe
.
A blog on operating systems, programming languages, testing, build systems, my own software projects and even personal productivity. Specifics include FreeBSD, Linux, Rust, Bazel and EndBASIC.
PowerShell is a very powerful command-line environment originally designed to support Windows administration. As an advanced shell, it even has neat editing features. Early on in my experimentation, I discovered that you can have a vi editing mode in PowerShell:
Set-PSReadlineOption -EditMode vi -BellStyle None
Set-PSReadlineKeyHandler -Key Tab -Function MenuComplete
And just because of this, I was an immediate convert. Navigating the command line efficiently is a must for productivity while programming and this little customization was a game-changer for me.
Knowing that I had to deal with PowerShell and get better at it, I bought the PowerShell in Action book (the first edition, second hand) and read it cover-to-cover. I didn’t really learn the language this way but I got a general glimpse of it and the original design principles behind this environment. And I have to confess that most of the design principles behind the language are well-aligned with my own sense of design. Objects! Orthogonal syntax! Extensibility via plugins! Yes! So I must like it.
Mustn’t I?
Syntax difficulties
I have not been able to come to terms with PowerShell’s verbosity yet. Yes, piping objects and doing fancy things on them is extremely powerful, but most of the time I just want to do things like ps ax | grep badprocess
and… it hurts. Get-Process | ? { $_.Name -like "*badprocess*" }
or its simpler variant ps | ? Name -like badprocess
don’t seem much longer than their Unix equivalent, but the presence of various non-alphanumeric characters make them more tedious to type.
Array-like arguments are another difference that bites me every single time. I still haven’t been able to get over the fact that cp a b c
should be cp a,b c
, and that rm a b c
should be rm a,b,c
. These make a ton sense from a design perspective but my muscle memory hasn’t accepted them.
System and tools integration
As the book I read puts it, PowerShell is… a late addition to Windows administration. It would be awesome if there were cmdlets for all imaginable things you would want to do from the command line and interact with Windows without touching the GUI at all… but that’s not the case.
There will be cases where you have to fall back to “old” tools that spit out plain text instead of objects because they don’t have counterparts in PowerShell. The book mentioned that this would get better over time (and it has), but the book edition that I read was published in 2007 and I’m still finding these scenarios in 2022.
Similarly, a lot of the tools we use these days on the command line come from Unix systems. Git is a must for any developer’s toolbox. This heterogeneity of tools is problematic because they all have different command-line parsing and quoting rules, which as I mentioned before is a core problem of Windows’ process execution. Path delimiter differences between Windows and what original Unix tools expect are a nightmare as well in this regard.
And that’s just scratching the surface. There are other scenarios where you’ll have to use the Windows Management Instrumentation (WMI) to get what you want, and in some others you’ll have to write actual code—in the shell!—to load DLLs and execute their functions. These are both extremely powerful, but the syntaxes are not consistent with native PowerShell2 and thus are not super convenient to use.
Speed… or not
PowerShell is yet another Windows component that feels bloated. On my Surface Go 2, opening a PowerShell session after a cold boot takes about 5 seconds while opening a cmd.exe
instance takes a few milliseconds only. Subsequent executions of PowerShell are in the hundreds of milliseconds range as well, but that first execution is painful and I’m sure it’s because of the need to bring up the .NET VM.
And you’d say that this problem is because the Go 2 is underpowered, which is partially true, but we are talking about massive differences in load times that are noticeable even on powerful hardware. Launching terminals feels sluggish in all machines I’ve tried so far, which still gives me this feeling that the console is not an integral part of the system. (Yeah, it has never been, but we already knew that.)
File encodings
And because we are talking about the shell, it’s important that we talk about file encodings and line endings as well.
In the Unix world, UTF-8 and LF line endings are pretty much all you will see these days. Not so much on Windows. For historical reasons, Windows APIs favor UTF-16 and text files use CRLF as line endings in addition to being UTF-16.
Differences in encodings and line endings become a problem when you try to interoperate with Unix systems and, of course, WSL. Creating files on Windows, via the GUI or PowerShell, and then interacting with them via WSL is a “fun” experience. Say you create a text file in UTF-16 by “mistake”. Voila. grep
won’t work on it:
PS C:\Users\jmmv> echo 'hello' >test.txt
PS C:\Users\jmmv> wsl
$ grep 'hell' test.txt
$ file test.txt
test.txt: Unicode text, UTF-16, little-endian text, with CRLF line terminators
I’ve found myself reaching for iconv
and dos2unix
quite frequently, which are tools I had not touched in years, and have also had to learn how to add a BOM to an UTF-8 file by hand. Granted, this is my privilege because I primarily write in English only these days…
On a more positive side of things, BOM-less UTF-8 output is the default for PowerShell (Core) v6+ so you would not notice the above problem if you are running a modern version of this shell. Unfortunately, the version that ships with Windows is older and I haven’t yet dared to install the more modern one; maybe I should.
As a tip: if you are going to work on the same Git repository from Unix systems (including WSL) and Windows, configure Git to normalize line endings. This is as easy as creating a .gitattributes
file at the top of the repository with the following contents:
* text=auto
Do this ASAP before you create a mess of different line endings in the checked-in files.
Up next
I use PowerShell daily now for work purposes and I’m much more comfortable with it than I was a year ago. I know that there are tons of people out there happily using this shell on a regular basis and are much more productive than I am at it, so my difficulties are primarily due to my own lack of knowledge. Unfortunately, I’m not convinced that it’s worth investing much more of my learning effort on it because most of my time goes into WSL anyway.
If you like PowerShell at all, be it its specific implementation or the design concepts behind it, I should point you towards Nushell—a new shell for Unix platforms that shares many of these design ideas. I haven’t used it though.
Tomorrow, let’s talk about networked file systems and we’ll be approaching the end of this long series!
Now that I’m finally adjusting to Windows shortcuts, I’m discovering that the old console is a bit richer than what I originally thought regarding editing features, but it’s still… very limited when compared to other shells. ↩︎
As I was writing this article, I have found out that
wmic.exe
is finally deprecated and that, as of Windows 10 21H2, PowerShell has native interaction points with WMI. One less source of inconsistencies but it has taken years to get here! ↩︎