Sunday, January 18, 2009

Determining If An Object Is An Array

Over on Hal Rotettenberg’s blog (TechProsaic), he recently posted an interesting article about how to tell if an object in PowerShell is an array or a scalar. His solution was to use the Get-Type method, and to look for the BaseType property of the object. Like this:

PS C:\foo> $array = 1,2,3
PS C:\foo> $scalar = 1
PS C:\foo> $array.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS C:\foo> $scalar.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Int32                                    System.ValueType

Looking carefully at what the GetType method returns, there’s an “IsArray” property, as follows:

PS C:\foo> $a
1
2
PS C:\foo> $a.GetType().IsArray
True

Pretty cool and to some degree more .NET than an another approach I’ve used. In some scripts, I’ve just checked for the existance of the count property. An array has one, but a scalar doesn’t. I do something like this:

PS C:\foo> if ($a.count) {"array"} else {"scalar"}
array
PS C:\foo> if ($b.count) {"array"} else {"scalar"}
scalar

These three approaches (basetype, IsArray and checking for count) all work – they can help to determine whether an object (i.e. a PowerShell variable) is an array or just a scalar. However, this doesn’t always work. In particular, the approach does not give proper results if you are using COM objects. To show this problem, let’s take a look the Group Policy Management Console COM interface. In this example, I search for all the GPOs in my domain. There are currently just 4 GPOs – and these are returned. However, although checking the count works, the BaseType is not so helpful. Here’s a small script I wrote to demonstrate this

# Setup GPMC
$gpm  = new-object -com GPmgmt.Gpm
$k    = $gpm.getconstants()
$dom  = $gpm.getdomain("cookham.net", "","")   
# Search for all GPOs
$sc=$gpm.CreateSearchCriteria()
$gpos = $dom.SearchGPOs($sc)
# Now - result time
"Type         :{0}" -f $gpos.gettype()
"Base Type    :{0}" -f $gpos.gettype().basetype
If ($gpos.Count) {"Seems to be an array"} else {"Seems to be a scalar"}
"Count        :{0}" -f $gpos.Count
"An array?    :{0}" -f $gpos.gettype().IsArray

The results of this were a little surprising:

PS C:\foo> . 'C:\Users\tfl\AppData\Local\Temp\Untitled4.ps1'
Type         :System.__ComObject
Base Type    :System.MarshalByRefObject
Seems to be an array
Count        :4
An array?    :False

As you can see, the $GPOS object appears to be an array and has a count – but GetType says it’s not an array. ALso, the Type and BaseType don’t actually help all that much.

If I change the code above marginally (to search for just one GPO) as follows:

# Setup GPMC
$gpm  = new-object -com GPmgmt.Gpm
$k    = $gpm.getconstants()
$dom  = $gpm.getdomain("cookham.net", "","")   
# Search for one GPO
$sc=$gpm.CreateSearchCriteria()
#Add a searcher to search for just ONE GPO Object
$sc.add($k.SearchPropertyGPODisplayName,$k.SearchOpEquals, "GPO1")
# Now search
$gpos = $dom.SearchGPOs($sc)
# Now - result time
"Type         :{0}" -f $gpos.gettype()
"Base Type    :{0}" -f $gpos.gettype().basetype
If ($gpos.Count) {"Seems to be an array"} else {"Seems to be a scalar"}
"Count        :{0}" -f $gpos.Count
"An array?    :{0}" -f $gpos.gettype().IsArray

The results of this were even more surprising to me:

PS C:\foo> . 'C:\Users\tfl\AppData\Local\Temp\Untitled4.ps1'
Type         :System.__ComObject
Base Type    :System.MarshalByRefObject
Seems to be an array
Count        :1
An array?    :False

This time, although only one object was returned, the count property exists and it appears to be an array! I wonder if I was the only person to be marginally confused!

While the three techniques above work great for .NET objects, they don’t work well for COM Objects. You just have to know what the underlying API (COM, .NET, etc) returns. In the case of the GPMC, the APIs seem to always return a collection, even when there’s only one object occurrence returned. For most harder-core developers, this really is not an issue, but for Admins using PowerShell it can be very confusing (It took me several hours to work this out. It’s one of those things that if you are to use PowerShell richly, you just have to know.

Technorati Tags: ,,,,

2 comments:

gpoguy said...

The key here is that regardless of how many objects a call to SearchGPOs() returns, its return type is always an IGPMGPOCollection object, which means that you will always get a collection returned. This is really just a function of this particular object model rather than something PowerShell (or even COM) is doing.

Jason said...

What happens if you run this?

$($gpos).gettype().IsArray

I know that if the collection has more than one item, PowerShell will convert it into an array. But I wonder what happens if there is only one item?