Question:
[Edit after both answers said lack of clarity]
My Goal: Perform different operations depending if what is passed to a function is an “object” type thing (e.g. an array, a hashtable) or just a simple “string” type thing. If it’s just a string, I’ll simply include it in the body of the email. If it’s an array or hashtable, I need to do a bunch of processing on it to convert it into an HTML table.
[Original Question Text]
I’m passing a variable $body
to a function Email-Report
that can either be a simple string or an object (e.g. a hashtable or array).
I want to check if $body
is an object and do different things depending.
My problem is $body
could be almost anything, not just a string or a hashtable. So I can’t just check if $body.GetType().Name -eq String
I’ve tried $body.GetType().Name
which returns
1 2 3 4 |
IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Hashtable System.Object |
However if the variable is an array BaseType
becomes System.Array
so I can’t filter on that property, also as mentioned above the $body
variable may not always be a hashtable or an array.
If it’s a hash table, $var.GetType()
returns a BaseType of System.Object
, however I can’t seem to refer to the BaseType property.
($hash.GetType()).BaseType
returns another object itself, which itself has a BaseType property which is blank.
I’ve also tried $body.IsObject
and $body.IsObject()
but those methods don’t seem to exist. I’ve also tried $body -eq [System.Object]
which I expected to be $true
, but it returns $false
.
Not sure where to go from here – I think I’m missing something obvious or having a logic error.
Answer:
It’s not entirely clear from the question what your goal or motivation is here, but here goes:
Every single object in PowerShell ultimately inherits from System.Object
due to the nature of .NET’s type system, so it’s a bit of a silly test to attempt to employ type identity comparison for, since you could simply do:
1 2 3 4 5 6 7 8 9 10 |
function Test-IsObject { param( [AllowNull()] $InputObject ) return $null -ne $InputObject } |
If you want to test that the the object in question is not a value type (ie. not a struct or an integral type, but a class), inspect the IsValueType
property of the type:
1 2 3 4 5 6 7 8 9 10 |
function Test-IsRefType { param( [AllowNull()] $InputObject ) return ($null -ne $InputObject -and -not $InputObject.GetType().IsValueType) } |
If you want a generalized solution to test whether a certain type occurs in the type hierarchy of an object, there are three general approaches:
- Rely on
PSTypeNames
- Use the
-is
operator - Resolve all base types yourself
PSTypeNames
All objects in PowerShell have a special property called PSTypeNames
that contains the type names of all types in the type hierarchy for the underlying object + (optionally) PowerShell-defined type extension names – this is how PowerShell differentiates formatting of instances of different CIM classes for example.
Since PSTypeName
can be manipulated by the user directly, this is inherently “unsafe”, but will work in most cases:
1 2 3 4 5 6 7 8 9 10 |
function Test-IsType { param( [object]$InputObject, [string]$TypeName ) return $InputObject.PSTypeNames -contains $TypeName } |
Builtin type operator(s)
Since PowerShell 3.0 we have two new type operators: -is
and its negated counterpart -isnot
. These actually inspect the runtime type of the underlying .NET object, so they’re safer than inspecting the synthetic PSTypeNames
property:
1 2 3 4 |
$Object -is [System.Object] # $true for any value assigned to $Object "" -is [string] # $true 5 -is [int] # $true |
-is
automatically tests for base types and interfaces as well (all statements below are $true
):
1 2 3 4 5 |
$strings = 'a', 'b', 'c' -as [string[]] $strings -is [array] $strings -is [System.Collections.Generic.IEnumerable[string]] $strings -is [object] |
These operators, along with the related -as
operator, are documented in the about_Type_Operators help topic.
Resolve type hierarchy manually
Finally, if we want to explore a little further we can resolve the type hierarchy manually by simply dereferencing GetType().BaseType
until we hit System.Object
. Below is a simple helper function that emits all the base types, which we can then compare against:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function Get-BaseType { param( [type]$Type, [switch]$IncludeLeaf ) if($IncludeLeaf){ # We're "walking backwards", so we'll start by emitting the type itself $Type } # Now go through the BaseType references # At some point we'll reach System.Object and (BaseType -eq $null) while($BaseType = $Type.BaseType){ ($Type = $BaseType) } } function Test-IsType { param( [object]$InputObject, [type]$TypeName ) return $TypeName -in (Get-BaseType -Type $InputObject.GetType() -IncludeLeaf) } |
Note that you could just use -is
instead of Test-IsType
, except if you specifically wanted to test for base classes only, not also for interfaces.