Earlier this year, PowerShell Core became generally available under an Open Source (MIT) license. PowerShell is hardly a new technology. From its first release for Windows in 2006, PowerShell's creators sought to incorporate the power and flexibility of Unix shells while remedying their perceived deficiencies, particularly the need for text manipulation to derive value from combining commands.
Five major releases later, PowerShell Core allows the same innovative shell and command environment to run natively on all major operating systems, including OS X and Linux. Some (read: almost everyone) may still scoff at the audacity and/or the temerity of this Windows-born interloper to offer itself to platforms that have had strong shell environments since time immemorial (at least as defined by a millennial). In this post, I hope to make the case that PowerShell can provide advantages to even seasoned users.
Consistency across platforms
If you plan to port your scripts from one execution environment to another, you need to make sure you use only the commands and syntaxes that work. For example, on GNU systems, you would obtain yesterday's date as follows:
date --date="1 day ago"
On BSD systems (such as OS X), the above syntax wouldn't work, as the BSD date utility requires the following syntax:
date -v -1d
Because PowerShell is licensed under a permissive license and built for all platforms, you can ship it with your application. Thus, when your scripts run in the target environment, they'll be running on the same shell using the same command implementations as the environment in which you tested your scripts.
Objects and structured data
*nix commands and utilities rely on your ability to consume and manipulate unstructured data. Those who have lived for years with sed
grep
and awk
may be unbothered by this statement, but there is a better way.
Let's redo the yesterday's date example in PowerShell. To get the current date, run the Get-Date
cmdlet (pronounced "commandlet"):
> Get-Date
Sunday, January 21, 2018 8:12:41 PM
The output you see isn't really a string of text. Rather, it is a string representation of a .Net Core object. Just like any other object in any other OOP environment, it has a type and most often, methods you can call.
Let's prove this:
> $(Get-Date).GetType().FullName
System.DateTime
The $(...)
syntax behaves exactly as you'd expect from POSIX shells—the result of the evaluation of the command in parentheses is substituted for the entire expression. In PowerShell, however, the $ is strictly optional in such expressions. And, most importantly, the result is a .Net object, not text. So we can call the GetType()
method on that object to get its type object (similar to Class
object in Java), and the FullName
property to get the full name of the type.
So, how does this object-orientedness make your life easier?
First, you can pipe any object to the Get-Member
cmdlet to see all the methods and properties it has to offer.
> (Get-Date) | Get-Member
PS /home/yevster/Documents/ArticlesInProgress> $(Get-Date) | Get-Member
TypeName: System.DateTime
Name MemberType Definition
---- ---------- ----------
Add Method datetime Add(timespan value)
AddDays Method datetime AddDays(double value)
AddHours Method datetime AddHours(double value)
AddMilliseconds Method datetime AddMilliseconds(double value)
AddMinutes Method datetime AddMinutes(double value)
AddMonths Method datetime AddMonths(int months)
AddSeconds Method datetime AddSeconds(double value)
AddTicks Method datetime AddTicks(long value)
AddYears Method datetime AddYears(int value)
CompareTo Method int CompareTo(System.Object value), int ...
You can quickly see that the DateTime object has an AddDays
that you can quickly use to get yesterday's date:
> (Get-Date).AddDays(-1)
Saturday, January 20, 2018 8:24:42 PM
To do something slightly more exciting, let's call Yahoo's weather service (because it doesn't require an API token) and get your local weather.
$city="Boston"
$state="MA"
$url="https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22${city}%2C%20${state}%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys"
Now, we could do things the old-fashioned way and just run curl $url
to get a giant blob of JSON, or...
$weather=(Invoke-RestMethod $url)
If you look at the type of $weather
(by running echo $weather.GetType().FullName
), you will see that it's a PSCustomObject
. It's a dynamic object that reflects the structure of the JSON.
And PowerShell will be thrilled to help you navigate through it with its tab completion. Just type $weather.
(making sure to include the ".") and press Tab. You will see all the root-level JSON keys. Type one, followed by a ".
", press Tab again, and you'll see its children (if any).
Thus, you can easily navigate to the data you want:
> echo $weather.query.results.channel.atmosphere.pressure
1019.0
> echo $weather.query.results.channel.wind.chill
41
And if you have JSON or CSV lying around (or returned by an outside command) as unstructured data, just pipe it into the ConvertFrom-Json
or ConvertFrom-CSV
cmdlet, respectively, and you can have your data in nice clean objects.
Computing vs. automation
We use shells for two purposes. One is for computing, to run individual commands and to manually respond to their output. The other is automation, to write scripts that execute multiple commands and respond to their output programmatically.
A problem that most of us have learned to overlook is that these two purposes place different and conflicting requirements on the shell. Computing requires the shell to be laconic. The fewer keystrokes a user can get away with, the better. It's unimportant if what the user has typed is barely legible to another human being. Scripts, on the other hand, are code. Readability and maintainability are key. And here, POSIX utilities often fail us. While some commands do offer both laconic and readable syntaxes (e.g. -f
and --force
) for some of their parameters, the command names themselves err on the side of brevity, not readability.
PowerShell includes several mechanisms to eliminate that Faustian tradeoff.
First, tab completion eliminates typing of argument names. For instance, type Get-Random -Mi
, press Tab and PowerShell will complete the argument for you: Get-Random -Minimum
. But if you really want to be laconic, you don't even need to press Tab. For instance, PowerShell will understand
Get-Random -Mi 1 -Ma 10
because Mi
and Ma
each have unique completions.
You may have noticed that all PowerShell cmdlet names have a verb-noun structure. This can help script readability, but you probably don't want to keep typing Get-
over and over in the command line. So don't! If you type a noun without a verb, PowerShell will look for a Get-
command with that noun.
Caution: although PowerShell is not case-sensitive, it's a good practice to capitalize the first letter of the noun when you intend to use a PowerShell command. For example, typing date
will call your system's date
utility. Typing Date
will call PowerShell's Get-Date
cmdlet.
And if that's not enough, PowerShell has aliases to create simple names. For example, if you type alias -name cd
, you will discover the cd
command in PowerShell is itself an alias for the Set-Location
command.
So to review—you get powerful tab completion, aliases, and noun completions to keep your command names short, automatic and consistent parameter name truncation, while still enjoying a rich, readable syntax for scripting.
So... friends?
There are just some of the advantages of PowerShell. There are more features and cmdlets I haven't discussed (check out Where-Object or its alias ?
if you want to make grep
cry). And hey, if you really feel homesick, PowerShell will be happy to launch your old native utilities for you. But give yourself enough time to get acclimated in PowerShell's object-oriented cmdlet world, and you may find yourself choosing to forget the way back.
23 Comments