PowerShell: The Programmer’s Shell

A couple years ago, I switched all my workstations to PowerShell. Folks often ask me why I did that. What made it worth the trouble of learning a new syntax? Here are the things about Posh that made me switch:

Mac, Linux, and Windows Support

I usually run PowerShell on Mac OS, but it also supports Linux and Windows. It gives me a standardized interface to all three operating systems.

Object Oriented Model

This is the big reason. It’s the thing that makes PowerShell a programming language like Python or Ruby instead of just a scripting language like Bash.

In Bash everything is a string. Say we’ve found something on the filesystem:

bash-3.2$ ls -l | grep tmp
drwxr-xr-x   4 adam  staff   128 Oct 15 18:10 tmp

If we need that Oct 15 date, we’d parse it out with something like awk:

bash-3.2$ ls -l | grep tmp | awk '{print $6, $7}'
Oct 15

That splits the line on whitepace and prints out the 6th and 7th fields. If the whitespacing of that output string changes (like if you run this on someone else’s workstation and they’ve tweaked their terminal), this will silently break. It won’t error, it just won’t parse out the right data. You’ll get downstream failures in code that expected a date but got something different.

PowerShell is object oriented, so it doesn’t rely on parsing strings. If we find the same directory on the filesystem:

PS /Users/adam> Get-ChildItem | Where-Object Name -Match tmp

    Directory: /Users/adam

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/15/2020  6:10 PM                tmp

It displays similarly, but that’s just formatting goodness. Underneath, it found an object that represents the directory. That object has properties (Mode, LastWriteTime, Length, Name). We can get them by reference:

PS /Users/adam> Get-ChildItem | Where-Object Name -Match tmp | Select-Object LastWriteTime

LastWriteTime
-------------
10/15/2020 6:10:55 PM

We tell the shell we want the LastWriteTime property and it gets the value. It’ll get the same value no matter how it was displayed. We’re referencing a property not parsing output strings.

This makes Posh less fragile, but also gives us access to the standard toolbox of programming techniques. Its functions and variable scopes and arrays and dictionaries and conditions and loops and comparisons and everything else work similarly to languages like Python and Ruby. There’s less Weird Stuff. Ever have to set and unset $IFS in Bash? You don’t have to do that in PowerShell.

Streams

Streams are a huge feature of PowerShell, and there are already great articles that cover the details. I’m only going to highlight one thing that makes me love them: they let me add informative output similar to a DEBUG log line in Python and other programming languages. Let’s convert our search for tmp into a super-simple script:

[CmdletBinding()]
param()

function Get-Thing {
    [CmdletBinding()]
    param()
    $AllItems = Get-ChildItem
    Write-Verbose "Filtering for 'tmp'."
    return $AllItems | Where-Object Name -Match 'tmp'
}

Get-Thing

If we run this normally we just get the tmp directory:

PS /Users/adam> ./streams.ps1

    Directory: /Users/adam

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/15/2020  6:10 PM                tmp

If we run it with -Verbose, we also see our message:

PS /Users/adam> ./streams.ps1 -Verbose
VERBOSE: Filtering for 'tmp'.

    Directory: /Users/adam

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          10/15/2020  6:10 PM                tmp

We can still pipe to the same command to get the LastWriteTime:

PS /Users/adam> ./streams.ps1 -Verbose | Select-Object LastWriteTime
VERBOSE: Filtering for 'tmp'.

LastWriteTime
-------------
10/15/2020 6:10:55 PM

The pipeline reads objects from a different stream, so we can send whatever we want to the verbose stream without impacting what the user may pipe to later. More on this in a future article. For today, I’m just showing that scripts can present information to the user without making it harder for them to use the rest of the output.

The closest you can get to this in Bash is stderr, but that stream is used for more than just information, and realistically you can’t guess the impact of sending messages to it. Having a dedicated stream for verbose messages makes it trivial to provide information without disrupting behavior.

PowerShell is a big language and there’s a lot more to it than what I’ve covered here. These are just the things that I get daily value from. To me, they more than compensate for the (minimal) overhead of learning a new syntax.

Happy scripting!

Adam
Operating Ops

Need more than just this article? We’re available to consult.

You might also want to check out these related articles: