VBScript Fundamentals for Windows Scripting – WMI

Before we begin, be forewarned that WMI encompasses A LOT of material and this will be a long article as is required to cover the material at even a high level. I will also preface the article with the fact that many of the diagrams and references included herein were derived from “The Windows Scripting Guide” by Microsoft Press which, in my opinion, is about the best book on Windows Scripting available at this time. Microsoft has done a great job in diagramming the many layers of the architecture that were used for modeling some of the diagrams seen later in this article. In addition, this article requires an intermediate to advanced knowledge of Widows Scripting and COM (Component Object Model) fundamentals. If you are not comfortable with the foundations of VBScript, please review these first two articles in this series:

VBScript Fundamentals for Windows Scripting – The Basics

VBScript Fundamentals for Windows Scripting – ADSI

In regards to managing resources on the network, you can’t find a more convenient tool than scripting with Windows Management Instrumentation, or WMI. WMI facilitates queries, monitoring, and changing settings on network resources including software, hardware, services, accounts, and many other objects. What used to require GUI interfaces to obtain information, such as how much physical memory was installed on a computer using System Properties, can now be done with one script launched from one machine against several remote machines.

The scripting syntax used with WMI is not quite as similar to that used in previous articles. It is still VBScript; however, the syntax used to connect to classes within WMI is more involved and looks more complicated. Don’t let that confuse you as you read. The majority of the code used in scripting WMI works as a template in that you can simply cut and paste the “meat and potatoes” of the code into almost every WMI script you write with minor editing to achieve the desired results. In fact Microsoft released a very cool tool called SCRIPTOMATIC that reveals the WMI classes on your machine as well as displays informational code pertaining to the WMI classes which you can modify directly or cut and paste into your own code.

Now, let’s dive into Windows Management Instrumentation beginning with a look at the WMI architecture itself.

Intro to WMI Architecture

The key to working with WMI is not actually writing the code, but finding out what classes are available to use within the script to access the required resources. For example, the Win32_Service class enables the collection of information about services, but how do find information on what other resource WMI can manage? Understanding the WMI architecture is the key to knowing what classes WMI can manage as well as what methods and properties can be used in each class. (if you need a refresher in properties and methods, please see my previous article here.)

WMI is made up of three main layers:

  1. Managed Resources
  2. WMI Infrastructure
  3. Consumers

These layers and their associated WMI components are displayed in the following diagram to demonstrate interoperability:

Figure

To obtain a better understanding, we will briefly look at the layers in reverse, beginning at the bottom as this is where the actual managed objects reside.

Managed Resources

This layer of the WMI model is quite easy to understand. It is simply the layer where items that you want to query or manage reside such as: disk drives, printers, registry, network interface cards, services, file system structures, hardware devices, etc. You can start to get an idea of how powerful WMI is by simply reading the incomplete list of resources WMI can manage in the previous sentence.

Let’s look at a simple WMI code to display the description of the loaded network adapters:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer)
Set colItems = objWMIService.InstancesOf("Win32_NetworkAdapterConfiguration")
For Each objItem in colItems
Wscript.Echo objItem.Description
Next

Depending on how many NIC’s you have installed and what types of settings are used in the network, you should receive an output similar to the following:

Figure

Note – To demonstrate the re-usability of the code, if we were to change Win32_NetworkAdapterConfiguration to read Win32_LogicalDisk and change the word Description to read DeviceID the output would list all the available drive letters on the machine.

WMI Providers

The Providers are one the main components of the core WMI architecture. Basically, the providers facilitate communication between the Consumer (the script itself) and the Managed Resources. The script example above uses the Win_32 Provider to obtain information about the network adapter as a managed resource. It is the Providers that allow the scripts to access the Win_32 API (Application Programming Interfaces) via scripting instead of needing a more robust programming language such as C++ or Visual Basic.

CIMOM

An acronym for Common Information Model Object Manager, the CIMOM facilitates interaction between Providers and Consumers. All requests sent from the WMI script to the Managed Resource pass through the CIMOM layer of the WMI architecture. It is the CIMOM layer that directs requests from the script to the appropriate Provider which then returns the requested information or performs the requested task. Let’s view this using a scripting example:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.InstancesOf("Win32_Process")
For Each objItem In colItems
Wscript.Echo objItem.Description
Next

