How To Access .NET Property Types Dynamically In PowerShell

How To Access .NET Property Types Dynamically In PowerShell

This article explains how you can access object properties in a dynamic way using PowerShell.

Question

So I have this given list of objects. I don’t know their structure beforehand. I want to be able to access their property values and underlying data types to accomplish some other task.

Here is some dummy data as an example:

$DataList = (
    @{ Id = 1; Name = "Red Pumpkin" },
    @{ Id = 2; Name = "Green Pumpkin" },
    @{ Id = 3; Name = "Blue Pumpkin" }
) | %{ New-Object PSObject -Property $_ };

How do I do this?

Short Answer

Make use of the Get-Member Cmdlet plus some PowerShell syntax sugar.

# get the properties
$Properties = $DataList |
    Get-Member -MemberType Property, NoteProperty

# access the powershell member information
# enumerate the data items
foreach ($DataItem in $DataList)
{
    # enumerate the properties
    foreach ($Property in $Properties)
    {
        # get the property name
        $PropertyName = $Property.Name;

        # get the property value
        $PropertyValue = $DataItem.$($Property.Name);

        # get the property .net type
        $PropertySystemType =
            $DataItem.$($Property.Name).GetType().FullName;

        # output them nicely
        New-Object PSObject -Property @{
            Name = $PropertyName;
            Value = $PropertyValue;
            SystemType = $PropertySystemType } |
        Select-Object Name, Value, SystemType;
    }
}

This example takes the given list and outputs this:

Name Value         SystemType                            
---- -----         ----------                            
Id   1             System.Int32
Name Red Pumpkin   System.String
Id   2             System.Int32
Name Green Pumpkin System.String
Id   3             System.Int32
Name Blue Pumpkin  System.String

This should work for most scenarios. However, there is some interesting stuff going on here, and some caveats to be aware of.

Long Answer

The Get-Member Cmdlet is a straightforward way of listing all properties of an object, both native and those added by PowerShell as NoteProperty.

$DataList | Get-Member -MemberType Property, NoteProperty

The example above returns the result below:

Name MemberType   Definition
---- ----------   ----------
Id   NoteProperty System.Int32 Id=1
Name NoteProperty System.String Name=Red Pumpkin

This is useful as it provides the names of the properties to be begin with. Sadly, the underlying system type comes mangled in a string in that Definition property.

However, if we only need to access the value of each property, we only need to do something as:

$DataItem."PropertyNameGoesHere"

This is the advantage of an interpreted language. PowerShell doesn’t care if we access that property via its property accessor or via a string with the name of the property. PowerShell just figures it out.

This is why the below works in the context of the previous code:

$DataItem.$($Property.Name);

What we’re doing here is simply using PowerShell’s expression syntax to grab the name of the property as a string by calling $Property.Name and then using it immediately to access the property value in $DataItem.

Retrieving the underlying .NET System.Type works the same way:

$DataItem.$($Property.Name).GetType()

There is, however, one caveat to be aware of. And that is our good friend, the $Null value.

Let’s say we go back to the sample data and change it to look like this:

$DataList = (
    @{ Id = 1; Name = "Red Pumpkin" },
    @{ Id = 2; Name = "Green Pumpkin" },
    @{ Id = 3; Name = $Null }
) | %{ New-Object PSObject -Property $_ };

If any of those fields happens to be $Null, we will be greeted with a nice message like:

You cannot call a method on a null-valued expression.
At line:32 char:25
+             $DataItem.$($Property.Name).GetType().FullName;
+                         ~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull

Which does make sense. $Null is not a value. It marks the absence of a value, so there is nothing to call GetValue() on.

We can handle this in a couple of basic ways:

  • Don’t use $Null at all in the source data or use known placeholders for it (i.e. avoid the problem if we can).
  • Use a “typed” $Null (aka a non-null property with a default value, aka cheating).
  • Ignore or handle the error.

To use a “typed” $Null, we can go back to the sample data and do this instead:

$DataList = (
    @{ Id = 1; Name = "Red Pumpkin" },
    @{ Id = 2; Name = "Green Pumpkin" },
    @{ Id = 3; Name = [String]$Null }
) | %{ New-Object PSObject -Property $_ };

One would expect PowerShell to use a Nullable .NET type here. However, what PowerShell does is create an actual String object using an empty string as a default value. This works the same for other types too. For example, creating a “null” integer ends up creating a non-null integer with a default value of zero.

Something else we can do is to suppress the error and, perhaps, returns a default value. This, for example, suppresses the null related error and returns a $Null of its own:

$PropertySystemType = try {
    $DataItem.$($Property.Name).GetType().FullName;
} catch { $Null }

Granted we can write cleaner code than this, but it does the job.

Depending on our needs (maybe we always need a valid type), we can also fake a default system type when the property is null.

$PropertySystemType = try {
    $DataItem.$($Property.Name).GetType().FullName;
} catch { [string].FullName }

Of course, there will be as many ways to handle these issues as there are scenarios, but this can be a starting point. Here is a simple example:

Function Form

With the above knowledge in hand, we can then write ourselves a helpful function to grab those data types whenever we need to:

function Get-SystemProperties
{
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Object]
        $Source,

        [Parameter(Mandatory = $false)]
        [System.Type]
        $TypeIfNull = $Null
    )

    begin
    {
    }

    process
    {
        $Source |
            %{
                $Item = $_;
                $Item |
                    Get-Member -MemberType Property, NoteProperty |
                    %{
                        $Property = $_;
                        New-Object PSObject -Property @{
                            Name = $Property.Name;
                            Value = $Item.$($Property.Name);
                            DataType = try
                            {
                                $Item.$($Property.Name).GetType();
                            }
                            catch
                            {
                                $TypeIfNull
                            }
                    };
                }
            }
    }

    end
    {
    }
}

Grabbing those properties then becomes as compact as:

$SomeObject | Get-SystemProperties -TypeIfNull String

Of course, loads of stuff can be improved here, as always.

Jorge Candeias's Picture

About Jorge Candeias

Jorge helps organizations build high-performing solutions on the Microsoft tech stack.

London, United Kingdom https://jorgecandeias.github.io