Powershell: Get-Type alternative for $null values

Sometimes its useful to get information about object properties in Powershell. You might be writing a script where you want to extract information from a series of objects and you need to know what data type a property contains. Often this is not a big problem since you can use the GetType function that most objects have.

Then you run in to the problem that I had recently: You expect a series of objects and you check the types on the properties and suddenly a few of them doesnt have the function GetType for some reason. What I realised whas that if a property is Null then a lot of features for that property doesnt work anymore. I did figure out a workaround for this, even if its not very pretty. I thought I share this in case more people stumble on the same issue.

So what is the problem? Well, it seems like powershell considers a property with a null value to not exist, kind of. Let me do an example with Get-Process.

Lets start with how it usually works, when there is no problem: Get-Process for Internet Explorer.
Get-Process returns one object per process as usual. Lets pick a property that we want to examine. I pick StartTime which I expect is when the process started, but I havent checked. It doesnt matter what it means for this example.
Then we check the data type for that property. Something like this:
# Sample any object property. 
# In this case StartTime from IE coming from from Get-Process.
$sampleObject = Get-Process -Name iexplore
$sampleObject[0].StartTime.GetType().Name
This will return "DateTime". So far so good. What if we look for a process that doesnt have a value for StartTime? How about the mysterious Idle process?
# Doesnt work if the property is empty
$sampleObject = Get-Process -Name Idle
$sampleObject[0].StartTime.GetType().Name
This returns an error: "You cannot call a method on a null-valued expression."
Thats a bit weird since its the same kind of object. They are both of the type System.Diagnostics.Process, but they seem to be different. Well, they are the same type, but powershell behaves this way when the value is null. You can get the feeling that it doesnt exist, and cannot have any functions.

There is other ways to get this information. One way is to use the output from Get-Member in a not-so-standard way. Parsing through the result to get what you need. This is a last resort, but works in most cases.

This is what you get if you pipe the object to Get-Member:
PS C:\>$sampleObject | Get-Member -MemberType Properties | Where-Object {$_.Name -eq "StartTime"}

   TypeName: System.Diagnostics.Process

Name      MemberType Definition
----      ---------- ----------
StartTime Property   datetime StartTime {get;}

Under Definition you can see "datetime". Thats what we need. So lets parse through Definition and remove everything after the first space.
# This time we are using the output from Get-Member, selecting StartTime 
# and removing anything after the space (' ').
$sampleObject = Get-Process -Name Idle
$propertyName = $sampleObject | Get-Member -MemberType Properties `
                              | Where-Object {$_.Name -eq 'StartTime'}
$propertyName.Definition.Remove( $propertyName.Definition.IndexOf(' ') )
This will output "datetime". As long as you are not case sensitive, this could work as a workaround.
I would prefer the first option to use the GetType so why not use that one first and then fall back on the second option if it doesnt work. Lets try both iexplore and Idle while we're at it.
$processes = 'Idle','iexplore'
foreach ($process in $processes){
    $sampleObject = Get-Process -Name $process
    try{
        $typeName = $sampleObject[0].StartTime.GetType().Name
    }catch {
        try{
            $propertyName = $sampleObject | Get-Member -MemberType Properties `
                                          | Where-Object {$_.Name -eq 'StartTime'}
            $typeName = $propertyName.Definition.Remove( $propertyName.Definition.IndexOf(' ') )
        }catch{
            $typeName = 'Doh!'
        }
    }
    Write-Host "$process = $typeName"
}
This should now output "datetime" for Idle and "DateTime" for iexplore.

Thats a workaround at least. I know this example with the Idle process is not really close to real life, but the concept is true for a lot of objects. Using Idle was just to prove the point. You can try these commands on your own machine. They should work on any machine, as long as Internet Explorer is running.
For some reason the custom objects that I have tried this on doesn't work with this workaround. For me it has worked with the objects returned from several built-in cmdlets, so give it a try. It might just work.

No comments

Post a Comment