In this script, we are returning a list of all running services on the local machine. CIMOM does not actually return the information, but rather forward the request to the Win32_Process Provider which actually does the legwork and then returns the information to the CIMOM layer of the architecture. CIMOM then forwards the returned information into the output you see as the results.

Note – You may notice that this script differs a little from the previous one in that the objWMIService variable has “\root\cimv2” appended to the end of the statement. This simply specifies the namespace that the query should be run against in the WMI Provider model. We will cover namespaces a little later. In this sample, you may remove the “\root\cimv2” and still obtain the same results providing you have not changed the default WMI namespace paths after installation.

CIM Repository

This is the object repository, or the schema, which defines all the data that is exposed by WMI. Think of the schema as a map that defines the objects stored in the repository. Using Active Directory as an example, First Name is an attribute of the User object. It is the schema that defines not only what type of information may be assigned to the First Name attribute, but also what the relationship is between the First Name attribute and the User object.

It is important to note here that while objects in the Active Directory are static for the most part, objects referenced in WMI are not. The state of the computer is dynamic and as such, a true repository, or snapshot, of the system is not possible. It is this design that can cause WMI scripts to perform slowly as they must retrieve new values each time they are run by sending requests to the CIM Repository. Querying or changing large amounts of information from several machines can take a while to complete using WMI due to the need to constantly obtain refreshed information.

The easiest way to learn about the classes, properties, and methods in the CIM repository and their relationships is to browse the repository itself. There are two tools that allow you to open the repository and visualize the interaction of these items. The WMI Tester is available as part of the default WMI install and can be located by searching for Wbemtest.exe on your machine. The second tool is called CIM Studio and may be obtained by downloading and installing the WMI Administration Tools here. Both are beneficial in examining the repository, but the CIM Studio utility offers a little more information and is a little easier to read.

Consumers

This is the highest layer in the Infrastructure model and the easiest to understand. In a nutshell, the Consumer is the script you write or the application you run that launches WMI code in the background.

CIM (Common Information Model)

This is the WMI schema that is used to store the class definitions that are used in modeling managed resources. It is the CIM definitions that allow us to make minor modifications to scripts and obtain completely different output as shown in the following two scripts:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer)
Set colItems = objWMIService.InstancesOf("Win32_OperatingSystem")
For Each objItem in colItems
Wscript.Echo "Name: " & objItem.Name
Wscript.Echo "BuildNumber: " & objItem.BuildNumber
Wscript.Echo "Manufacturer: " & objItem.Manufacturer
Next

The above script returns information regarding the Operating System currently installed on the local machine. Making some slight modifications to the script as shown below will return information about shares on the local machine.

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer)
Set colItems = objWMIService.InstancesOf("Win32_Share")
For Each objItem in colItems
Wscript.Echo "Description: " & objItem.Description
Wscript.Echo "Name: " & objItem.Name
Wscript.Echo "Path: " & objItem.Path
Next

The only changes made were to the class name (changing Win32_OperatingSystem to Win32_Share) and to the property values to return the appropriate information related to the different classes. The CIM allows us to obtain completely different results from a script without re-writing the entire script.

Note – If you are wondering how to figure out what properties are associated with what classes, it is here that the SCRIPTOMATIC tool comes into play. This tool enumerates every property for all available classes on the machines by simply selecting the appropriate class.

Namespaces

Namespaces are used to logically contain similar classes within the CIM. These are similar to folder path structures on a logical drive and follow the same rules in that while you cannot have two files with the same name in the same folder path, it is not possible to have two classes with the same name in a shared namespace. For a detailed view of the WMI Schema namespace, click here.

The default namespace is \root\cimv2 within WMI. This can be changed, but unless necessary for advanced scripting, it will usually be found within that context. You can find out what the current default namespace is on a machine by running the following script:

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colWMISettings = objWMIService.InstancesOf("Win32_WMISetting")

For Each objWMISetting in colWMISettings
Wscript.Echo "Default namespace for scripting: " & _
objWMISetting.ASPScriptDefaultNamespace
Next

Similarly, you can retrieve a list of all available namespaces by running the following script:

