Identifying Object Types

In an object oriented world, commands return all kinds of different objects. To really find what you are looking for, identifying object types is crucial.

Identifying .NET Objects

Most often, you work with .NET objects since PowerShell is based on .NET. Cmdlets generally return .NET objects. Let's take for example a simple Dir (aka Get-Childitem) to list the contents of your windows folder:

PS> dir $env:windir

    Directory: Microsoft.PowerShell.Core\FileSystem::C:\Windows

Mode           LastWriteTime       Length Name
----           -------------       ------ ----
d----    18.11.2007    16:53        <DIR> ADAM
d----    02.11.2006    13:35        <DIR> addins
d----    11.09.2008    08:13        <DIR> AppPatch
d-r-s    13.10.2008    20:04        <DIR> assembly
d----    04.04.2008    19:17        <DIR> Boot
(...)
-a---    29.03.2007    13:11       285488 BtwIEProxy.exe
-a---    06.12.2006    18:05          478 CLNDR.CMD
-a---    16.11.2007    16:36          762 comsetup.log
-a---    15.01.2008    01:04          370 comsetup.log.zip
-a---    24.10.2007    20:19           12 csup.txt
(...)

When you start working with PowerShell, you may think Cmdlets return text when in fact all you see are objects. PowerShell simply "reduces" objects to text if you don't do anything with them, so the text output you see in the PowerShell console is just a simplified text representation of your objects. I am not going to dive deeper into this conversion today.

As you can see, though, Dir obviously returned two completely different object types: files and folders. Why on earth would you care? For example, because you might want to filter the output and show only subfolders or only files. To do that, you need to know what object type you are looking for.

With .NET, finding out object types is easy. Every .NET object has a GetType() method which returns the type information. Type information again is an object with a number of properties. The property FullName gets you the fully qualified type name of any .NET object. Check this out:

PS> dir $env:windir | % { $_.GetType().FullName }
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.DirectoryInfo
(...)
System.IO.DirectoryInfo
System.IO.DirectoryInfo
System.IO.FileInfo
System.IO.FileInfo
System.IO.FileInfo
System.IO.FileInfo
System.IO.FileInfo
System.IO.FileInfo
System.IO.FileInfo
(...)

As you discover, files are represented by a System.IO.FileInfo object whereas folders are wrapped up by a System.IO.DirectoryInfo object. To filter the output of a Cmdlet by object type, all you do is use Where-Object (short: ?) and make sure the condition is only met by the object you want. The following code lists only subfolders of a folder:

PS> dir $env:windir | ? { $_ -is [System.IO.DirectoryInfo] }

    Directory: Microsoft.PowerShell.Core\FileSystem::C:\Windows

Mode           LastWriteTime       Length Name
----           -------------       ------ ----
d----    18.11.2007    16:53        <DIR> ADAM
d----    02.11.2006    13:35        <DIR> addins
d----    11.09.2008    08:13        <DIR> AppPatch
d-r-s    13.10.2008    20:04        <DIR> assembly
(...)
d----    02.11.2006    11:24        <DIR> SchCache
PS>

Note that in PowerShell, types are always identified by square brackets. You can use types in conjunction with the -is operator to check whether an object is a specific type.

Why are there different types in the first place? Because types are specialized data containers, and each type is best at representing a specific set of information. For example, a String type can store any text whereas a DateTime type stores Dates and Times.

Of course you could store a Date or Time in the generic String type but a DateTime type can handle Dates and Times much better and display the Weekday, format  the date according to your locate and provide specific Date and Time methods to add or remove time intervals.

This is why you can also use types to convert information into the type you want:

PS> (Get-Date).GetType().FullName
System.DateTime
PS> "10/10/2008"
10/10/2008
PS> [System.DateTime]"10/10/2008"

Friday, October 10, 2008 00:00:00

And you can use types to "strongly type" a variable and make sure it only accepts data that can be converted into the specified type:

PS> # regular variables store any type, and PowerShell picks the type for you:
PS> $date1 = "10/10/2008"
PS> # here, PowerShell has chosen the plain vanilla String type:
PS> $date1
10/10/2008
PS> # chose a specific type by appending it to a variable:
PS> [System.DateTime]$date2 = $date1
PS> $date2

Friday, October 10, 2008 00:00:00

PS> # $date2 will only accept data that can be converted to the DateTime type:
PS> $date2 = "This is no valid date."
Cannot convert value "This is no valid date." to type "System.DateTime".
Error: "The string was not recognized as a valid DateTime. There is a unk
nown word starting at index 0."
At line:1 char:7
+ $date2  <<<< = "This is no valid date."

Identifying COM Objects

COM objects are a completely different ballgame. COM is the "old" technique used before .NET was invented, and COM objects have no common methods such as GetType(). How do you find the type of a COM object?

In VBScript, there was a function called TypeName() which worked almost like GetType(). This function is not available in PowerShell, though. Since you cannot access TypeName() or GetType(), are you stuck? Fortunately not.

Whenever you instantiate a COM object in PowerShell, PowerShell actually adds a .NET interface to it. The good thing is that PowerShell remembers where the COM object came from and adds the property pstypenames to every object. Let's check that out:

PS> $com = New-Object -comObject WScript.Shell
PS> $com.pstypenames
System.__ComObject#{41904400-be18-11d3-a28b-00104bd35090}
System.__ComObject
System.MarshalByRefObject
System.Object
PS> $com.pstypenames[0]
System.__ComObject#{41904400-be18-11d3-a28b-00104bd35090}

As it turns out, pstypenames is an array and gives you the conversion history, telling you the derivation of each object. In the example above, the COM object came from a COM Interface with a given GUID. The same works for .NET objects as well:

PS> (Get-Date).pstypenames
System.DateTime
System.ValueType
System.Object

Now, for COM objects a GUID is nice but does not tell you anything interesting about the interface. You are not getting the information you got from VBScript's TypeName. The GUID really is the identifier of a COM interface, and to find its name you can look it up in the registry:

PS> $com = New-Object -comObject WScript.Shell
PS> $type = $com.pstypenames[0].Split('#')[1]
PS> (Get-ItemProperty "HKLM:\SOFTWARE\Classes\Interface\$type").'(default)'
IWshShell3

The clear text interface name is "IWshShell3" which happens to be exactly the output you used to get from VBScript's TypeName() method. How did that work?

PowerShell adds the Interface GUID to the COM type, separated by "#", so you can use Split() to use this as split mark and get back a string array. The GUID is located in the second element (index 1). This is stored in $type.

Next, Get-ItemProperty looks up this GUID in your Windows Registry. Since Get-ItemProperty returns all kind of garbage and you are only interested in the (default) entry, only the result of Get-ItemProperty is taken. You then can query the Registry value you need. Since the key we are after contains special characters, it needs to be put in single quotes.

Ok, I admit this was a deep dive at parts but I still hope you enjoyed this little tour.

Tobias


Posted Oct 14 2008, 10:42 AM by Tobias Weltner
Copyright 2012 PowerShell.com. All rights reserved.