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.ArrayPS 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.
2 comments:
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.
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?
Post a Comment