strComputer = "."
Call EnumNameSpaces("root")

Sub EnumNameSpaces(strNameSpace)
Wscript.Echo strNameSpace
Set objSWbemServices = _
GetObject("winmgmts:\\" & strComputer & "\" & strNameSpace)
Set colNameSpaces = objSWbemServices.InstancesOf("__NAMESPACE")
For Each objNameSpace In colNameSpaces
Call EnumNameSpaces(strNameSpace & "\" & objNameSpace.Name)
Next
End Sub

Running this using cscript from the command line returns the following output:

Figure

This script recursively searches through all available namespaces on the target computer (in this example, the local machine since we defined the strComputer variable as “.”) using a subroutine. When called, the subroutine uses the GetObject function to connect to the initial namespace identified as “root”, establishes a connection to the WMI provider, and then returns all instances of namespace beneath the current namespace using the For…Next control loop.

WMI Class Components

The CIM Repository contains several class categories. While the details of the categories are beyond the scope of this article, understanding the differences in the classes and the class types can be useful in determining classes to use in your scripting. More information on class categories and class types can be found here.

The WMI classes represent the templates mentioned earlier that define the manageability of the resource instances within WMI. Take for example the Win32_Process class. All processes in this class (which are the processes on the target machine) share the same properties which can be queried or managed using WMI. Classes represent tangible components on a computer which can be both hardware and/or software related (e.g. fixed disks, PCI cards, services, printers, network shares, etc.).

WMI Class Components – Properties

Properties are used to further describe the managed resources. For example, a NIC card has a description name, an OS installation has a build number, a monitor has a refresh rate, etc. In the following code snippet, the properties of the Win32_Process class are in bold:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.InstancesOf("Win32_Process")
For Each objItem In colItems
Wscript.Echo "Description: " & objItem.Description
Wscript.Echo "ExecutablePath: " & objItem.ExecutablePath
Wscript.Echo "Name: " & objItem.Name
Next

WMI Class Components – Methods

Methods are actions that can be taken directly upon a managed resource. Services present the best examples to demonstrate methods because of the actions that can be performed on them including stop, start, pause, install, delete, etc. In the following example we will SWbemObject in conjunction with a method to stop the Alerter service:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _ \root\cimv2:Win32_Service.Name='Alerter'")
objWMIService.StopService

For this example the SWbemObject is actually a reference to objWMIService and the method used on the managed resource (the Alerter service) is StopService. Objects, such as the SWbemObject, declared in WMI are resources managed using the WMI Scripting Library object model which we’ll cover a little later.

WMI Class Components – Qualifiers

Qualifiers offer additional information about properties, classes, and methods. They define the operations of the called method, class or property. Using the example in the Properties section earlier, it is the qualifier that defines the properties used in the fact that the information retrieved is to be stored in an array within the control loop.

SWbemObject Concept Explained

To better understand SWbemObject, it helps to understand what an object path is. An object path is the logical path that defines an object’s location in the WMI Repository. The object path can be represented by the following:

[\\Computer Name] [\Namespace] [:Class Name] [.Property=Value]

Used in code, it would look like this:

… GetObject(“winmgmts:\\ServerA\root\cimv2:Win32_Service.Name=’Alerter’”)

The underlined portion in bold represents the object path defines as follows:

[\\Computer Name] = \\ServerA
[\Namespace] = \root\cimv2
[:Class Name] = :Win32_Service
[.Property=Value] = .Name=’Alerter’

To put it all together, consider the following script:

1. strComputer = "."

2. strNameSpace = "root\cimv2"

3. strClass = "Win32_Service"

4.

5. Set objClass = GetObject("winmgmts:\\" & strComputer & _

"\" & strNameSpace & ":" & strClass)

6.

7. For Each objClassProperty In objClass.Properties_

8.     Wscript.Echo objClassProperty.Name

9. Next

In the above script, lines 1-3 are initializing variables and assigning values, line 5 uses the GetObject function to connect to the WMI service and return the SWbemObject reference to the target class (in this example, the Win32_Service class), lines 7-9 used a For… Next control loop to display the properties collection for the Win32_Service class property. The output would resemble the following:

AcceptPause
AcceptStop
Caption
CheckPoint
CreationClassName
Description
DesktopInteract
DisplayName
ErrorControl
ExitCode
InstallDate
Name
PathName
ProcessId
ServiceSpecificExitCode
ServiceType
Started

Each of the above properties describe the Win32_Service class in that every service has a Description, a DisplayName, a Name, a value of Started (or stopped), etc…

To again illustrate the versatility and ease of use of the WMI code structure, if we were to use the same code as previously executed and simply change the three instances of the word “Properties” to the word “Methods” as seen here:

strComputer = "."

strNameSpace = "root\cimv2"

strClass = "Win32_Service"

Set objClass = GetObject("winmgmts:\\" & strComputer & _

"\" & strNameSpace & ":" & strClass)

For Each objClassMethod In objClass.Methods_

Wscript.Echo objClassMethod.Name

Next

We would return the following results using cscript from a command prompt:

Figure

As explained in the Methods section earlier, these are the actions that may be performed upon a managed resource within the Win32_Service class. In this instance, a managed resource might be the Alerter service, the Spooler service, the DHCP Server service, etc.
WMI Object Model

Now that we’ve touched on some of the WMI architecture, it is a good time to introduce the object model. Again, Microsoft has a nice flowchart of the library object model for WMI which can be found here. This will greatly aid in visualizing the process flow between the objects and instances mentioned so far. Take a few minutes to view the diagram and understand the processes occurring between objects in the library object model when different methods are called. This article will not go into details surrounding the object model; however, the following script will demonstrate the object calls made within the model.

strComputer = "ServerA"
Set objSWbemLocator = CreateObject("WbemScripting.SWbemLocator")
Set objSWbemServices = objSWbemLocator.ConnectServer _
(strComputer, "root\cimv2", "alan", "p@ssw0rd")
Set colSWbemObjectSet = objSWbemServices.InstancesOf("Win32_Process")
For Each objSWbemObject In colSWbemObjectSet
Wscript.Echo "Name: " & objSWbemObject.Name
Next

In the first line, we define the variable strComputer, next we create a reference to the SWbemLocator which resides at the top of the object model library (use the diagram for reference) using the CreateObject function. In the third line, we do two things: first we use the ConnectServer method of the SWbemLocator object to connect to the \root\cimv2 WMI namespace and second, pass user credentials to authenticate against the namespace on the remote server. This is only necessary if you are running the script in the context of a user account without administrative privileges on the remote computer. Now that we have connected to the WMI service, we again use the reference to connect to the Win32_Process class in line 4 (line 3 wraps around). Finally, we use the For… Next control loop in lines 5-7 to display the name property of each instance of the Win32_Process class.

For general WMI scripts used for tasks such as finding all the services on a machine, it is not completely necessary to understand every aspect of the WMI object model. However, a solid knowledge of the Automation objects (SWbem*) within the model is the beginning to writing more advanced WMI scripts.

Specifying Return Parameters in Queries

When using WMI to obtain information, it is not always desired to obtain all information regarding every instance of the object class being queried. Take the Event Logs for example. If you were to run the following script:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _ "\root\cimv2")
Set colItems = objWMIService.InstancesOf("Win32_NTLogEvent")
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next

You would receive information on every Event ID from every Event Log on the local system. While that is a lot of information, let’s take it a step further. Let’s say that every machine in an organization has a log file size limit of 30MB and that we use an array to query 50 different machines in one script. Since WMI scripts store their data in memory as they run, guess what’s going to occur. That’s right… system crash when we run out of available memory with the script process still hung running.

To narrow down the returned information in our script, we can use the WHERE clause in our variable statements. Up to now, we have been using the InstancesOf method in our script. To specify tighter query parameters, we will now use a new method called ExecQuery which replaces the InstancesOf method in the syntax. Compare the method below using our previous script with some modifications:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
"\root\cimv2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM " & _
"Win32_NTLogEvent WHERE Logfile = 'System'")
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next

This would return specified information from the System Log only. Let’s examine the script more closely to identify the processes associated with the new ExecQuery method:

The first line sets the strComputer variable to the local machine. The second line (wrapped) uses the GetObject method to attach to the \root\cimv2 namespace on the local machine using the winmgmts:// WMI moniker (think of moniker as another word for name) and assign this value to the objWMIService variable. The third line (wrapped) uses the ExecQuery method of the SWbemObject (objWMIService) to specify the information query parameters. If you were reading the statement in plain English, it would read as follows, “Select all (*) from the Win32NTLogEvent class where the Logfile property equals System.” So basically, only information in the System log will be returned.

Finally we use the For… Next control loop to return specified property information on the objects return from the query.

We can even drill down further for more detailed returns with another slight modification to the ExecQuery clause as demonstrated below in the same script using bold text:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
"\root\cimv2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM " & _
"Win32_NTLogEvent WHERE Logfile = 'System' AND " & _
"EventCode = ‘6005’")
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next

This is the exact same script as before; however, using the AND clause we are now filtering the query for not only information specific to the System Event Log, but also only System Event Log information with the Event ID of 6005.

As you can see, using clauses can dramatically narrow your results and thereby limit the amount of data you would have to filter through to find the desired results. You may also notice that the query clauses resemble Structured Query Language (SQL). While the syntax is very similar, it is actually WMI Query Language (WQL) and not really SQL. Basically it doesn’t have the SQL functionality to UPDATE or INSERT or modify database tables as SQL does and is used for SELECT query statements.

Conclusion

You can definitely see how powerful WMI can be in Windows Scripting from the many previous examples. With a robust object model, access to an extensive amount of classes and methods, the ability to make very minor modifications to scripts to obtain completely different results, and the ability to easily specify finite target information in WMI queries it becomes apparent why more and more applications are leveraging WMI via scripting to retrieve information about the current state computing environment.

If you have experience with WMI, then hopefully this article clarified some behind the scenes processes that you may have had taken for granted when running your scripts. If you are new to WMI, don’t get discouraged. WMI is more in-depth than the other subjects we have covered in scripting so far and takes a little more effort to learn. Don’t let any confusion you may have now dissuade you from continuing to learn WMI as it can make your administration tasks immensely easier not to mention being an invaluable addition to your skill set. Take advantages of the many resources on WMI listed in this article to help and post any questions to the forums as you test your new WMI scripts.

The following are simply a few script examples that can be used as templates demonstrating a few common tasks that may be performed using WMI Scripting with minor editing changes. Once you grasp the WMI concepts, you will find you only need to write a few scripts to have a whole arsenal of powerful tools for administration.

Stopping Services

strComputer = "." ‘Edit this for remote machines
strNamespace = "\root\cimv2" ‘Edit to change namespace
strServiceName = "Alerter" ‘Edit to reflect service to be stopped

Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & strNamespace)
Set colServices = objWMIService.ExecQuery _
("SELECT * FROM Win32_Service WHERE Name = '" & strServiceName & "'")
For Each objService in colServices
errReturnCode = objService.StopService() ‘StartService may also be used
Next

