Managing Windows with WMI

Michael Maston
Microsoft Corporation

November 1999

Summary: Introduces Microsoft Windows Management Instrumentation, part of Windows 2000 (but available for Windows 95, Windows 98, and Windows NT 4.0), which is designed to help you manage your enterprise systems, applications, and networks as they become larger and more complex. (15 printed pages)

Contents

Introduction
List All Services on the System
List Automatic Services That Are Stopped
Listing All Drive Partitions with Less Than 20 Percent Free Space
Setting the Operating System Boot Delay
Backing Up the Application Event Log
Reboot a Remote Machine
Launch Notepad Through WMI
Collecting Windows NT Log Events in WMI
Collecting High CPU Utilization Events
Conclusion

Introduction

The goal of this article is to introduce Microsoft® Windows® Management Instrumentation (WMI) and get you up and running as quickly as possible, using it to manage your system through the WMI scripting interface. Before diving into what WMI can do, including several useful examples, we first need a little background in what WMI is, how it came about, and why it is valuable.

One of the greatest challenges facing information technology managers is managing enterprise systems, applications, and networks as they become larger and more complex. In order to help solve these problems and reduce the total cost of ownership (TCO) of Windows-based servers and desktops, Microsoft has developed Windows Management Instrumentation (WMI), a scalable management infrastructure, and included it as part of Windows 2000. WMI is based on the Web-based Enterprise Management (WBEM) initiative and the Common Information Model (CIM) adopted by the Distributed Management Task Force (DMTF) (see the "DMTF, CIM, WBEM, and WMI" sidebar at http://www.msdn.microsoft.com/voices/news/wmisidebar2.asp). WMI includes the managed objects defined by CIM as well as extensions to the CIM model for additional information available from the Windows platform.

What this all means is that WMI makes Windows 2000 extremely manageable using a single consistent, standards-based, extensible and object-oriented interface. Also, any application or script accessing WMI data can do so on the local machine or remotely in a seamless way. And, it's not only for Windows 2000—WMI is available for Windows 95, Windows 98, and Windows NT® 4.0. There are several key features in WMI that will be valuable in solving the complex management tasks IT administrators are challenged with today:

Let's first take a look at WMI from an architectural perspective. Figure 1 describes the three-layer model WMI uses, which consists of providers, the CIM Object Manager (CIMOM), and consumers of WMI information.

Figure 1. WMI architecture

Working from the lowest level upward, the first tier is the provider. Simply put, a provider is an intermediate agent between the system to be managed (for example, operating system, service, application, device driver, and so on) and the CIM object manager. The job of a provider is to extract management information from the underlying data source using whatever interfaces that software presents for management. The management information and interfaces are then mapped by the provider into the object classes that WMI presents to WMI consumers. Moving forward, new and updated managed systems will use providers as a direct way to expose management APIs without significant intermediate conversions.

Next is CIMOM, the CIM Object Manager, which has its own storage repository and acts as a broker for object requests. CIMOM and its repository are represented on the system by the system service called WinMgmt. Providers plug into CIMOM via a published set of COM interfaces. CIMOM keeps track of what classes are available (their definitions are stored in the repository) and what provider is responsible for supplying instances of those classes. When a request for management information comes from a WMI consumer to CIMOM, it evaluates the request, identifies which provider has the information, and upon getting it returns the data to the consumer. The consumer only needs to ask for the information it wants but at no time needs to know the exact source of it or any details of how it is extracted from the underlying API. It should be noted that static data can be stored in the repository and retrieved without a provider, but the real power of the WMI system is that it supplies dynamic information about the managed system, and this is done entirely through providers.

Finally, there are consumers of WMI data. These can be management tools such as MMC snap-ins, management applications like Microsoft Systems Management Server (SMS) or third-party applications or scripts. These consumers, as previously noted, only need to know about the classes of the objects about which they wish to get information. The details of where the information comes from and how it is actually obtained are hidden and not relevant. In this way, an application or script can write to one common API, WMI, and get a wealth of information about the computer, operating system, applications, devices, and even information available via other management protocols like SNMP and DMI.

Before getting into some examples of using WMI, we should spend a moment on what sorts of things are possible to do through the management classes WMI provides. There are several types of information that can be defined in WMI classes:

The latest version of WMI, shipped with Windows 2000 and available for all other Windows operating systems, includes several providers that expose a rich set of instrumentation for many parts of the system. These providers include:

See the WMI system schema chart at http://www.msdn.microsoft.com/voices/news/wmichart.asp, which shows some of the more important WMI system and device classes that are available in Windows 2000 and the properties, methods, and associations they define. The entire schema for the providers alone just listed is just too large and rich in information to fully publish here, but this chart will be a useful reference tool as you explore what WMI has to offer and can serve as a starting point to learn more about what is available. WMI providers are not just limited to core operating system functionality, however. Numerous providers have been developed both by Microsoft and third parties for other key applications and services that run on Windows platforms. See the sidebar called "How others are making use of WMI" at http://www.msdn.microsoft.com/voices/news/wmisidebar1.asp.

Now that we've defined what WMI is and why you will find it useful in managing your systems, we'll take a look at how you can do that with its scripting support. As is usual with scripting on Windows platforms, you have a range of script language choices available to you through the Windows Scripting Host (WSH). These include Microsoft Visual Basic® Scripting Edition (VBScript), Microsoft JScript®, and any script language that supports Microsoft ActiveScripting technology, such as Perl. WMI classes can be just as easily accessed via C++, Visual Basic, Active Server Pages (ASP), and scripts within standard HTML pages.

There are so many things that can be done with WMI instrumentation; there is no way to give a representative example of every major subsystem in this article. Fortunately, once you understand how to access a class, read or write a property, execute a method, or receive an event in one class, it is exactly the same for every other WMI class. To illustrate some of the capabilities of WMI, I have selected several examples, written in VBScript. These examples include:

List All Services on the System

In this example we use the Win32_Service class to output a list of all the services that are installed on the system, regardless of whether they are running:

Set ServiceSet = GetObject("winmgmts:{impersonationLevel=impersonate}").InstancesOf("Win32_Service")
for each Service in ServiceSet
   WScript.Echo Service.Description
Next

Let's briefly walk through this example and understand what is happening at each stage. First, we request an object in the GetObject() call from the WMI service (WinMgmt). We do this by submitting a request for all instances of the Win32_Service class through the InstancesOf() method. The result of this call is a list of instances of the Win32_Service class.

After getting the instances back in the ServiceSet variable, all that is left to do is iterate through them and display the property of interest, the Description field, which contains the friendly name of the service. It is important to note here that the Description property is being handled as an automation property of the Service object, allowing for much more intuitive scripting. Alternately, the exact same results can be obtained by using an SQL query and submitting it to the ExecQuery() method:

Set ServiceSet = GetObject("winmgmts:{impersonationLevel=impersonate}").ExecQuery("select * from Win32_Service")

The SQL query asks for all instances (and all properties of those instances) to be selected and returned. The resulting set of objects that are returned are exactly equal using this form, but as will be clear in the examples to follow, the query form can be used to refine the number of properties, instances, or both returned to only those of interest. Most importantly, either mechanism can be used for any class supplied through WMI that supports being enumerated.

One final note about this example and those that follow involves the security model being used. You may have seen the statement "{impersonationLevel=impersonate}" as part of the GetObject() call just shown. In essence, this tells the system to use your current login credentials as those to be presented when asking for data or executing methods. It is the same as though you were calling the native APIs directly. This applies for any requests made either on your local machine or a remote machine. In any case, the user the system sees is the one defined by your current login. It is possible, of course, to specify different credentials for requests if desired, but this is outside the scope of this article. Also, on Windows 2000 the "{impersonationLevel=impersonate}" statement can be omitted from the script, as "impersonate" is the default impersonation level configured for COM. It is retained in the samples so that they will work equally well across previous platforms, such as Windows NT 4.0.

List Automatic Services That Are Stopped

If we now want to see just those services that are set to run automatically but for some reason are not running (for example, a user manually stopped one), we really only need to make one change to the query version of the script, as shown in bold here:

Set ServiceSet = GetObject("winmgmts:{impersonationLevel=impersonate}").ExecQuery("select * from Win32_Service where State='Stopped' and StartMode='Auto'")
for each Service in ServiceSet
   WScript.Echo Service.Description
next
if ServiceSet.Count = 0 Then WScript.echo "No services found meeting query criteria."

Just as if I were requesting more specific information from an SQL database, all that was needed to get a list of services that are stopped, even though they are configured to run automatically at boot time, was to add a WHERE clause to the query. The specified query narrows the results I get to only those instances that have a State property with the value "Stopped" and a StartMode property set to "Auto."

Queries can be very simple or quite complex, depending on how specific a set of information you require. Note that I added one more line to the script at the end to catch the case where no instances that match the criteria were returned, which would almost always be the case for this scenario. In order to see an instance get returned by this script, I recommend temporarily stopping the Print Spooler service on your system and then running the script. Before turning off the Print Spooler, you should get the "no matching instances" message, and after you should get a single instance for the Print Spooler service being turned off. Don't forget to restart the Print Spooler service when you are done with the test and check that it disappears from the instances returned by the script.

Listing All Drive Partitions with Less Than 20 Percent Free Space

In order to determine if a drive partition is low on space, two properties from the Win32_LogicalDisk class need to be retrieved: FreeSpace and Size. With these two pieces of information, the percentage of free space can be determined and those disk partitions below the limit identified:

Set DiskSet = GetObject("winmgmts:{impersonationLevel=impersonate}").ExecQuery("select FreeSpace,Size,Name from Win32_LogicalDisk where DriveType=3")
for each Disk in DiskSet
   If (Disk.FreeSpace/Disk.Size) < 0.20 Then 
      WScript.Echo "Drive " + Disk.Name + " is low on space."
   End If
Next

You might notice that in this query the exact properties needed to build the script are requested, not everything in the Win32_LogicalDisk class. In order to just get the information for local hard drives, the WHERE clause limits the query to only that drive type (DriveType=3). Without this filter floppy drives, CD-ROM drives, and network shares would also get included in the check, which would probably not be desired. The Name property is also retrieved and used when a low space message needs to be displayed.

Setting the Operating System Boot Delay

In this script we use the Win32_ComputerSystem class to modify the delay setting used at boot time to give the user time to choose a different boot option than the default operating system. The major difference in this script is that a property in the Win32_ComputerSystem class is being modified and then written back to WMI, which in turn updates the operating system setting:

Set CompSysSet = GetObject("winmgmts:{impersonationLevel=impersonate}").ExecQuery("select * from Win32_ComputerSystem")
for each CompSys in CompSysSet
   CompSys.SystemStartupDelay = 20
   CompSys.Put_()
next
WScript.Echo "Boot up delay time set"

To accomplish this task, the Win32_ComputerSystem class is enumerated with a query as before. The script iterates through each instance (in the case of this class, there is always just one instance because there is only one computer system instance for a given machine) and sets the SystemStartupDelay property value in the temporary variable to the desired setting. Finally, to write the instance back with the changed value, the Put_() method is invoked. It should be noted that several properties in the instance could have been updated simultaneously with the same Put_() call. The same mechanism can be used for any write-enabled property supported by WMI.

A value of 20 seconds is written to the property, which can be verified after running the script in the Control Panel->System applet under the Advanced tab Startup and Recovery options, as shown in Figure 2.

Figure 2. Startup and Recovery options

Backing Up the Application Event Log

The Win32_NTEventLogFile class models the event logs in Windows NT 4.0 and Windows 2000. By default, there are three standard event logs: System, Security, and Application. In this example the method to back up one of these logs is invoked. Methods in WMI are simply function calls that are applied to particular managed objects; here, the backup method is applied to an instance of a Windows NT Event Log object:

LogFileSet = GetObject("winmgmts:{impersonationLevel=impersonate,(Backup)}").ExecQuery("select * from Win32_NTEventLogFile where LogfileName='Application'")
for each Logfile in LogFileSet
   RetVal = LogFile.BackupEventlog("c:\BACKUP.LOG")
   if RetVal = 0 then WScript.Echo "Log Backed Up"
next

The basics remain the same here, in that a query is run against the Win32_NTEventLogFile class, filtering for the specific log file of interest, "Application." Note the addition of the statement "(Backup)" in the GetObject() call. In order to back up an event log file, the "Backup" privilege must be assigned to your account. Members of the Administrators and Backup Operators NT security groups have this privilege by default. Even so, having a privilege assigned to you is not enough to make use of it. When executing a privileged operation such as backup, the privilege must be explicitly enabled by the user before it is used. If "(Backup)" had not been specified in the GetObject() call, the script would receive an "Access Denied" message when the BackupEventlog() method was invoked and the operation would not be performed. The BackupEventlog() method takes the path and name of the backup file as its only parameter.

To clear the entries in the Application event log after it is backed up, the script requires only a simple modification, shown in bold:

LogFileSet = GetObject("winmgmts:{impersonationLevel=impersonate,(Backup)}").ExecQuery("select * from Win32_NTEventLogFile where LogfileName='Application'")
for each Logfile in LogFileSet
   RetVal = LogFile.BackupEventlog("c:\BACKUP.LOG")
   if RetVal = 0 then WScript.Echo "Log Backed Up"
   RetVal = LogFile.ClearEventlog()
   if RetVal = 0 then WScript.Echo "Log Cleared"   
next

The bolded area in the updated example adds the functionality required to clear the log file by calling the ClearEventlog() method and, upon a successful result, displays a message.

Reboot a Remote Machine

Remote operations on WMI are virtually identical to those performed locally. All of the previous examples could be easily modified to specify a remote machine as the target instead of defaulting to the local system. Other than calling out a particular target machine, nothing else about dealing with WMI is any different. This is no small point because it means any instrumented property or method can be accessed across individually or across an entire enterprise using a very simple script. Here, we reboot a remote system using the Reboot() method in the Win32_OperatingSystem class:

Set OpSysSet = GetObject("winmgmts:{impersonationLevel=impersonate,(RemoteShutdown)}//alexn-pc ").ExecQuery("select * from Win32_OperatingSystem where Primary=true")
for each OpSys in OpSysSet
   OpSys.Reboot()
Next

To build this script, we have to query the remote machine for the instance of the operating system it is running, in this case the one with a Primary flag set to TRUE. Naturally, the name of the machine, alexn-pc, needs to be specified so the method is directed to the correct system. Other than specifying the target machine, nothing is different about how information is retrieved in this case than from a local machine.

Again, notice that this is a privileged operation. (I must be part of the Administrator's group on the remote machine to successfully use it.) For the script to work, I must have the privilege to perform a remote shutdown assigned to me on the remote system and explicitly enable it using "(RemoteShutdown)" before attempting the privileged operation. Remember, as discussed earlier, all permissions and privileges are based on who the logged-in user is that runs the scripts; your abilities as currently defined by your user account are impersonated by WMI such that only the information and actions you are allowed to perform are available.

Launch Notepad Through WMI

In this example we launch a new process through a WMI method in the Win32_Process class. I have chosen a relatively innocuous application, Notepad, for the purpose of demonstration, but any executable can be substituted here. This capability can be extremely powerful for support personnel dealing with relatively inexperienced users. For example, rather than guide an end user through the user interface, menus, and so on to run a diagnostic tool, the support person can simply pop the tool up on the user's system using a script that invokes it remotely via WMI. This alone can be a big timesaver:

set process = GetObject("winmgmts:{impersonationLevel=impersonate}!Win32_Process")
result = process.Create ("notepad.exe",null,null,processid)
WScript.Echo "Method returned result = " & result
WScript.Echo "Id of new process is " & processed

There are a couple of new concepts in this example. The first new development is the use of the COM moniker notation in the GetObject() call when asking for the Win32_Process class. A moniker is a COM standard mechanism for encapsulating the location and binding of another COM object. This lets you get back a particular object based on the display name. Next, rather than submitting a query through ExecQuery() to get back instances of the Win32_Process class, I have asked to get back the class object itself. The reason for this is fairly simple, but may not be obvious. Creating a new process is not really an action to be taken against an existing process instance. Which currently running process should be the one to launch the new one? Because there is no consistent answer to this issue, it becomes apparent that creating a process is really creating a new instance of the Win32_Process class. Therefore, the concept of a static method, one defined against the class definition itself instead of its instances, is needed. The Create() method for Win32_Process is an example of such a static method; previous method examples have all been dynamic methods—those that operate against individual instances. Incidentally, the Delete() method is a dynamic method because it typically applies to particular instances rather than the class as a whole.

The Create() method takes several input parameters, but in the example I simply supply the name of the executable I wish to launch, "notepad.exe." Aside from returning a value for success or failure of the operation, the method also returns the ID of the new process that was created. The script displays the method execution results and process ID values after the method is executed and, of course, the Notepad application should appear on your desktop.

Collecting Windows NT Log Events in WMI

Now that we have collected some data and executed some methods, we move on to the subject of events. WMI can supply events for many types of changes or occurrences in the system. In this example the script is built to register for any new events written to the Windows NT or Windows 2000 event log. When a new event is written, WMI generates a WMI event that an event consumer such as this script can receive. It is important to note, however, that WMI events are only sent when at least one consumer has registered to receive them. If no one is listening, WMI does not spend resources on watching for a given occurrence in the system:

set events = GetObject("winmgmts:{impersonationLevel=impersonate}").ExecNotificationQuery _ 
   ("select * from __instancecreationevent where targetinstance isa 'Win32_NTLogEvent'")                
if err <> 0 then
   WScript.Echo Err.Description, Err.Number, Err.Source
end if 
' Note this next call will wait indefinitely - a timeout can be specified 
WScript.Echo "Waiting for NT Events..."
do 
   set NTEvent = events.nextevent 
   if err <> 0 then
      WScript.Echo Err.Number, Err.Description, Err.Source
      Exit Do
   else      
      WScript.Echo NTEvent.TargetInstance.Message
   end if
loop
WScript.Echo "finished".

Using the ExecNotificationQuery() method, the script submits a query defining the types of events to be sent when they occur. When an event is written to the log, it in effect creates an instance of the Win32_NTLogEvent class. WMI can detect when an instance is created, modified, or deleted, and this information is the source of the event we are looking for. The SQL query statement in effect tells WMI to "send any instance creation events where the instance being created is of the Win32_NTLogEvent class." Instance creation events for other classes will not be reported because they do not match the criteria of the query.

The script simply waits in a loop for the events to arrive and then displays the message property of the received instances.

Collecting High CPU Utilization Events

In the previous example the events were being supplied by the Event Log provider directly and were driven by the provider being called back by the Event Log service as new events were logged. Not all services supply such mechanisms for reporting interesting events, nor is it always reasonable to expect them to. Rather than not have events for some instrumentation, WMI has a built-in mechanism to generate events where no underlying event support exists. In fact, by virtue of the fact that a property is instrumented, it can be the source of events.

In this example I demonstrate how CPU utilization can be monitored and, when a processor is heavily loaded, an event will be generated and sent to registered consumers. This is available even though there is no internal event mechanism for this particular measurement built in to the operating system:

set events = GetObject("winmgmts:{impersonationLevel=impersonate}").ExecNotificationQuery _ 
   ("select * from __instancemodificationevent within 5 where targetinstance isa 'Win32_Processor' and targetinstance.LoadPercentage > 50")                
if err <> 0 then
   WScript.Echo Err.Description, Err.Number, Err.Source
end if 
' Note this next call will wait indefinitely - a timeout can be specified 
WScript.Echo "Waiting for CPU load events..."
WScript.Echo ""
do 
   set NTEvent = events.nextevent 
   if err <> 0 then
      WScript.Echo Err.Number, Err.Description, Err.Source
      Exit Do
   else      
WScript.Echo NTEvent.TargetInstance.DeviceID WScript.Echo NTEvent.TargetInstance.LoadPercentage   
end if
loop

WScript.Echo "finished"

In this script we modify the previous Windows NT event log script to watch now for a CPU being heavily loaded. Notice that now we are watching for events regarding instance modification rather than instance creation as in the previous example. Also, the class being monitored is the Win32_Processor class and there is additional constraint that the LoadPercentage property (a member of the Win32_Processor class) must have a value greater than 50. Because WMI is monitoring the value of the LoadPercentage property and generating events when its value changes and meets the query criteria, it needs a parameter to tell it how often to monitor the value. This is the purpose of the WITHIN clause in the query. The WITHIN clause defines the maximum time interval that should pass before WMI checks the value of the property to see if it has changed and meets the query requirements. This value can also be viewed as the maximum amount of time the consumer can wait before getting the desired event.

Just as before, when the query criteria is fulfilled, WMI sends an event to the script, which in turn prints some information about the event; in this case CPU name and the actual measured load percentage is shown. Incidentally, this script works equally well on single and multiple CPU systems. Because the script looks for any instance modification that affects an instance of the Win32_Processor class, it will report any CPU that exceeds the specified load criteria. Naturally, if only a particular CPU load was of interest, the query could easily be changed to limit its scope.

This same functionality can be leveraged for any property that is visible through WMI, whether it is part of the instrumentation provided in the operating system or added by a third party. Absolutely no additional work needs to be done to make this happen; it is a standard feature that comes for free by instrumenting your product with WMI. And, if the underlying API does support an internal event system that can deliver the same information in perhaps a more optimized way, the writer of the provider can easily and seamlessly override the WMI event monitoring without the event consumer having to be aware of the difference. Thus, a consumer will automatically get the benefits of the best event mechanism available without any changes to the way it works. And, it should be emphasized again, WMI will not monitor any property in the way just described unless there is an event consumer registered for those particular events. Once the registration is removed (for example, the script ends), WMI will no longer watch for the requested events.

Conclusion

WMI is a key part of Microsoft's strategy to make the Windows platform the most manageable operating systems available and lower the TCO for its customers. Based on industry standards and endorsed by the DMTF, WMI provides a rich and extensible object model that allows computer systems to be managed in a consistent, scriptable way, either locally or remotely. Most core operating system information and services are already instrumented as part of the basic WMI system that comes with Windows 2000. Instrumentation for other Microsoft and third-party products is available and under development, making WMI the best way to manage Windows-based systems in a scalable, reliable, and unified way.

In this article, you have been introduced to just some of the useful features available in WMI. WMI provides an object-oriented set of classes, properties, methods, events, and associations that can be easily and powerfully accessed through scripting languages, Microsoft Visual Basic and Microsoft Visual C++® alike. I have tried to demonstrate some of WMI's powerful functionality through several sample scripts, but these just scratch the surface. I hope with these examples, along with the accompanying instrumentation chart, you will take an opportunity to explore the information WMI makes available to enable powerful management of your systems. Future articles will cover more in-depth concepts such as writing WMI providers, advanced event handling, and client applications.

A full set of documentation on building WMI components and using WMI is available in the WMI SDK, which ships as part of the MSDN Windows 2000 Platform SDK, or can be downloaded for free from http://msdn.microsoft.com/developer/sdk/wmisdk/. The core WMI components needed for Windows NT 4.0 systems and Windows 95/98 can also be downloaded from this site.