Introduction To WMI

WMI - Windows Management Instrumentation - is Microsoft's implementation of the WBEM [Web-Based Enterprise Management] standard, and a very powerful tool for administering computers. It was introduced with Windows 2000, but was partially implemented for Windows 95/98/NT. If you are using one of those operating systems you would need to install the WMI core, if it isn't already installed. There are two versions, one for 95/98 and NT and one specifically for 95/98.

WMI is analagous to a database of objects relating to you computer - programs, for example, or hardware - which Windows maintains internally. This database can be queried, and the query results are objects which expose
information and allow actions to be taken. So what does it do? What doesn't it do!

To show by example, run this simple program: you will need the WMI, RPC and DCOM services running (under Windows NT/W2K/XP). You do not need Administrative rights to query your local PC. If you are using VFP6 or earlier, see below for the alternative object syntax.

cClass = "WIN32_ComputerSystem"
oWMI = getobject("winmgmts:")
oItems = oWMI.ExecQuery("select * from " + cClass)
clear

for each oItem in oItems
	? alltrim(oItem.Name)
	? replicate("-", len(alltrim(oItem.Name)))
	GetProperties(oItem)
next

procedure GetProperties(oItem as object)
	for each oProperty in oItem.Properties_
		if oProperty.IsArray
			? oProperty.name + " : [Array]"
		else
			? oProperty.name + " : " + ;
				iif(isnull(oProperty.value), "", transform(oProperty.value))
		endif
	next
endproc

This produces a lot of information, and this is only the instances of one class: the other classes include your BIOS, your operating system, the startup commands in your registry - and there are hundreds more: hardware classes, operating system classes and it goes deeper still: you can access Active Directory: you can access drivers; you can, if you are using Windows 2000 or later, access real-time performance data (you need XP to use the formatted ("cooked") performance data classes).

Not all classes are available in all operating systems: generally, the more recent the operating system, the more classes are available. There are classes available under Windows 2003 Server that aren't in XP, classes in XP that aren't in Windows 2000, and classes that aren't in anything earlier.

There are also a lot more WMI providers - the engines that do the work: other providers include the Registry, Event Log, Windows Installer, SQL Server, and IIS providers (availability varies wildly by operating system). Try the above code with cClass = "Win32_Product" - that was the Windows Installer provider (it takes a minute or so on my computer before the output is produced)

I don't know of anything that can be determined about your computer that can't be found using WMI. I would expect that anything an application needs to know, it can find out using WMI with just a few lines of code. We'll see.

Let's look at the code above in more detail:

WMI Moniker Syntax

We are using GetObject with a moniker string, winmgmts: - this is just another way of accessing COM objects, a shortcut if you like. It was introduced in VFP7: if you're using VFP6 or earlier skip down for a different syntax you can use. This is using the simplest syntax of all - connecting to the the default namespace on the local computer: this format is short enough to remember for ad-hoc use in the command window, but if the invocation above does not work for you, a more complete moniker syntax is:

oWMI = getobject("winmgmts:{impersonationLevel=Impersonate}!\\.\root\cimv2")

Setting the impersonation level shouldn't be necessary for local connections, but it's possible for the default impersonation level to be incorrectly set and this will probably be needed under Windows NT anyway. The . between the backslashes represents the local computer: \root\cimv2 is the default namespace, the one where all the classes we've looked at so far are found.

An alternative to the moniker syntax

VFP6 and earlier doesn't support creation by moniker, so you need to use this alternative syntax:

oWMILocator = createobject("WbemScripting.SWbemLocator")
oWMI = oWMILocator.ConnectServer(".", "root\cimv2")

WMI Query Language

The next thing of note is the SQL-like query syntax. This is called WQL (WMI Query Language). As far as the SELECT syntax goes:

  • You can select a list of properties as the equivalent of the field list - run the above code but change select * from to select name from and see that the only property of the returned object is the name property.
  • You can only specify one class in the FROM clause, so there aren't any joins.
  • You can use WHERE clauses, and would want to in some circumstances: oWMI.ExecQuery("SELECT * FROM win32_NetworkAdapter WHERE ((macaddress IS NOT NULL) AND (manufacturer 'Microsoft'))") gets active network cards while ignoring the internal Windows networking components.
  • LIKE is supported.

