Effective PowerShell Item 2: Use the Objects Luke. Use the Objects!

Using Windows PowerShell requires a shift in your mental model for how command line shells deal with information.  In most shells like CSH, Korn shell, BASH, etc you deal primarily with information in text form.  For instance the output of ls or ps is captured into a string variable and cut, prodded and parsed to coax out the required piece of information.  As it turns out, PowerShell provides very handy text manipulation functions like:

  • -like
  • -notlike
  • -match
  • -notmatch
  • -replace
  • -eq
  • -ne
  • -ceq (case-sensitive)
  • -cne (case-sensitive)

Note that by default, PowerShell treats all text (actually System.String objects) case-insensitive when doing things like a comparison or a regular expression search or replace.  Because of these handy string manipulation features, it is very easy to "fall back" into the old way of string cutting, parsing and string comparisons.  Sometimes this is unavoidable even in PowerShell but many times you can just use the object provided to you.  The benefits are often:

  • Easier to understand code
  • Easier to avoid mistakes (bad regexes, incorrect comparison technique)
  • Better performance

Let’s look at an example.  The following issue came up in the public.microsoft.windows.powershell newsgroup recently.  How do you test the output of dir a.k.a. get-childitem to filter out directories leaving only the files to be operated on further down the pipeline?  Here’s an approach to this problem that I think of as "falling back" into the old ways:

PS C:\Windows\System32> get-childitem | where-object {$_.mode -ne "d"}

First let me point out that this command doesn’t work but more importantly it relies on string comparisons to determine whether or not an item passing down the pipeline is a folder.  If you are bent on doing the filtering the "old way" then the following will work however vis-a-vie the previous solution it illustrates how easy it is to get the string comparison wrong:

PS C:\Windows\System32> get-childitem | where-object {$_.mode -notlike "d*"}

Yet there is a better approach for this type of problem – the PowerShell way.  PowerShell decorates every item that is output by the Get-ChildItem and *-Item CMDLETs with additional properties.  This is even independent of which provider is being used – file system, registry, function, etc.  We can see those extra properties, all of which are prefixed with PS, by using our old friend Get-Member like so:

PS Function:\> new-item -type function "foo" -value {} | gm

   TypeName: System.Management.Automation.FunctionInfo