Forcing User Logoff

Const x = 4 'This can be changed to one of the following:
'0 = Logoff
'1 = Shutdown
'2 = Reboot
'4 = Forced Logoff
'5 = Forced Shutdown
'6 = Forced Reboot
'8 = Power Off Computer
'12 = Forced Power Off

strComputer = "." 'This can be changed for remote machines
strNamespace = "\root\cimv2" 'This can be changed for namespaces

Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" _
& strComputer & strNamespace)
Set colOperatingSystems = objWMIService.ExecQuery _
("SELECT * FROM Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
ObjOperatingSystem.Win32Shutdown(x)
Next

Account Logon Failures

strComputer = "." ‘This can be changed for remote machines
strNamespace = “\root\cimv2\” ‘This can be changed for namespaces
strEventCode = “681” ‘Edit to reflect error code

Set objWMIService = GetObject("winmgmts:\\" & strComputer & _
"\root\cimv2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM " & _
"Win32_NTLogEvent WHERE Logfile = 'Security'AND " & _
"EventCode = ‘" & strEventCode & “’”)
For Each objItem in colItems
Wscript.Echo "EventCode: " & objItem.EventCode
Wscript.Echo "User: " & objItem.User
Wscript.Echo "Logfile: " & objItem.Logfile
Wscript.Echo "Message: " & objItem.Message
Next