The results

Once the query has executed, we have a collection of zero or more objects, represented here by oItems. We can look at oItems.Count to see how many there are: what we can't do easily is iterate through them using the Item collection, we need to use FOR.. EACH. The reason for this is that the Item collection is keyed on the object's WMI path, which you don't know until you have looked at the object.

Once you have a reference to an instance of the class, you can access it's properties in the normal way - oItem.Name - as well as via the Properties_ collection. As you see, the program tests for properties which are arrays: the BIOS class has a good example of what useful things might be found there. Usually the arrays have multiple entries, but not always, and they can be numeric or character data.

oWMI = getobject("winmgmts:")
* InstancesOf method just gets instances - less flexible than WQL
oBioses = oWMI.InstancesOf("win32_bios") 
oBios = .null.
for each ox in oBioses
	oBios = ox
next

for each nElement in oBios.BiosCharacteristics
    do case
	case nElement = 3
		? "BIOS Characteristics Not Supported"
	case nElement = 4
		? "ISA is supported"
	case nElement = 5
		? "MCA is supported"
	case nElement = 6
		? "EISA is supported"
	case nElement = 7
		? "PCI is supported"
	case nElement = 8
		? "PC Card (PCMCIA) is supported"
	case nElement = 9
		? "Plug and Play is supported"
	case nElement = 10
		? "APM is supported"
	case nElement = 11
		? "BIOS is Upgradable (Flash)"
	case nElement = 12
		? "BIOS shadowing is allowed"
	case nElement = 13
		? "VL-VESA is supported"
	case nElement = 14
		? "ESCD support is available"
	case nElement = 15
		? "Boot from CD is supported"
	case nElement = 16
		? "Selectable Boot is supported"
	case nElement = 17
		? "BIOS ROM is socketed"
	case nElement = 18
		? "Boot From PC Card (PCMCIA) is supported"
	case nElement = 19
		? "EDD (Enhanced Disk Drive) Specification is supported"
	case nElement = 20
		? "Int 13h - Japanese Floppy for NEC 9800 1.2mb (3.5, 1k Bytes/Sector, 360 RPM) is supported"
	case nElement = 21
		? "Int 13h - Japanese Floppy for Toshiba 1.2mb (3.5, 360 RPM) is supported"
	case nElement = 22
		? "Int 13h - 5.25 / 360 KB Floppy Services are supported"
	case nElement = 23
		? "Int 13h - 5.25 /1.2MB Floppy Services are supported"
	case nElement = 24
		? "Int 13h - 3.5 / 720 KB Floppy Services are supported"
	case nElement = 25
		? "Int 13h - 3.5 / 2.88 MB Floppy Services are supported"
	case nElement = 26
		? "Int 5h, Print Screen Service is supported"
	case nElement = 27
		? "Int 9h, 8042 Keyboard services are supported"
	case nElement = 28
		? "Int 14h, Social Services are supported"
	case nElement = 29
		? "Int 17h, printer services are supported"
	case nElement = 30
		? "Int 10h, CGA/Mono Video Services are supported"
	case nElement = 31
		? "NEC PC-98"
	case nElement = 32
		? "ACPI supported"
	case nElement = 33
		? "USB Legacy is supported"
	case nElement = 34
		? "AGP is supported"
	case nElement = 35
		? "I2O boot is supported"
	case nElement = 36
		? "LS-120 boot is supported"
	case nElement = 37
		? "ATAPI ZIP Drive boot is supported"
	case nElement = 38
		? "1394 boot is supported"
	case nElement = 39
		? "Smart Battery supported"
	otherwise
endcase

next

Quite a lot of useful information from one property of one class, don't you think? Implement a few classes and your clients might be impressed that your application knows more about their computers than they do.

Methods

If the class has methods, you can call them directly, or by using ExecMethod. Note that although methods may be defined in the class, they are not necessarily implemented by the provider. WIN32_Fan's Reset method is a good example. You will need to know whether the method is static - which apply only to WMI class definitions and not to class instances - or non-static, where the method affects a specific instance. The Create method of the Win32_Process class is a static method: you use it from a class definition to create a new process, it doesn't make sens to create one process from an instance of another. Correspondingly, non-static methods apply only to class instances: the Terminate method of the class is a non-static method because it terminates the process it was called from. You can determine whether the method is implemented and it's staticity by looking at it's qualifiers.

