DekGenius.com
[ Team LiB ] Previous Section Next Section

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 Perl

Here 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 . . .

    [ Team LiB ] Previous Section Next Section