Get-TypeName Function Helps Understand What’s Going Down the Pipe

One of the most important concepts to understand about Windows PowerShell is that objects are processed in the pipeline and not text or binary.  To help understand this and to help debug your PowerShell commands that use the pipeline it is very useful to know exactly which objects or more specifically which "type" of objects are flowing down the pipeline at various stages of the pipeline.  For example, take the simple "dir" command in PowerShell:
 
1> dir
    Directory: Microsoft.PowerShell.Core\FileSystem::C:\Documents and Settings\Keith
Mode           LastWriteTime       Length Name
—-           ————-       —— —-
d—-     6/20/2006 10:38 AM        <DIR> Contacts
d—-      1/5/2007 11:03 PM        <DIR> Desktop
d-r–    12/28/2006 10:23 PM        <DIR> Favorites
d-r–    12/23/2006 12:22 AM        <DIR> My Documents
d-r–     3/23/2006  4:53 PM        <DIR> Start Menu
-a—     12/3/2006 11:28 PM        30120 PSHistory.xml
 
What most new folks to PowerShell don’t realize is that dir (aka Get-ChildItem) outputs objects and that PowerShell formats those objects into human readable text.  So how would you normally go about finding what types of objects are output by dir?  The Get-Member cmdlet an be used for this:
 
2> dir | get-member

 
   TypeName: System.IO.DirectoryInfo
Name                      MemberType     Definition
—-                      ———-     ———-
Create                    Method         System.Void Create(), System.Void Create(Direc
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(Ty
CreateSubdirectory        Method         System.IO.DirectoryInfo CreateSubdirectory(Str
Delete                    Method         System.Void Delete(), System.Void Delete(Boole
Equals                    Method         System.Boolean Equals(Object obj)
<snip>
 
   TypeName: System.IO.FileInfo
Name                      MemberType     Definition
—-                      ———-     ———-
AppendText                Method         System.IO.StreamWriter AppendText()
CopyTo                    Method         System.IO.FileInfo CopyTo(String destFileName),
Create                    Method         System.IO.FileStream Create()
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(Typ
CreateText                Method         System.IO.StreamWriter CreateText()
Decrypt                   Method         System.Void Decrypt()
Delete                    Method         System.Void Delete()
<snip>
 
There are a couple of problems with this approach. First Get-Member outputs not only the full typename but all of the members of each type: properties and methods.  This results in a lot of noise when all you wanted to know was the typename.  The second problem is this.  Get-Member only outputs the member information once for each type that it encounters in the pipeline.  Considering how much information that Get-Member already outputs for a single type this behavior is a good thing.  However it can be misleading for example:
 
3> Get-Content Readme.TXT | Get-Member
   TypeName: System.String
Name             MemberType            Definition
—-             ———-            ———-
Clone            Method                System.Object Clone()
<snip>
 
Now this output might lead you to believe that Get-Content retrieves the contents of Readme.txt as a single string but that’s not correct.  OK so let’s solve this problem with a simple filter:
 
4> filter Get-TypeName { $_.GetType().Name}
 
NOTE: This filter definition is a naive implementation so don’t use it. 
 
This filter gets us pretty much what we want:
 
5> Get-Content Readme.TXT | Get-TypeName
String
String
String
String
String
<snip>
 
This shows us clearly that Get-Content outputs multiple strings.  But how about an alias to simplify the typing:
 
6> Set-Alias gtn Get-TypeName
 
Now how about our dir example that we started with:
 
7> dir | gtn
DirectoryInfo
DirectoryInfo
DirectoryInfo
DirectoryInfo
DirectoryInfo
FileInfo
 
However our naive implementation has a few problems.  First is this:
 
8> $neverbeendefined | gtn
You cannot call a method on a null-valued expression.
At line:1 char:25
+ filter Get-TypeName { $_.GetType( <<<< ).Name }
 
It also has problems with COM objects for example:
 
9> $shell = new-object -com Shell.Application
10> $shell | gtn
Method invocation failed because [System.__ComObject#{efd84b2d-4bcf-4298-be25-eb542a59fbda}] doesn’t contain a method named ‘GetType’.
At line:1 char:25
+ filter pick { $_.GetType( <<<< ).Name }
 
It turns out that there is a better way to find the type name of an object in PowerShell.  Keep in mind that PowerShell wraps all objects and via the properties on this wrapper you can find out the type name like so:
 
11> $shell.psobject.typenames[0]
System.__ComObject#{efd84b2d-4bcf-4298-be25-eb542a59fbda}
 
The TypeNames array contains all the type names for the type’s inheritance hierarchy.  Fortunately the most derived type is always in index 0. 
 
It would also be nice to handle $null input values which is easily enough done with check against $null.  Finally the last piece we would like to solve is to provide help when you have a senior moment aka a duh moment.  I’ve been working with PowerShell for a long time and occasionally I pull one of these.  Here’s the gotcha:
 
12> $collection | Get-Member
Get-Member : No object has been specified to get-member.
At line:1 char:16
+ @() | Get-Member <<<<
 
My first reaction was – "What do you mean no object was specified.  It’s right there – $collection.  Why can’t you get me the member info on the elements of this collection!"  Keep in mind even $null get’s sent down the pipeline.  Well here’s the deal.  If the collection is empty, nothing is sent down the pipe because PowerShell always sends the contents of a collection down the pipeline rather than the collection object itself.  Well duh!  This is certainly the desired behavior and I should have remembered that.  Anyway I want my Get-TypeName function to handle this case and give some prescriptive guidance.  I have listed my complete Get-TypeName function below.  Here are some ways that it can be used:
 
13> $NonExistingVar | gtn
<null> 
 
14> gtn 1 3.14 "Hi" $true
Int32
Double
String
Boolean
 
15> $emptyColletion | 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".
 
16> ,$emptyColletion | gtn
Object[]
 
17> [xml]"<doc></doc>" | gtn -FullName
System.Xml.XmlDocument
 
OK without any further ado here’s the complete function.  Note that this updated Get-TypeName function will be included with the next release of PowerShell Community Extensions sometime towards the beginning of February.
 

set-alias gtn Get-TypeName
function Get-TypeName([switch]$FullName=$false) {
    begin {
       $processedInput = $false
       function WriteTypeName($obj) {
            if ($obj -eq $null) {
                "<null>"
            }
            else {
                $typeName = $obj.PSObject.TypeNames[0]
                if ($fullName) {
                    $typeName
                }
                else {
                    $ndx = $typeName.LastIndexOf(‘.’)
                    if (($ndx -ne -1) -and ($ndx -lt $typeName.length)) {
                        $typeName.Substring($ndx+1)
                    }
                    else {
                        $typeName
                    }
                }
            }
        }
    }

    process {
        if ($args.count -eq 0) {
            WriteTypeName $_
            $processedInput = $true
        }
    } 

    end {
        foreach ($arg in $args) {
            WriteTypeName $arg
        }
        if (!$processedInput -and ($args.Count -eq 0)) {
            Write-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".’
        }
    }
}

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

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