Here's code which enumerates methods and optionally their parameters and qualifiers, and illustrates what information is contained in the SystemProperties_ object.

cClass = [Win32_Service]
oWMI = getobject("winmgmts:{impersonationLevel=Impersonate}!\\.\root\cimv2")
oItems = oWMI.ExecQuery("select * from " + cClass )

clear

for each oItem in oItems

	? "Name: " + iif(isnull(oItem.name), "[Unnamed Item]", oItem.name)
	* Some classes don't have a name property and will raise an error

	?
	? "Full path: " + oItem.SystemProperties_("__PATH").value
	? "Namespace: " + oItem.SystemProperties_("__NAMESPACE").value
	? "Server: " + oItem.SystemProperties_("__SERVER").value
	nProperties = oItem.SystemProperties_("__PROPERTY_COUNT").value
	? "Property Count: " + transform(nProperties)
	? "Relative path: " + oItem.SystemProperties_("__RELPATH").value
	? "Dynasty: "+ oItem.SystemProperties_("__DYNASTY").value
	? "Superclass: "+ oItem.SystemProperties_("__SUPERCLASS").value
	? "Genus: "+ iif(oItem.SystemProperties_("__GENUS").value = 1, "Class", "Instance")
	? "Class: "+ oItem.SystemProperties_("__CLASS").value

	* Derivation is an array
	?
	? "Derivation"
	? replicate("-", len("Derivation"))

	for each oElement in oItem.SystemProperties_("__DERIVATION").value
		? oElement
	next

	?
	? "Methods:"
	? replicate("-", len("Methods"))
	GetMethodInfo(oItem, .t.) && (oItem, .T.) to get parameter info.

	* All objects will be of the same class so no need to see them all
	exit

next


procedure GetMethodInfo(oItem as object, lVerbose as Boolean)

	for each oMethod in oItem.Methods_
		? oMethod.name
		if not isnull(oMethod.InParameters) and lVerbose
			? "Input Parameters"
			for each oParameter in oMethod.InParameters.Properties_
				? chr(9) + oParameter.name + " :: " + GetParameterType(oParameter.CIMType)
			next
		endif

		if not isnull(oMethod.OutParameters) and lVerbose
			? "Output Parameters"
			for each oParameter in oMethod.OutParameters.Properties_
				? chr(9) + oParameter.name + " :: " + GetParameterType(oParameter.CIMType)
			next
		endif
		?
		if not isnull(oMethod.Qualifiers_) and lVerbose
			? "Qualifiers"
			for each oQualifier in oMethod.Qualifiers_
				? chr(9) + oQualifier.name
			next
		endif
		?
	next

endproc

function GetParameterType(nType as integer) as string

	do case
		case nType = 2 && wbemCimtypeSint16
			cType = "Signed 16-bit integer"
		case nType = 3 &&  wbemCimtypeSint32
			cType = "Signed 32-bit integer"
		case nType = 4 &&  wbemCimtypeReal32
			cType = "32-bit real number"
		case nType = 5 &&  wbemCimtypeReal64
			cType = "64-bit real number"
		case nType = 8 &&  wbemCimtypeString
			cType = "String"
		case nType = 11 && wbemCimtypeBoolean
			cType = "Boolean"
		case nType = 13 && wbemCimtypeObject
			cType = "CIM object"
		case nType = 16 && wbemCimtypeSint8
			cType = "Signed 8-bit integer"
		case nType = 17 &&  wbemCimtypeUint8
			cType = "Unsigned 8-bit integer"
		case nType = 18 &&  wbemCimtypeUint16
			cType = "Unsigned 16-bit integer"
		case nType = 19 &&  wbemCimtypeUint32
			cType = "Unsigned 32-bit integer"
		case nType = 20 &&  wbemCimtypeSint64
			cType = "Signed 64-bit integer"
		case nType = 21 &&  wbemCimtypeUint64
			cType = "Unsigned 64-bit integer"
		case nType = 101 &&  wbemCimtypeDatetime
			cType = "Date/time"
		case nType = 102 &&  wbemCimtypeReference
			cType = "Reference to a CIM object"
		case nType = 103 &&  wbemCimtypeChar16
			cType = "16-bit character"

		otherwise
			cType = "(Unknown)"

	endcase
	return cType

