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.