Windows Management Instrumentation (WMI) is perhaps one of the best kept secrets in the IT world. It is a very powerful set of tools that you can use to gather information, configure settings, and manage PCs either locally or across the network. It truly is "management" and "instrumentation" of Windows.
WMI is the Microsoft implementation of an industry-based Web-Based Enterprise Management (WBEM) initiative. The Distributed Management Task Force (DMTF) now sets the standards for WMI.
There are WMI "namespaces" for performing operations on the registry, operations on the file system, discovering and configuring hardware, and manipulating settings of Windows itself. The system is extensible, so a new WMI "provider" can be added during the install of an application (like SQL Server or Internet Information Service). The default namespace (called "root\cimv2") contains over 500 classes in the following categories
The WMI data is accessed via a relational database. Like any database, there are schemas, data types, primary keys, table relationships, etc.
By way of introduction, let's just jump in with a simple example. The example below will return the amount of free disk space on the C: Drive of your PC. This example will use the VB.Net "ManagementClass" class to return information from the WMI "Win32_LogicalDisk" class. The GetInstances() method returns a collection of Win32_LogicalDisk instances, so we just loop through each one to find the one we want (in this example "C:").
The instance of the Win32_LogicalDisk class is like a database record, so we get the information contained in each field the same way we'd get information from a DataRow object (namely, using the object("FieldName") notation). And just like a DataRow, the field returns an "object" which we have to cast back to its native type. In this example, we convert the object returned from obj("FreeSpace") into a double. You'll need to consult the documentation for the Win32_LogicalDisk class to know the names of the fields and the data types in the "database".
Dim wmi As System.Management.ManagementClass Dim obj As System.Management.ManagementObject Dim ans As Double wmi = New System.Management.ManagementClass("Win32_LogicalDisk") For Each obj In wmi.GetInstances() If obj("DeviceID").ToString = "C:" Then ans = Convert.ToDouble(obj("FreeSpace")) / (1024.0# * 1024.0#) Exit For End If Next Return ans
To make this example work on a remote PC, you only have to change one line of code.
Dim wmi As System.Management.ManagementClass Dim obj As System.Management.ManagementObject Dim ans As Double ' The argument to ManagementClass includes 3 things: ' 1) the name of remote PC ' 2) the WMI "Namespace" (\root\cimv2:) ' 3) the WMI Class (Win32_LogicalDisk) wmi = New System.Management.ManagementClass("\\" & RemotePC & "\root\cimv2:" & _ "Win32_LogicalDisk") For Each obj In wmi.GetInstances() If obj("DeviceID").ToString = "C:" Then ans = Convert.ToDouble(obj("FreeSpace")) / (1024.0# * 1024.0#) Exit For End If Next Return ans
Note: There are some obvious requirements for security, permissions, and firewall settings that must be met before performing any WMI operations on a remote PC.
So far ,the example is just looping through all of the instances of Win32_LogicalDisk to find the correct one (to find the "C:" drive letter). WMI also supports its own query language, called WMI Query Language (WQL), that allows you to ask for a specific instance rather searching through the collection to find a match. Take a look at the modified version of the example:
Dim ms As System.Management.ManagementScope Dim oq As System.Management.ObjectQuery Dim mos As System.Management.ManagementObjectSearcher Dim obj As System.Management.ManagementObject Dim ans As Double ' The "scope" includes the name of the PC and the WMI namespace ms = New System.Management.ManagementScope("\\" & RemotePC & _ "\root\cimv2") ' use WQL to get just the one instance we want. This should look familiar ' to those who are used to SQL oq = New System.Management.ObjectQuery("SELECT * FROM Win32_LogicalDisk" & _ WHERE DeviceID='C:'") ' execute the query mos = New System.Management.ManagementObjectSearcher(ms, oq) For Each obj In mos.Get ans = Convert.ToDouble(obj("FreeSpace")) / (1024.0# * 1024.0#) Exit For Next Return ans
This example is much more efficient. If you don't believe me, put a floppy in drive A: and then execute the original example... it will take several seconds to scan the floppy to determine its free space before WMI gets around to looking at drive B: and then C:. This shortcuts the system to return just the instances that meet the criteria in the WQL query.
But wait, there's more! (said in a infomercial tone of voice). The records in WMI are truly a database and therefore have primary keys. So, when we consult the documentation, we see that the DeviceID field is a primary key. Now, we can short cut the system even more... since primary keys are guaranteed to be unique, we can ask for that specific instance "by name" rather than looping. Take a look at this shortened version:
Dim obj As System.Management.ManagementObject Dim ans As Double obj = New System.Management.ManagementObject("\\" & RemotePC & _ "\root\cimv2:Win32_LogicalDisk.DeviceID='C:'") ans = Convert.ToDouble(obj("FreeSpace")) / (1024.0# * 1024.0#) Return ans
Note: If you know the primary key and only want that one instance, you can ask for a specific instance of the management object.
Let's look at different example to demonstrate the concept of "WMI Associations". This example uses the Win32_UserAccount class to create a list of local accounts with a few of their attributes, then we get the related "rows" of information from the Win32_Group class. This allows us to get the group membership for each account. Again, you have to consult the documentation to determine if a relationship exists between the Win32_UserAccount and Win32_Group classes.
Dim wmi As System.Management.ManagementClass Dim obj As System.Management.ManagementObject Dim sb As New System.Text.StringBuilder ' create a WMI object based upon a simple "path" wmi = New System.Management.ManagementClass("\\" & RemotePC & _ "\root\cimv2:Win32_UserAccount") ' iterate through the instances For Each obj In wmi.GetInstances() sb.Append("Name: " & obj("Name").ToString & vbCr) sb.Append("Caption: " & obj("Caption").ToString & vbCr) sb.Append("Description: " & obj("Description").ToString & vbCr) sb.Append("SID: " & obj("SID").ToString & vbCr) ' Now let's use a "data relationship" to get associated data from ' another class sb.Append("Groups: ") For Each related_obj In obj.GetRelated("Win32_Group") sb.Append(related_obj("Name").ToString & ", ") Next sb.Append(vbCr) Next Return sb.ToString
WMI is not just about obtaining read-only data from a system, it can also be used to interact and control a system. This next example uses the InvokeMethod() method of the Win32_OperatingSystem class to shutdown a remote PC. Notice the use of a new class called ManagementBaseObject that is used to pass parameters and return codes to and from the WMI class. There is some initial "prep work" using the GetMethodParameters() method to set the number and types of parameters.
Dim wmi As ManagementClass Dim inParams, outParams As ManagementBaseObject Dim obj As ManagementObject Const LOGOFF As Integer = 0 Const REBOOT As Integer = 2 Const SHUTDOWN As Integer = 8 Const FORCEACTION As Integer = 4 wmi = New ManagementClass("\\" & RemotePC & "\root\cimv2:Win32_OperatingSystem") For Each obj In wmi.GetInstances() ' Get an input parameters object for this method inParams = obj.GetMethodParameters("Win32Shutdown") ' fill 'em in inParams("Flags") = SHUTDOWN + FORCEACTION inParams("Reserved") = 0 ' do it! outParams = obj.InvokeMethod("Win32Shutdown", inParams, Nothing) Result = Convert.ToInt32(outParams("returnValue")) Next
The meaning of the returns code from the Win32Shutdown method are found in the documentation.
The following tools from Microsoft are very useful in exploring the WMI universe. Of particular note is the "WMI Object Browser" from the WMI Administrative Tools download.