Hip, Hip, Array: Retrieving Multi-Valued WMI Properties Using Windows PowerShell
If you’ve ever written a WMI script then you’ve probably encountered the dreaded property-stored-as-an-array issue. And if you’ve failed to handle this problem properly, well, don’t feel bad: in the original Scriptomaticthe Scripting Guys forgot all about multi-valued properties. As a result, Scriptomatic1.0 didn’t always return all the data available to you (a problem that was fixed in Scriptomatic 2.0 ).
Live and learn, huh?
If you aren’t sure what we’re taking about then consider this simple little VBScript script, one designed to return the MAC address and IP address for each IP-enabled network adapter installed on a computer:
strComputer
= "."
Set
objWMIService=
GetObject
("
winmgmts:\\" &
strComputer& "\root\cimv2")
Set
colItems=
objWMIService.ExecQuery_
("Select
* From Win32_NetworkAdapterConfiguration Where
IPEnabled= True")
For Each
objItemin
colItems
Wscript.Echo
objItem.MACAddress
Wscript.Echo
objItem.IPAddress
Next
Looks harmless enough, doesn’t it? But here’s what happens when you run the script:
00:0D
:56:F8:1C:3F
C:\scripts\x.vbs(9, 5)
Microsoft VBScript runtime error: Type mismatch
Yikes. As you can see, the script starts off by echoing back the MAC address. That’s good. But then it blows up with a “Type mismatch” error when it tries to echo back the IP address. That’s bad.
The problem is that a network adapter can actually have more than one IP address; therefore, IP addresses are stored as an array. (That’s true even if a network adapter doesn’t have anyIP addresses.) Because the IPAddressproperty is an array, you can’t directly echo back the value of that property; instead, you need to set up a For Each loop and loop through the collection of values. Here’s a revised script that uses a For Each loop to correctly handle the IPAddressproperty:
strComputer
= "."
Set
objWMIService=
GetObject
("
winmgmts:\\" &
strComputer& "\root\cimv2")
Set
colItems=
objWMIService.ExecQuery_
("Select
* From Win32_NetworkAdapterConfiguration Where
IPEnabled= True")
For Each
objItemin
colItems
Wscript.Echo
objItem.MACAddress
For Each
strAddressin
objItem.IPAddress
Wscript.Echo
strAddress
Next
Next
And here’s the output we get when we run this script on a computer with a single IP-enabled network adapter:
00:0D
:56:F8:1C:3F
192.168.244.186
Much better.
If you write your scripts in VBScript, well, problem solved: just make sure you use a For Each loop any time you encounter a multi-valued property such as IPAddress. But what if you’re getting started with the new Windows PowerShell ? If that’s the case, then you might not know how to set up a For Eachloop to loop through a multi-valued property. Is there a similar solution for Windows PowerShell scripters, one just as easy as using a For Each loop?
To answer that question, let’s take a look at a Windows PowerShell script that doesn’tuse a For Each loop to iterate values in the IPAddressproperty:
$
strComputer= "."
$
colItems= get-
wmiobject-class
"Win32_NetworkAdapterConfiguration" -namespace "root\CIMV2"
`
-
computername$
strComputer-filter "
IPEnabled= true"
foreach
($
objItemin $
colItems) {
write-host "
MACAddress: " $
objItem.MACAddress
write-host "
IPAddress: " $
objItem.IPAddress
write-host
}
Note. In Windows PowerShell the grave accent character (`) is used to indicate a line break. In that respect it is equivalent to the underscore character (_) in WSH and VBScript.
Here’s the output we get when we run the script:
00:0D
:56:F8:1C:3F
192.168.244.186
From the error message we can clearly see that – wait a second: the script worked, even without a For Each loop! As it turns out Windows PowerShell, unlike VBScript, isn’t fazed by a property that stores values as an array; instead, Windows PowerShell handles the situation itself, without any additional coding on your part. And that’s a nicer touch than you might think. Admittedly, working with an array property isn’t particularly hard: after all, it’s just a matter of setting up a For Eachloop to loop through all the values. What ishard is knowingwhich properties store values as arrays and which ones don’t; if you mistakenly assume a multi-valued property is a plain old single-valued property your script will fail to return important data. Take this script, for example:
On Error Resume
Next
strComputer
= "."
Set
objWMIService=
GetObject
("
winmgmts:\\" &
strComputer& "\root\cimv2")
Set
colItems=
objWMIService.ExecQuery_
("Select
* From Win32_NetworkAdapterConfiguration Where
IPEnabled= True")
For Each
objItemin
colItems
Wscript.Echo
objItem.MACAddress
Wscript.Echo
objItem.IPAddress
Next
This script won’t return any values for the IPAddressproperty; however, because of the On Error Resume Nextstatement it won’t return an error message, either. Because there are no error messages you might assume that the script is fine and that your network adapters just don’t have any IP addresses assigned to them. That’s probably not true, so this assumption could cause problems for you later on. (For example, you might spend a lot of time trying to figure out whyyour network adapters don’t have any IP addresses assigned to them.)
But that’s a problem you won’t have to worry about with Windows PowerShell; Windows PowerShell will correctly handle both single-valued and multi-valued properties, without you having to identify and code for the multi-valued ones. And that’s pretty cool: as you might have guessed, the Scripting Guys are always in favor of anything that does our work for us.