WMI part 1 - Local Queries
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 using the % and _ characters, but unfortunately only for Windows XP/2003 or later.
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