endfunc

Not shooting yourself in the foot

Some classes have writeable properties: you will need to call oItem.Put_() after setting the new value to save the changes.

I suggest you are very cautious calling methods and setting properties: it's up to you what you do and not my fault if your computer breaks, but I would feel bad if it all went wrong after running this code. Please be careful with WMI, it is surprisingly powerful: if you have Microsoft Virtual PC that is the ideal testbed.

I will come back to the security aspects of WMI later but for now the essence is that WMI can only do for you what you can do for yourself: you can't obtain any rights you don't already have over your PC.

Here's a final demonstration of things to do with WMI. Calvin Hsia blogged recently about how to use COM interoperability to access the .NET classes capable of enumerating which DLLs are in use by which process. The example he gives is to determine which processes have kernel32.dll loaded.

Here's VFP code to determine the same thing using WMI.

* Objective: show which processes have, for example, “kernel32.dll” loaded.

oWMI = getobject("winmgmts:")
oProcesses = oWMI.ExecQuery("select * from WIN32_Process")

clear

for each oProcess in oProcesses

	oFileAssociations = oWMI.ReferencesTo(oProcess.Path_.path, "CIM_ProcessExecutable")
	for each oFileAssociation in oFileAssociations
		oFile = oWMI.Get(oFileAssociation.Antecedent)
		if oFile.FileName = "kernel32" and oFile.Extension = "dll"
			? oProcess.name + " has kernel32.dll loaded"
			loop
		endif
	next
next

7 Responses to WMI part 1 – Local Queries

  • Anonymous says:

    This is HOT! HOT! HOT!

  • Stuart,

    Awesome job! Excellent job explaining how WMI works and how to use WMI with VFP.

    I agree with Bernard: HOT, HOT, HOT!

    Thanks for sharing your knowledge,

    Malcolm

  • Hi, Stuart!

    (send post but it doesn’t appear – may be I make something wrong?)

    Don’t forget that not all classes may be available on OS – Win2K, for example, don’t support some of them.

    See code below – list of the available classes

    LOCAL lcComputer, loWMIService, loClass, loQualifier, llIsQualifier

    lcComputer="."

    loWMIService = Getobject("winmgmts:\\" + lcComputer + "\root\cimv2")

    For Each loClass In loWMIService.SubclassesOf

    If Upper(Left(loClass.Path_.Class,5)) = "WIN32" Then

    For Each loQualifier In loClass.Qualifiers_

    If Upper(Alltrim(loQualifier.Name)) = "ASSOCIATION" Then

    llIsQualifier = .T.

    Else

    llIsQualifier = .F.

    Endif

    Next

    If llIsQualifier = .F. Then

    ? loClass.Path_.Class

    Endif

    Endif

    Next

    Next point – every class has own list of the properties

    We can get it with the simple code

    lcComputer="."

    loWMIService = GetObject(;

    "winmgmts:{impersonationLevel=impersonate}!\\" + ;

    lcComputer + ;

    "\root\cimv2";

    )

    loWMIClass = loWMIService.Get("Win32_OperatingSystem")

    For Each loProperty in loWMIClass.Properties_

    ? loProperty.name

    Next

    P.S.I wrote few articles about using WMI with VFP 2 years ago and it available only in russian at present.

    (http://www.hot.ee/jurisfox/vfpplus/wmi_01_ru.htm)

    Can I link your resource on my WMI related pages?

  • Anonymous says:

    Juri

    That code is going to come in very handy for something I’m doing at the moment, thank you very much. Your WMI articles are very good (I can only read the code though!)

    Stuart

  • Ken McGinnis says:

    Great job. This is a good teaching tool.

  • Auge_Ohr says:

    hi, will this Code work unter VISTA / W2k3 ?

  • stuartd says:

    Hi Auge_Ohr

    Most of this was developed using w2k3, so it will certainly all work under that. Vista and Windows 7’s UAC model might affect some parts due to different permissions and security requirements, but the WMI documentation is usually good about specifying which parts work on which platforms. The only way to really know is to try it..

Leave a Reply

Your email address will not be published. Required fields are marked *