Hijacking a meme for the sole glorification of PowerShell

Yesterday I saw this post on James Bennet’s blog -basically, people are posting the top 10 or so commands from their shell history. Now, using Windows on the desktop myself, I have nothing much to contribute there (I do manage a couple debian servers, but the data is probably too distributed to be useful in this context).

However, I recently decided that I would renew my interest in PowerShell (I was in the beta program early on – the pillars of Longhorn timeframe – but never did much with it). And porting this seemed like a nice opportunity to get started.

First of all, it might be worth noting that PowerShell, at least the default host, only provides a session-based history feature – close and it’s gone. Apparently it is easily possible to add persistence yourself, using a combination of the add-history cmdlet, a startup script, and aliasing exit, but I haven’t tried that (yet).

I know the suspense must be killing you, so here’s what I came up with:

PS> history | group commandline | sort count -desc | select -f 10

Or the more verbose version:

PS> get-history | group-object -property commandline | sort -property count -descending | select -first 10

That was actually pretty simple, and should be quite easy to read. Compare it to the original:

$ history | awk '{print $2}' | sort | uniq -c | sort -rn | head

Now, I like the object-based approach a lot, but you have to admit: The *nix version isn’t half bad, considering it’s text-based.

Let’s take it one step further. PowerShell conveniently stores the start and end timestamps for each history item, so instead of by count, wouldn’t it be cool to sort the list by total runtime? Unfortunately, this is considerably more difficult. The following took me about an hour (but note that I am basically new to PowerShell, and needed to figure out a lot about commands, syntax etc. along the way):

PS> history | select commandline,@{name="duration";expression={($_.endExecutionTime - $_.startExecutionTime).totalMilliseconds}} | group commandline | foreach { $_ | add-member NoteProperty Time (($_.group | measure-object -p duration -sum).sum/1000); $_ } | sort time --desc | select name,time -f 10

It’s a bit long, but – in retrospect, considering the time I spent creating it – should be moderately simple to follow as well: Calculate the runtime duration for each item, then group by the commandline string. For each group (of instances of the same command) sum up the total duration. Finally sort, and return the top 10.

There might very well be a better way. Particularly, I had trouble with the following:

  • After calculating the sum execution time for a group with measure-item, I wasn’t sure how to continue so that the next step in the pipeline has access to both the measurement result and the commandline string. I tried a hash, but failed to get sort working with it. I suppose one could use new-object, but I wasn’t keen on bothering with it’s syntax and long .NET dotted namespace hierarchies.

    As you can see, the version above simply adds the measure-item output to each group object itself, using add-member (which by itself doesn’t seem to output/return anything btw, which is why the foreach block ends with a single $_). Stuff like add-member feels slightly strange to me anyway – why is it even necessary? Why not allow adding new properties on the fly? It kind of feels like Powershell has to work around .NETs static nature. I wonder if the DLR might change some of this.

  • I’m also not sure as to how you would use measure-object to sum up timespan objects, which is why the above disposes of them early on and continues to work with milliseconds.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s