Name            MemberType   Definition
—-            ———-   ———-
Equals          Method       System.Boolean Equals(Object obj)
GetHashCode     Method       System.Int32 GetHashCode()
GetType         Method       System.Type GetType()
get_CommandType Method       System.Management.Automation.CommandTypes…
get_Definition  Method       System.String get_Definition()
get_Name        Method       System.String get_Name()
get_Options     Method       System.Management.Automation.ScopedItemOp…
get_ScriptBlock Method       System.Management.Automation.ScriptBlock …
set_Options     Method       System.Void set_Options(ScopedItemOptions…
ToString        Method       System.String ToString()
PSDrive         NoteProperty System.Management.Automation.PSDriveInfo …
PSIsContainer   NoteProperty System.Boolean PSIsContainer=False
PSPath          NoteProperty System.String PSPath=Microsoft.PowerShell…
PSProvider      NoteProperty System.Management.Automation.ProviderInfo…
CommandType     Property     System.Management.Automation.CommandTypes…
Definition      Property     System.String Definition {get;}
Name            Property     System.String Name {get;}
Options         Property     System.Management.Automation.ScopedItemOp…
ScriptBlock     Property     System.Management.Automation.ScriptBlock …

One of those extra properties is PSIsContainer and this property tells us that the object is a container object.  For the registry, this means RegistryKey and for the file system it means directory (DirectoryInfo object).  So this problem can be solved more directly like so:

PS C:\Windows\System32> get-childitem | where-object {!$_.PSIsContainer}

That is a bit less to type and is much less error prone.  However what about this performance claim?  OK let’s try both of these approaches (I’ll also throw in the regex-based -notmatch) and measure their performance:

PS C:\Windows\System32>
$oldWay1 = 1..20 | measure-command {get-childitem | where-object {$_.mode -notlike "d*"}}

PS C:\Windows\System32>
$oldWay2 = 1..20 | measure-command {get-childitem | where-object {$_.mode -notmatch "d"}}

PS C:\Windows\System32>
$poshWay = 1..20 | measure-command {get-childitem | where-object {!$_.PSIsContainer}}

OK so what are the results:

PS C:\Windows\System32> $oldWay1 | measure-object TotalSeconds -ave

Count    : 1
Average  : 169.2571743
Sum      :
Maximum  :
Minimum  :
Property : TotalSeconds

PS C:\Windows\System32> $oldWay2 | measure-object TotalSeconds -ave

Count    : 1
Average  : 181.929144
Sum      :
Maximum  :
Minimum  :
Property : TotalSeconds

PS C:\Windows\System32> $poshWay | measure-object TotalSeconds -ave

Count    : 1
Average  : 61.5349126
Sum      :
Maximum  :
Minimum  :
Property : TotalSeconds

So doing a little math – in PowerShell – we get:

PS C:\Windows\System32> "{0:P0}" -f (169.26 / 61.53)
275 %

Yikes!  The string comparison approach using the Mode property is over 275% slower than using the PSIsContainer property.  With PowerGadgets we can see this:

PS C:\> $data = @{
>> ‘Mode-Notlike’=$oldWay1.TotalSeconds;
>> ‘Mode-Notmatch’=$oldWay2.TotalSeconds;
>> ‘PSIsContainer’=$poshWay.TotalSeconds;
>> }
>>
PS C:\> $data.Keys | select @{n=’Method’;e={$_}},@{n=’TotalSeconds’;e={$data[$_]}} |
>> out-chart -Title "PSIsContainer vs Mode"
>>

PowerGadgets are pretty sweet.  I use them a lot in presenting various reports to my management chain.  This is off topic but I have one chart that displays the checkin activity per day.  It is interesting to see the spike in checkins just before each iteration milestone is reached.  🙂 

In summary, keep in mind that even though the PowerShell console output gives you the illusion that you are only dealing with text, there are .NET objects behind all that text output!  You are often dealing with objects richer in information than System.String and many times those objects have just the information you are looking for in the form of a property.  You can then extract that information without resorting to text parsing.  For an additional example of operating object properties vs text, check out my post on Sorting IPAddresses the PowerShell Way

Advertisements
This entry was posted in Effective PowerShell. Bookmark the permalink.

5 Responses to Effective PowerShell Item 2: Use the Objects Luke. Use the Objects!

  1. Andrew says:

    Thanx !! Of all the PowerShell books, blogs, videos, podcasts available … I think that this single example is one of the most compelling. Any other commentary upon the re-visioning of a problem space in PowerShell terms would be most welcome.Regards
    Andy.

  2. Andrew says:

    Hi ! Keith -Any comments on the PS 1.00 > CTP 2.00 display would be welcome – Thanx again.A.
    http://thoughtware.co.nz/PowerShell/KeithHillPS.htm

  3. Keith says:

    Well it is a CTP which means that the team doesn\’t shoot for as high of quality as they would a Beta.  Plus the major focus isn\’t on performance.  The CTP is meant to give users an early peek at the "planned" new features in v2.  That way if they got it wrong somehow we would let them know before they finish the new features.  IOW if you are concerned about performance you probably don\’t want to use the CTP.  And I would not recommend installing the CTP on anything other than a test PC (something you have no qualms about re-imaging) or a Virtual PC.  I have been burned too many times with CTP products that don\’t uninstall cleanly.

  4. Jason says:

    Hi Keith,Really like your blog on PowerShell. I really like powershell, but I am young and in training. I can really see the power behind it and am working hard to better understand it.If I may ask a question.You ran the above command:new-item -type function "foo" -value {} | gmMy question is, when all of the members are returned, how were you able to determine that PSIsContainer would be the member to do the trick?Also, after you find the members of any cmdlet, how do you get further information on those members?Still learning powershell, so I might be missing part of it.Appreciate it.Jason

  5. Keith says:

    Jason, I learned that trick through osmosis.  🙂  One of the PowerShell devs suggested using that property a few years ago when PowerShell was in early betas.  There are a number of PowerShell supplied PS* members that provide standard information on provider items like the full provider path (PSPath) and whether the item is a container or leaf (PSIsContainer).  The best way to get more information on type members of standard .NET types is to do an internet search.  You could limit your search to MSND e.g. String.Join site:msdn2.microsoft.com.

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 )

Google+ photo

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

Connecting to %s