Downloads
19828.zip

Last month, I discussed EventMon.pl, a script that monitors events in logs on remote Win32 machines and displays those events in realtime. The magic behind this script is Windows Management Instrumentation (WMI). In my explanation of EventMon.pl, I mentioned that you ought to become familiar with WMI. This month, I insist on it.

Many of you are probably rolling your eyes right now and moaning about yet another Microsoft technology to learn. WMI is well worth learning because it offers a rich set of features that will meet most of your administrative needs. With WMI, your Perl scripts can query a machine's settings, processes, hardware, and more. These scripts can even monitor events that a remote machine fires.

To help you learn WMI, I explain how to access WMI from Perl. In addition, I provide a practical example of how you can use WMI with Perl. I don't focus on the nitty-gritty but rather give a cursory overview of the information you need to start using this technology.

Understanding WMI
WMI is Microsoft's version of an industrywide initiative called Web-Based Enterprise Management (WBEM). This initiative uses the Common Information Model (CIM), which describes how information is obtained.

Classes are key to WMI. Think of a class as a blueprint. Architects create blueprints to inform others about a building's design and characteristics. For example, a house's blueprint tells you the size of the rooms, the number of fireplaces, and other pertinent information. Builders use that blueprint to create a physical manifestation of the house.

Similarly, code writers create classes to inform others about objects' properties and functions (also called methods). In the case of WMI, Microsoft code writers created WMI classes to describe objects that represent computer components. For example, the Win32_DiskDrive class describes the object that represents a machine's physical disk drive. This class tells you the size of the disk drive, the number of heads, and other pertinent data. Perl scripts use that class to create a physical manifestation, or instance, of the object in the computer's memory. You can then examine and explore the object, just as you might examine and explore a new house. WMI classes exist for all types of information. Classes address a computer's physical characteristics (e.g., memory), OS (e.g., available virtual memory), applications (e.g., installed programs), subsystems (COM components), and more.

Using WMI Classes
To request that a machine create an instance of, or instantiate, a class into an object, you need to know which class to request. You can access WMI Win32 class information from the Microsoft Developer Network (MSDN) Online Library. After you know which class you want to use, you can instantiate that class by following these steps:

  1. Load the Win32::OLE extension.

  2. Request an instance of the object that represents the WMI service on the target machine.

  3. Request all instances of the target class.

  4. Enumerate each instance in the target class.

For example, let's use WMI to request information about a machine's disk drives, which means you need to instantiate the Win32_DiskDrive class.

Step 1
The first step is to load the Win32::OLE extension. This extension's name is misleading because Win32::OLE doesn't have much to do with OLE; instead, the extension has more to do with COM.

