14.2 WMI Scripting with VBScript and Perl
The learning curve to
develop
WMI scripts is relatively short if you have any experience with a
scripting language. In fact, once you understand how to reference,
enumerate, and query objects of a particular class with WMI, it is
straightforward to adapt the code to work with any managed component,
including DNS. And fortunately, by understanding just a few
guidelines, you can convert VBScript code to Perl and vice versa.
14.2.1 Referencing an Object
To reference objects
in WMI, you use a UNC-style path name. Here is an example of how to
reference the C: drive on the host
terminator:
\\terminator\root\CIMv2:Win32_LogicalDisk.DeviceID="C:"
The first part of the path (\\terminator\) is a
reference to the computer on which the object resides. To reference
the computer on which the script is running, you can use a dot (.)
for the computer name. The second part
(root\CIMv2) is the namespace the object resides
in. Each WMI provider uses a namespace to store its associated
objects. The third part (Win32_LogicalDisk) is the
class of the object to reference. The fourth part is the key/value
pairs representing an object of that class. Generically, the path can
be described as follows:
\\ComputerName\NameSpace:ClassName.KeyName="KeyValue"[,KeyName2="KeyValue2" . . . ]
Now that we know how to reference WMI objects, let's
instantiate an object using VBScript's
GetObject function. In order for
GetObject to understand we are referencing WMI
objects, we have to include one additional piece of information: the
moniker. If you've done any Active Directory
scripting before, you're probably familiar with the
LDAP: and
WinNT: monikers used in ADSI.
For WMI, we need to use the
winmgmts: moniker:
set objDisk = GetObject("winmgmts:\\terminator" & _
"\root\CIMv2:" & _
"Win32_LogicalDisk.DeviceID='C:'")
To accomplish the same thing in Perl, we need to use the
Win32::OLE module. (The sidebar details
differences between VBScript and Perl.) Here is the same code written
in Perl:
use Win32::OLE;
$objDisk = Win32::OLE->GetObject("winmgmts:\\\\terminator" .
"\\root\\CIMv2:" .
"Win32_LogicalDisk.DeviceID='C:'");
Differences Between VBScript and PerlHere are some of the main differences between VBScript and Perl:With Perl, you have to use the Win32::OLE module to access the WMI Scripting interface. With VBScript, you simply need to call the GetObject function.Perl uses the arrow operator (->) to invoke a method on an object whereas VBScript uses a dot (.).In Perl, the backslash (\) character is the escape character, so we need to use two backslashes when using it within double quotes.Perl uses the dollar sign ($) to indicate a variable whereas VBScript doesn't use a character to distinguish variables.VBScript requires an underscore to continue a statement to the next line whereas Perl does not.Perl uses the dot (.) for concatenation whereas VBScript uses the ampersand (&).Perl requires that each statement end with a semicolon (;) whereas VBScript assumes the end of the line is the end of the statement (unless the underscore continuation character is used).Perl uses pound (#) for comments whereas VBScript uses a single quote ('). If you can keep these differences in mind, along with being able to convert basic language constructs (for loops, if then else conditionals, etc.), you should have no problems converting VBScript to Perl. |
14.2.2 Enumerating Objects of a Particular Class
Now let's enumerate all logical
disks on a machine. To do so, we
need to use the InstancesOf method on a WMI object
pointing to the namespace of the provider that contains the class. An
example should make this clear:
strComputer = "."
set objWMI = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
set objDisks = objWMI.InstancesOf("Win32_LogicalDisk")
for each objDisk in objDisks
Wscript.Echo "DeviceID: " & objDisk.DeviceID
Wscript.Echo "FileSystem: " & objDisk.FileSystem
Wscript.Echo "FreeSpace: " & objDisk.FreeSpace
Wscript.Echo "Name: " & objDisk.Name
Wscript.Echo "Size: " & objDisk.Size
WScript.Echo "
next
Here we get a WMI object pointing to the
root\CIMv2 namespace, after which we call the
InstancesOf method and pass the
Win32_LogicalDisk class. That method returns a
collection of Win32_LogicalDisk objects, which we
then iterate over with a for loop.Since we used a for loop in the last example,
we'll show the equivalent code in Perl:
use Win32::OLE 'in';
my $strComputer = ".";
my $objWMI = Win32::OLE->GetObject("winmgmts:\\\\$strComputer\\root\\cimv2");
my $objDisks = $objWMI->InstancesOf("Win32_LogicalDisk");
for my $objDisk (in $objDisks) {
print "DeviceID: ", $objDisk->DeviceID,"\n";
print "FileSystem: ", $objDisk->FileSystem ,"\n";
print "FreeSpace: ", $objDisk->FreeSpace,"\n";
print "Name: ", $objDisk->Name,"\n";
print "Size: ", $objDisk->Size,"\n";
print "\n";
}
As you can see, the Perl code is very similar to the VBScript code.
One thing to note is that we had to import the in
function on the first line, which was later used in the
for loop to iterate over the
$objDisks collection. VBScript provides this
function natively within the language whereas Perl does not.Having the capability to easily obtain the instances of a certain
type of class is very powerful. As you can imagine, you could adapt
the code to retrieve a list of all CPUs, services, processes, etc.,
on a computer. The only issue with the last example is that we needed
to know which property methods of the
Win32_LogicalDisk class we wanted to print. We can
instead retrieve all properties of the
Win32_LogicalDisk class using the
Properties_ method on each object as shown here:
strComputer = "."
strWMIClass = "Win32_LogicalDisk"
set objWMI = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
set objDisks = objWMI.InstancesOf(strWMIClass)
for each objDisk in objDisks
for each objProp in objDisk.Properties_
' Print out NULL if the property is blank
if IsNull(objProp.Value) then
Wscript.Echo " " & objProp.Name & " : NULL"
else
' If the value is an array, we need to iterate through each element
' of the array
if objProp.IsArray = TRUE then
For I = LBound(objProp.Value) to UBound(objProp.Value)
wscript.echo " " & objProp.Name & " : " & objProp.Value(I)
next
else
' If the property was not NULL or an array, we print it
wscript.echo " " & objProp.Name & " : " & objProp.Value
end if
end if
next
WScript.Echo "
next
14.2.3 Searching with WQL
So far we've shown how to instantiate specific
objects, such as a logical drive, and also how to enumerate all the
objects of a particular class using the
InstancesOf method. Knowing how to do both of
these functions will take us a long way with WMI, but we are missing
one other important capability: the ability to find objects that meet
certain criteria.The creators of WMI found an elegant way to handle this problem. They
implemented a subset of the Structured
Query Language (SQL) known
as the WMI Query Language (WQL). WQL
greatly
increases the power of WMI by giving the programmer complete control
over locating objects.With WQL, we can even perform the same function as the
InstancesOf method we used earlier. The following
query retrieves all the Win32_LogicalDisk objects
on the system:
select * from Win32_LogicalDisk
We can use any property available on
Win32_LogicalDisk objects as criteria in our
search. As an example, let's say we wanted to find
all NTFS logical disks that have less than 100 MB of available space.
The query would look like the following:
select * from Win32_LogicalDisk
where FreeSpace < 104857600
and filesystem = 'NTFS'
Pretty easy, huh? Now let's put WQL to use. First,
we need to get a WMI object to the namespace we want to query. After
we've done that, we can call the
ExecQuery method on that object and pass the WQL
query to use. The next example uses the "less than
100 MB" query we just described to print out all
logical disks on the local computer that match that criterion:
strComputer = "."
set objWMI = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
set objDisks = objWMI.ExecQuery _
("select * from Win32_LogicalDisk " & _
"where FreeSpace < 104857600 " & _
"and filesystem = 'NTFS' ")
for each objDisk in objDisks
Wscript.Echo "DeviceID: " & objDisk.DeviceID
Wscript.Echo "Description: " & objDisk.Description
Wscript.Echo "FileSystem: " & objDisk.FileSystem
Wscript.Echo "FreeSpace: " & objDisk.FreeSpace
next
14.2.4 Authentication with WMI
So far, the examples we've
shown
assume that the caller of the script has the necessary rights to
access the WMI information on the target machine. In most cases in
which you are trying to automate a task that may not be the case.
Luckily, using alternate credentials in WMI is very straightforward.Previously, to connect to a WMI namespace, we would have used the
following:
strComputer = "terminator.movie.edu"
set objWMI = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
But let's say that the person calling the script
does not have any privileges on
terminator. We must now use the following:
strComputer = "terminator.movie.edu"
strUserName = "administrator"
strPassword = "password"
set objLocator = CreateObject("WbemScripting.SWbemLocator")
set objWMI = objLocator.ConnectServer(strComputer, "root\cimv2", _
strUserName, strPassword)
We've replaced the single call to
GetObject with a call to
CreateObject to instantiate a
WbemScripting.SWbemLocator object. The
SWbemLocator object has a method called
ConnectServer, which allows us to specify the
target machine, username, and password.[1] We can then use the object returned from
ConnectServer to get the instances of a class,
perform a WQL search, or any other function.[1] Obviously it
is less than ideal to include passwords in plain text scripts. An
alternative would be to require the user to use the
runas command to authenticate as the
privileged user. If you plan on running the script via Scheduled
Tasks, you can provide credentials when you configure the
task.
This was a quick introduction into WMI scripting.
We'll cover additional tasks, such as invoking an
action or modifying properties of an object, as we walk through
specific DNS examples later in the chapter. Now, back to the good
stuff . . .