Effective PowerShell Item 6: Know What Objects Are Flowing Down the Pipe

To use Windows PowerShell pipes effectively, it really helps to know what objects are flowing down the pipe.  Sometimes objects get transformed from one type to another.  Without the ability to inspect what type is being used at each stage of the pipeline the results you see at the end can be mystifying.  For example, the following question came up on the microsoft.public.windows.powershell newsgroup:

Given a set of subdirs in a known directory, I need to cd into each directory and execute a command.

One approach to solving this is:

PS C:\> Get-Item * | where {$_.PSIsContainer} | push-location -passthru |
>>      foreach {du; pop-location}

That worked fine for my du utility because it works off the current directory.  However in the spirit of experimentation I thought I would try specifying the full path.  I was a bit surprised when it didn’t work:

PS C:\> Get-Item * | where {$_.PSIsContainer} | push-location -passthru |
>>      foreach {du $_.Fullname; pop-location}

Du v1.31 – report directory disk usage
Copyright (C) 2005-2006 Mark Russinovich
Sysinternals – http://www.sysinternals.com

No matching files were found.

So what is going on here?  Let’s see how you could find out using our old friend Get-Member:

PS C:\> Get-Item * | where {$_.PSIsContainer} | Get-Member

   TypeName: System.IO.DirectoryInfo

Name                      MemberType     Definition
—-                      ———-     ———-
Create                    Method         System.Void Create(), System.Void C…

OK that is what I expected so far – DirectoryInfo objects.  Let’s look further down the pipe:

PS C:\> Get-Item * | where {$_.PSIsContainer} | Set-Location -PassThru | Get-Member

   TypeName: System.Management.Automation.PathInfo

Name             MemberType Definition
—-             ———- ———-
Equals           Method     System.Boolean Equals(Object obj)
GetHashCode      Method     System.Int32 GetHashCode()
GetType          Method     System.Type GetType()
get_Drive        Method     System.Management.Automation.PSDriveInfo get_Dri…
get_Path         Method     System.String get_Path()
get_Provider     Method     System.Management.Automation.ProviderInfo get_Pr…
get_ProviderPath Method     System.String get_ProviderPath()
ToString         Method     System.String ToString()
Drive            Property   System.Management.Automation.PSDriveInfo Drive {…
Path             Property   System.String Path {get;}
Provider         Property   System.Management.Automation.ProviderInfo Provid…
ProviderPath     Property   System.String ProviderPath {get;}

WTF!  Set-Location took our DirectoryInfo objects and turned them into PathInfo objects and passed those down the pipe honoring my -PassThru parameter.  However in this case, Set-Location didn’t actually "pass through" the object.  It gave us an entirely new object!  You will notice that the PathInfo object doesn’t have a Fullname parameter but it does have several path related parameters.  I wonder which one we should use?  Don’t forget item 4 – know your output formatters (aka format-list * is your friend).  Let’s try it:

PS C:\> Get-Item * | where {$_.PSIsContainer} | Set-Location -PassThru |
>>      Select -First 1 | Format-List *

Drive        :
Provider     : Microsoft.PowerShell.Core\FileSystem
ProviderPath : C:\Bin
Path         : Microsoft.PowerShell.Core\FileSystem::C:\Bin

Now that we can see the property values it is pretty obvious that the ProviderPath property is the one to use when passing the path to a legacy EXE.  It is very doubtful that such an EXE would understand how to interpret the Path property.  Note that in this example I also used Select -First 1 to pick off the first directory.  This is handy if the command outputs a *lot* of objects.  There’s no use waiting for potentially thousands of objects to be processed when all you need is to see the property values for one of them.

One thing to note about Get-Member for this scenario is that it outputs a lot of type member information that is just noise when all you want to know is the type names of the objects.  Get-Member also only shows you the type information once for each unique type of object.  This gives you no sense of how many objects of the various types are passing down the pipe.  This information is easy to access via the GetType() method that is available on all .NET objects e.g.:

PS C:\> Get-ChildItem | Foreach {$_.GetType().FullName}
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.FileInfo
System.IO.FileInfo
System.IO.FileInfo

GetType() returns a System.RuntimeType object that has all sorts of interesting information.  The property we are interested in is FullName.  If I had used Get-Member instead I would have gotten about 125 lines of text surrounding the two lines indicating the type names.  In fact this sort of filter is so handy that it is worth putting in your profile:

PS C:\> filter gtn { if ($_ -eq $null) {'<null>’} else {$_.GetType().Fullname }}
PS C:\> Get-Date | gtn
System.DateTime

The PowerShell Community Extensions provides this filter however its implementation is a bit more robust.  For instance, there are occasions when it is also important to know that *no* objects were passed down the pipeline.  Our simple gtn (short for Get-TypeName) filter isn’t so helpful here:

PS C:\> @() | gtn

We get no output which is perhaps a reasonable indication that no objects were output down the pipe.  However with the PSCX implemention of this filter, we wanted to provide a bit more guidance in this situation e.g.:

PS C:\> @() | gtn
WARNING: Get-TypeName did not receive any input. The input may be an empty collection.
You can either prepend the collection expression with the comma operator e.g.
",$collection | gtn" or you can pass the variable or expression to Get-TypeName as an
argument e.g. "gtn $collection".

126> ,@() | gtn -full
System.Object[]

In summary, when debugging the flow of objects down the pipe be sure to take advantage of Get-Member to show you what properties and methods are available on those objects.  Use Format-List * to show you all the property values on those objects.  And use our handy little filter gtn (aka Get-TypeName) to see the type names of each and every individual object passed down the pipe in the order that the next cmdlet will see them.

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

1 Response to Effective PowerShell Item 6: Know What Objects Are Flowing Down the Pipe

  1. Shay says:

    WOW. I love reading your stuff!  This Effective PowerShell series is so cool and
    I was thinking, why don\’t ya write a book?
     
    Shay
    http://scriptolog.blogspot.com
     
     

Leave a comment