The Win32::OLE extension comes with ActivePerl, so you probably already have the extension installed. (If you don't have ActivePerl, you can download it from ActiveState Tool at http://www.activestate.com.) If you're unfamiliar with the Win32::OLE extension, I recommend you learn about it because it's one of the most useful Win32 extensions.

To load the extension, you use the code that Listing 1 shows for Step 1. In addition to loading the extension, this code imports the in() function so that you can use it later in your script (more on this function later).

Step 2
The next step is to request an instance of the object that represents the WMI service on the target machine. You need to use the Win32::OLE extension's GetObject() function to make this request because you're requesting an instance of a COM object. (WMI objects are COM objects.) The GetObject() function requires a string that identifies the COM object you want. In this case, the string is winmgmts:\{impersonationLevel=impersonate,(security)\}//MyMachine. This string tells the function that you want the winmgmts subsystem (aka the WMI service) to connect to the computer called \\MyMachine. If the connection is successful, the function returns a COM object that represents the WMI service on \\MyMachine.

If you're requesting a WMI object on the local machine, you can use a period (i.e., \\.) instead of the computer name (i.e., \\MyMachine). In addition, you can use either forward slashes (//) or backslashes (\\); they're interchangeable.

If you run your script on Windows 2000, you don't have to specify the security component \{impersonationLevel=impersonate,(security)\}. You can simply use the string winmgmts://MyMachine. However, it doesn't hurt to keep the security component if you expect to also run your script on machines running earlier versions of Windows.

So, to request an instance of the WMI object on \\MyMachine, you use the code that Listing 1 shows for step 2. This code returns an instance of the COM object that represents the WMI service on \\MyMachine and assigns that instance to the $WMIServices variable. You'll need to make sure that the user running the script has the required permissions on the specified machine. Otherwise, the script will fail.

Step 3
Now that you've obtained the COM object that represents the WMI service, you need to request all instances of the target class. Some classes have only one instance. Take, for example, the Win32_OperatingSystem class, which represents a machine's Win32 OS. This class has only one instance because only one OS can run on a machine at a time. However, many classes can have several instances. For example, the Win32_DiskDrive class can have several instances—one instance for each disk drive attached to the machine.

To obtain each instance of the specified class, you call the WMI object's InstancesOf() method and pass in the name of the class you're interested in. InstancesOf() returns an instance of the COM collection object that represents that class. This collection object contains all the instances of that class.

So, to request all instances of Win32_DiskDrive, you use the code that Listing 1 shows for Step 3. In this code, InstancesOf() returns all instances of the Win32_DiskDrive class and assigns that collection of instances to the $DriveCollection variable.

Step 4
The last step is to use the Win32::OLE->in() function to enumerate the class instances. This function returns an array that contains each class instance found in the COM collection object. You might recall that you imported the in() function in Step 1. Because you imported the function at that time, you can simply refer to the function as in() in this step.

You pass the in() function the collection of instances ($DriveCollection), as Listing 1 shows. With each iteration of the foreach loop, the $Drive variable contains a COM-based instance of the Win32_DiskDrive class. In this case, you're just enumerating the drives, but you can query properties and call methods on $Drive as you would any other COM object.

These four steps provide a script with disk-drive information from a remote machine. Let's now look at a real-world example of how you can use the Win32_DiskDrive class in a script.

A Practical Example
Listing 2 contains a real-world script called WDiskDrives.pl. The Win32_DiskDrive class doesn't contain any writable properties or methods. Thus, WDiskDrives.pl explores the target machine's disk drives by querying 7 of the class's 48 properties:

  • Capabilities (returns a list of the drive's capabilities)

  • InterfaceType (returns the type of interface the drive uses, such as SCSI or IDE)

  • MediaType (returns the type of media that the drive uses)

  • Model (returns the drive's model number)

  • Name (returns the OS's physical path to the drive)

  • Partitions (returns the number of partitions on the drive)

  • Size (returns the drive's size in bytes)

Let's begin exploring WDiskDrives.pl by finding the four steps just discussed. In Listing 2, callouts A, B, C, and D highlight the code for Steps 1, 2, 3, and 4, respectively.

The code between callouts A and B in Listing 2 prepare the script for querying. For example, the portion of the code that starts with

my @CAPABILITIES =

defines the friendly names of the possible values that the Capabilities property returns, and the line

my $TotalSize = 0;

initializes the $TotalSize variable. The code

my $Machine = shift @ARGV || ".";

tells the script to accept a machine name that the user passes in through the command line; if a user doesn't pass in a machine name, the script defaults to the local machine (which the period specifies). Finally, the line

$Machine =~ s/^\[\\\/\]+//;

removes any forward slashes or backslashes that might be in the machine name. This removal prevents an error if the user specifies a machine such as \\MyMachine.

The code at callout D in Listing 2 queries each instance of the Win32_DiskDrive class for the six properties. The Capabilities property is the most interesting. It returns a COM object that contains an array of strings. However, if the array is empty, the property doesn't return a COM object. Instead, the code

my $CapabilityList = $Drive->\{Capabilities\} || \[\];

assigns an empty anonymous array to the $CapabilityList variable to prevent an error. Note that the next line

foreach my $Cap ( @\{$CapabilityList\} )

casts $CapabilityList as an array. If $CapabilityList is an empty anonymous array, casting it as an array simply dereferences it. If $CapabilityList is a COM array object, the object's list of properties is returned as a Perl array reference. Either way, the result is an array.

To use WDiskDrives.pl, you need to have WMI installed. Win2K and Windows Millennium Edition (Windows Me) come with WMI. For earlier platforms (Windows NT 4.0 and Windows 9x), you can download the WMI service from http://msdn.microsoft.com/downloads/c-frame.htm?/downloads/sdks/wmi/default.asp and install it.

To run WDiskDrives.pl on the local machine, use the command

perl WDiskDrives.pl

If you want to run the script on a remote machine called \\MyMachine, use the command

perl WDiskDrives.pl MyMachine

Another approach is to specify the IP address of \\MyMachine instead of MyMachine in that command.

Give WMI a Test Drive
If you were unfamiliar with WMI before you read this column, you might be thinking, "WMI can do all that?" Indeed, it can—and I've only shown you the tip of the proverbial iceberg. Next month, I'll show you more uses for WMI. Until then, give WMI a test drive. You might be surprised at what information it can provide.