Copyright © 2020 Ashok P. Nadkarni. All rights reserved.
Windows Management Instrumentation (WMI) is Microsoft’s implementation of an industry standard for managing distributed computer systems. This chapter describes the use of WMI from Tcl.
WMI is a vast topic. And that is understating it. However, the major concepts are straightforward and the most common tasks are not difficult. This chapter focuses on these but provides enough background that those who need to go deeper can do so by reading the references at the end of the chapter. |
1. Introduction
Employers Computers are like horses, they require management.
WMI is Microsoft’s implementation of the Web-Based Enterprise Management (WBEM) architecture. WBEM in turn is an industry-wide initiative by the Distributed Management Task Force (DMTF) to define a common architecture and framework for management of heterogeneous computers systems. Its primary goals are to define a common, extensible data model for describing and exchanging data across differing platforms in a distributed network. This data model is referred to as the Common Information Model (CIM).
In this chapter, we use the terms WBEM and WMI somewhat interchangeably.
The Windows WMI implementation includes an extensive set of WBEM providers which implement access through WMI to the various components of the system. This chapter describes how to access these WMI API’s from Tcl. It is focused on the practical aspects of working with WMI and does not go into the details of the WBEM/WMI architecture and model. Readers wishing to learn more can see the references listed at the end of this chapter.
It is not possible to implement a WMI provider directly in Tcl or any other scripting language. See Developing WMI Solutions for sample implementations in C.
To run the code in this chaper, the Tcl interpreter process must be running under an account with administrative privileges. If running on Vista or later, it must also be running in elevated mode. |
The sample code in this chapter assumes
the following commands have been executed to load
the TWAPI twapi_wmi
package for accessing WMI.
% package require twapi_wmi
→ 4.4.1
% package require twapi_process
→ 4.4.1
% namespace path twapi
Not needed for WMI, but used in some example code |
A simple example of the use of WMI is this snippet to enumerate the OS updates installed on the system.
% set wmi [wmi_root]
→ ::oo::Obj306
% $wmi -with {
{ExecQuery "select * from Win32_QuickFixEngineering"}
} -iterate app {
puts "[$app HotFixID] [$app Description]"
$app -destroy
}
→ KB4576945 Update
KB4561600 Security Update
KB4570334 Security Update
KB4577266 Security Update
KB4571756 Update
% $wmi -destroy
The neat thing about WMI is that with just a change to the first line, the same snippet will enumerate the updates on a remote system.
2. WMI Concepts
Working with WMI and WBEM requires an understanding of some common concepts and terms. This section is only a brief introduction to these. More detail and explanation is provided in subsequent sections as we work through examples.
2.1. Classes, Properties, Methods and Qualifiers
A WBEM class defines a particular type of managed resource or some aspect of the WMI environment itsef. A class definition includes includes
-
properties (data) associated with that resource type, for example the number of packets sent by a network adapter.
-
methods (operations) applicable to that resource type, for example starting a Windows service.
-
qualifiers which provide additional information about the class, for example, the privileges required for access.
-
metadata for the class itself
What is metadata? Simply put, metadata is data that describes data. For example, a class describing printers may define properties related to a printer, such as manufacturer, supported paper sizes and so on. How does one discover what properties a particular class has ? That is where metadata for the class comes in. Among other things, it stores information about what properties are defined by the class, the types, and whether they are modifiable. Metadata allows us to write programs in generic fashion, for example to write a script that prints all properties for any class by looking up the property names associated with each. |
2.1.1. Class inheritance
The Common Information Model is based on object oriented concepts
so WBEM classes follow an inheritance hierarchy. For example,
the Win32_Process
class is defined by Microsoft and describes
instances of running programs. This class is derived from
the CIM_Process
class defined in the CIM
and extends that class with properties and methods specific
to Windows.
One of the goals, and consequently benefits, of WBEM was to
provide a consistent means of access to all manageable components
in the computing environment. Thus the same mechanisms can be
used to access information pertaining to a printer as that for
a network adapter.
Most WMI classes are directly or indirectly derived
from classes defined in the CIM, such as
CIM_ManagedSystemElement
. This allows them to share some generic
properties and methods that applications can use without having to
know class details.
2.1.2. Association classes
Most classes we will work with correspond to specific manageable
resources such as devices or processes. There is another type of
class called an association class which does not correspond to a
resource. Instead its instances reflect a relationship between
two or more classes.
For example, an association class may reflect
the fact that disk drives (class Win32_DiskDrive
)
are attached to a computer (class Win32_ComputerSystem
).
2.2. Instances
While class defines the logical information associated with a
managed resource type, an instance of a class contains the
actual information for a specific resource of that type. For
example, the Win32_Service
class defines properties and methods
for a Windows service. Each Windows service, like Rpcss
, will
have a Win32_Service
instance that contains the values of the
properties for that service, such as its display name, status and
so forth.
2.3. The CIM Repository
The WMI providers installed on a system need to make their class definitions accessible to applications. This is done by registering their class definitions with a database that stores the metadata and class definitions for all WMI classes on the system. This database is called the CIM repository.
On Windows 8, the repository is stored as a set of files in the
%SYSTEMROOT%\system32\wbem\repository
directory.
2.4. Schemas and Namespaces
A WMI schema is a group of classes, generally defined by a single
entity, that represent a certain type of managed
environment. For example, the Win32
schema is directed towards
Windows-based computers.
By convention, the class name prefix, for example Win32_
,
reflects the schema that defines it.
For the most part, in WMI we work with two schemas:
-
the
CIM
schema which is owned by the DMTF and defines the core classes in the CIM. -
the
Win32
schema which is owned by Microsoft and defines the extended classes specific to Windows. These are generally derived from the core classes in CIM.
On Windows, both the above schemas are loaded in the root\cimv2
namespace.
A WMI namespace contains a subset of classes and instances from one or more schemas that are relevant to a particular operating environment. Namespaces are hierarchical and may be nested. They are stored as a logical databases in the CIM repository. Applications need to specify the namespace they are interested in when accessing WMI services.
Namespaces and schemas should not be confused with each other. A network adapter vendor may define a class or an entire schema for its products. In a Windows network, the schema or a subset of it may be loaded into an existing namespace in the CIM repository or even a new namespace as appropriate.
2.5. WMI Query Language
The classes and properties in WMI can be viewed as a database and correspondingly, WMI provides a subset of SQL, the WMI Query Language (WQL) that allows for extracting data from this database. Using WQL instead of programmatically accessing data can often be more efficient, particularly when dealing with remote systems. Although we do not discuss WQL in complete detail the sections below provide examples of using WQL from Tcl. For a detailed description, see WQL Reference.
2.6. WMI names
Names are generally case-insensitive in WMI. This includes names
for namespaces, classes, methods, properties and WQL keywords.
Also, hierachical names, as are used for nested namespaces,
may use either \
or
/
as a separator. Thus root/cimv2
and root\CIMV2
refer
to the same namespace.
2.7. Object paths
A WMI object path is a string that describes the location of a WMI object. An `object' in this context can be a namespace, a class, or instances of a class. The path itself contains three components:
-
the name of the target system and the namespace of interest. This is of the form
\\SYSTEM\NAMESPACE
or//SYSTEM/NAMESPACE
. If this is the only component specified, the resulting path refers to the entire namespace. -
the name of the class. This is separated from the first component by a
:
so the entire path looks like\\SYSTEM\NAMESPACE:CLASS
. If the first component is not specified, it defaults to the current namespace. If the third component is not specified, the resulting path refers to the class itself. -
the instance identifier. This is separated from the class by a
.
character. The instance is identified by specifying a property value separated from the property name by a=
character. The entire path then looks like\\SYSTEM\NAMESPACE:CLASS.PROPNAME="PROPVALUE"
. Again, the system and namespace may be defaulted but the class name must be specified.
Object paths are used in WMI monikers which can be used to connect to a WMI object.
2.8. WMI Events
WMI also provides a mechanism for applications to be asynchronously notified for events. Like the rest of WMI, this mechanism is both flexible and extensible. WMI events may correspond to events like services starting or to a change in state or values of WMI objects, like disk space, or even changes to the WMI schemas.
2.9. Security
Because it is used to manage important computing resources, access to WMI is controlled. This security mechanism is based on the standard Windows security model and uses Windows user and group accounts for authentication and authorization. This access control extends to specific namespaces within WMI and includes control of what specific actions a particular user is allowed to invoke. WMI also makes use of impersonation so that access to resources is attempted under the account requesting the access so the WMI service itself, which runs as a privileged process, cannot be used to subvert the protections.
2.10. WMI service
Access to WMI from applications, whether locally or remote, is
channeled through the winmgmts
service. All access to WMI on a system requires connecting either
explicitly or implicitly to this service.
Now that we have the basic concepts out of the way, we can move on to the nitty gritty details and how to access WMI from Tcl.
-
We first cover the basics of the WMI scripting library since all WMI access makes used of it.
-
Any access to WMI requires connecting to the WMI service on the target computer so we cover that next.
-
We examine the security considerations for WMI objects.
-
We then interactively explore the structure of the CIM. This will flush out the concepts discussed above and lay the groundwork for working with WMI.
-
We next describe how to use WMI for its intended purpose - management and administration of distributed computer systems.
-
Finally, we describe the use of WMI for monitoring and event notification.
3. WMI scripting library
Scripting languages such as Tcl or VBScript access WMI through the WMI scripting library which provides a set of COM automation objects. These objects provide access to the WMI server, repository and managed resources. Since these form the basis for all WMI programming, we summarize their function and role here. Subsequent sections will demonstrate actual usage.
This section is only contains a partial list of commonly used types. The same is true of the methods and properties. Types related to WMI events are discussed in WMI Events. Other types are discussed as and when they are encountered in the rest of the chapter. As always, see WMI Reference for full details. |
3.1. The SWbemLocator object
Generally the first WMI object used, it is used to establish an authenticated connection to a namespace in a WMI service. It has a single method.
|
Connects to a namespace on a local or remote system. |
3.2. The SWbemServices object
Represents an established, authenticated connection as created
by an SWbemLocator
object. Its primary use is to retrieve and
manipulate SWbemObject
objects within the connected namespace.
|
Returns a |
|
Deletes an object from the namespace. Note this does not have any effect on the corresponding physical resource, if any. Generally used for deleting metadata like class definitions. |
|
Invokes any specified method of an object identified by its object path. |
|
Returns a |
|
Retrieves a single |
|
Returns a |
|
Returns a |
|
Returns a |
3.3. The SWbemObject object
An SWbemObject
automation object can represent a managed WMI
resource or a WMI class definition.
It exposes generic methods can be used to access the underlying
resource irrespective of the actual class representing the
resource, for example, Win32_Process
versus Win32_Service
, to
discover properties and methods defined for the resource etc.
In addition, it also exposes the type-specific
specialized properties and methods underlying resource.
The methods and properties that are part of
SWbemObject itself have a _ suffix. This distinguishes them from
the specialized properties of the actual underlying type.
|
|
Returns a |
|
Deletes the object from the namespace. Note this does not have any
effect on the corresponding physical resource, if any. Generally
invoked on an |
|
Invokes any specified method of the object. This is used to invoke type specific methods on the underlying object when the actual type is not known a priori. |
|
Returns a |
|
Retrieves a text description of the object in MOF syntax. |
|
Returns a |
|
Modifications to an object’s properties are only written back to WMI when this method is invoked. |
|
Returns a |
|
Returns a |
|
A list of class names comprising the inheritance chain of the obect which must correspond to a class definition. |
|
An |
|
An |
|
An |
|
An |
3.4. The SWbemObjectEx object
An SWbemObjectEx
automation class extends SWbemObject
with additional methods and properties.
In this book, we will
use SWbemObject generically to reference either type even
if the referenced property or method actually belongs to
SWbemObjectEx .
|
|
Returns a text representation of the object in various XML formats. |
|
Updates the property value cache of the object from the values in the underlying resource. |
SystemProperties_ |
An |
3.5. The SWbemObjectPath object
A SWbemObjectPath
contains a WMI
object path and provides properties
and methods to manipulate it. It can be used to parse the path for
an object or to construct one.
|
Authority component of path |
|
Name of the class |
|
Full moniker for the object |
|
Boolean that indicates whether the object is a class definition or an instance |
|
Namespace component of path |
|
Absolute path to the object |
|
Name of the WMI server |
3.6. The SWbemObjectSet object
Contains a collection of SWBemObject
objects. You can loop over
the objects in the collection with the comobj
wrapper’s -iterate
method.
|
Returns an item by indexed by its relative object path |
|
Number of items in the collection. |
The following short code snippet demonstrates use of the above automation objects.
% set wbem_locator [comobj WbemScripting.SWbemLocator]
→ ::oo::Obj335
% set wbem_services [$wbem_locator ConnectServer]
→ ::oo::Obj340
% $wbem_locator -destroy
% set wbem_object_set [$wbem_services InstancesOf Win32_Process]
→ ::oo::Obj345
% $wbem_object_set Count
→ 288
% $wbem_object_set -iterate wbem_object {
puts [$wbem_object Name]
$wbem_object -destroy
}
→ System Idle Process
System
Secure System
Registry
smss.exe
...Additional lines omitted...
% comobj_destroy $wbem_object_set $wbem_services
Get hold of a SWbemLocator to establish a connection to a WMI service | |
Returns a SWbemServices representing the local WMI service | |
The locator is no longer needed | |
Returns a SWbemObject set collection containing SWbemObjects corresponding to processes | |
Count of items in the collection (number of processes for this example) | |
Iterates over each SWbemObject, retrieving its name |
The rest of the chapter illustrates use of these objects in detail.
4. Connecting to WMI with SWbemLocator
Connecting to a remote system requires that various system components on the local and remote system are correctly configured. These include the Windows Firewall, DCOM and UAC. The application must also be running with appropriate privileges. See the WMI Reference for details. |
The first step in accessing WMI on a system is establishing an authenticated connection to the WMI service on the target system. There are two ways in which this can be done. The first method, which is more general, is described here. The second method, using monikers, is described in WMI monikers.
The SWBemLocator
COM automation object, implemented in the
WMI scripting library, can be used to establish a
connection to a namespace within
the local or remote WMI service.
Because WMI security can be on a per-namespace basis, a WMI connection is always to a specific namespace on the target system. Accessing a different namespace will require a separate connection, potentially with different credentials, even if the target system is the same. |
We create the
object passing its ProgID WbemScripting.SWbemLocator
to
the TWAPI comobj
call used for
creating any COM object.
% set wbem_locator [comobj WbemScripting.SWbemLocator]
→ ::oo::Obj1504
We then call its ConnectServer
method
to establish an authenticated connection to the desired
namespace on the target server. In the samples below,
we demonstrate various calls to ConnectServer
with different
combination of parameters. After each call, we destroy the created
object as we do not plan to use it just yet.
Without any parameters, ConnectServer
will connect to the default
namespace on the local system. We will have more to say on default
namespaces below.
% set wbem_services [$wbem_locator ConnectServer]
→ ::oo::Obj1508
% $wbem_services -destroy
To connect to a remote system, we specify its name or IP address.
Note that specifying .
as the remote computer name indicates a
connection to the local system.
% set wbem_services [$wbem_locator ConnectServer $::env(COMPUTERNAME)]
→ ::oo::Obj1510
% $wbem_services -destroy
% set wbem_services [$wbem_locator ConnectServer 127.0.0.1]
→ ::oo::Obj1512
% $wbem_services -destroy
For illustrative purposes the remote computer name we specify is the local system itself |
If a connection to a namespace that is not the default namespace is desired, we can specify it as the second parameter.
% set wbem_services [$wbem_locator ConnectServer . root/cimv2]
→ ::oo::Obj1514
% $wbem_services -destroy
If the connection to the WMI server cannot be made for whatever reason,
the above calls will hang. To prevent this, we can make use of the
(inappropriately named) iSecurityFlags
parameter. Setting this to a
value of 0x80
will cause the call to return with an error code instead
of hanging if the connection cannot be made.
% set wbem_services [$wbem_locator -callnamedargs ConnectServer iSecurityFlags \
0x80]
→ ::oo::Obj1516
% $wbem_services -destroy
Note the -callnamedargs modifier which allows parameters to be passed by name without having to pass intermediate parameters. |
Note that like all COM objects, the SWbemLocator
objects also
have to be destroyed when no longer needed. This might be as soon
as right after the ConnectServer
call if no more connections are
to be created.
% $wbem_locator -destroy
4.1. Authenticating connections
Connections to a WMI service are always authenticated connections. The above examples use the user credentials of the account the process is running under to authenticate to the WMI server. If we want connect using different credentials, we can specify the account name and password as two additional parameters.
set wbem_services [$wbem_locator ConnectServer REMOTESYSTEM root/cimv2 ACCOUNTNAME PASSWORD]
The ACCOUNTNAME
may be a simple name such as Administrator
or in
domain\name format such as Mydomain\Administrator
.
You cannot specify a user name and password when connecting to the local system. Local connections are always made using the credentials of the calling thread. |
The ConnectServer
call has other optional parameters as well,
for example to control the security authority, NTLM or Kerberos,
to be used for authentication. See WMI Reference for details
on these.
5. WMI Security Settings
An important consideration when working with WMI is security. We saw one aspect of this, connection authentication, previously. In addition, there are security settings associated with the connection and each object (class, instance, system metadata etc.) in the CIM. These settings control how objects are accessed, what privileges are required and so on. This settings are summarized in Summary of WMI security settings.
5.1. Modifying security settings with SWbemSecurity
Security settings in WMI are manipulated through objects of
type SWbemSecurity
. These settings may be
associated with several different object types including
the SWbemLocator
object used for connecting, the SWbemServices
object representing a connection and SWbemObject
objects.
All these objects have the
Security_
property which can be used to retrieve the associated
SWbemSecurity
object.
Security settings are passed on through objects so for example
setting the privileges associated with a SWbemLocator
object
will affect not only any SWbemServices
objects retrieved with
ConnectServer
but also all SWbemObject
objects retrieved
through them.
5.1.1. Setting privileges
Some objects and operations may require the invoker to have passed
certain operating system privileges in the connection. For
example, querying the Windows Security event log requires the
SeSecurityPrivilege
privilege to be held. This section provides
a sample of how the SWbemSecurity
object may be used to add
privileges.
Setting of privileges only require for local connections. When running against a remote WMI service, the RPC service takes care of enabling all permitted privileges. |
We start off with the usual boilerplate assuming we do not already have an established connection.
% set wbem_locator [comobj WbemScripting.SWbemLocator]
→ ::oo::Obj1518
% set wbem_services [$wbem_locator ConnectServer]
→ ::oo::Obj1522
% $wbem_locator -destroy
We then retrieve the SWbemSecurity
object for the connection.
Its Privileges
method will return a SWbemPrivilegeSet
object
containing the currently enabled privileges.
% set privileges [$wbem_services -with Security_ Privileges]
→ ::oo::Obj1532
% $privileges Count
→ 0
Note use of -with so we do not have explicitly deal with the SWbemSecurity object just for retrieving the privileges. |
We now add the desired SeSecurityPrivilege
using the AddAsString
method.
Once we have made the changes, we are free to destroy the
SWbemPrivilegeSet
object.
% set privilege [$privileges AddAsString SeSecurityPrivilege]
→ ::oo::Obj1538
% puts "Name: [$privilege Name], Value: [$privilege Identifier], Display: \
[$privilege Displayname]"
→ Name: SeSecurityPrivilege, Value: 7, Display: Manage auditing and security log
% $privilege -destroy
% $privileges -destroy
In the above code, we used the AddAsString method which accepts the
defined names assigned to privileges by Microsoft. We could have also
used the Add method. However, that method and its converse Remove ,
expect to be passed the corresponding integer value of the privilege
as shown in the puts statement output above.
You would need to look that up manually or use one of the techniques
described in the COM chapter to map mnemonics to integer values.
|
Finally, we print the event count and clean up.
% set logs [$wbem_services ExecQuery "Select * from Win32_NTEventLogFile where \
LogFileName='Security'"]
→ ::oo::Obj1545
% $logs -iterate log {puts [$log NumberOfRecords]; $log -destroy}
→ 30166
% $logs -destroy
% $wbem_services -destroy
5.1.2. Setting authentication levels
Authentication levels in this context does not mean authentication of user accounts. Rather it refers to DCOM authentication and encryption settings which control whether packets sent between systems are protected against tampering and whether data is kept private.
The different authentication levels are shown in the table below.
Setting the authentication level is illustrated in the sample below where we get the current level for a connection and then change it to require only authentication.
% set wbem_locator [comobj WbemScripting.SWbemLocator]
→ ::oo::Obj1553
% set wbem_services [$wbem_locator ConnectServer]
→ ::oo::Obj1557
% $wbem_locator -destroy
% set wbem_security [$wbem_services Security_]
→ ::oo::Obj1561
% $wbem_security AuthenticationLevel
→ 6
% $wbem_security AuthenticationLevel 5
% $wbem_security -destroy
% $wbem_services -destroy
6. Retrieving WMI objects
To retrieve data or invoke operations on any WMI object, we need to retrieve as a COM automation object.
One of the nice, but also confusing, features of WMI is that it presents the same uniform access not just to different types of managed resources but also to the WMI infrastructure and metadata itself. Thus a WMI object may contain the properties of a specific network adapter. At the same time, its class definition, Win32_NetworkAdapter, is also itself an object. Even the WMI infrastructure can be accessed as objects; for example the __NAMESPACE class whose instances correspond to WMI namespaces. All these are WMI objects and can be treated in a uniform manner from a programming perspective. |
There are two general mechanisms for attaching to a WMI object.
-
We can explicitly connect to the
SWbemServices
object for a WMI service using theSWbemLocator
object as in the previous section and then invoke methods to retrieve the desired object. -
We can directly retrieve to the object of interest by specifying its WMI moniker, a string that uniquely identifies the object to WMI.
The following short examples contrast the two methods by using them
to find state of the Rpcss
Windows service, first using SWbemServices
.
% set wbem_locator [comobj WbemScripting.SWbemLocator]
→ ::oo::Obj1568
% set wbem_services [$wbem_locator ConnectServer]
→ ::oo::Obj1572
% $wbem_locator -destroy
% set rpcss [$wbem_services Get "Win32_Service.Name='RPCSS'"]
→ ::oo::Obj1577
% $wbem_services -destroy
% puts "[$rpcss Displayname]: [$rpcss Status]"
→ Remote Procedure Call (RPC): OK
% $rpcss -destroy
The same may written using monikers as
% set rpcss [comobj_object {winmgmts:\\IO\root\cimv2:Win32_Service.Name="Rpcss"}]
→ ::oo::Obj1581
% puts "[$rpcss Displayname]: [$rpcss Status]"
→ Remote Procedure Call (RPC): OK
% $rpcss -destroy
Note comobj_object , not comobj , has to be used with monikers |
which is considerably less code.
How do you choose between these methods ?
-
If you need to specify credentials (password and/or the authority) you have to use
SWbemLocator
. There is no choice because these cannot be specified in monikers. -
For interactive use or short programs, monikers are convenient as the code is more succinct.
-
On the other hand, monikers can be less efficient when accessing multiple objects in the target namespace.
-
In case of errors connecting to an object, use of
SWbemLocator
often provides more detailed errors that help in diagnosis.
We will illustrate both of these methods throughout this chapter but we first need to describe monikers in more detail.
6.1. WMI monikers
Microsoft COM uses monikers as specially formatted names that identify COM objects. A WMI moniker is just a special case of this where the moniker uniquely identifies objects in the WMI world, whether they be namespaces, classes or class instances.
A WMI moniker takes the form of a string with a specific format:
-
The moniker always starts with the literal winmgmts: prefix.
-
This is followed by a optional string which specifies the security settings, such as impersonation level and privileges and locale. If not present, the default settings are used.
-
The last component is also optional and specifies the WMI object path. If not present, the default namespace on the local system is assumed.
For full syntax details for WMI monikers, see WMI Reference. Some examples of monikers are shown in WMI moniker examples.
As seen in the last example above, the names used to specify privileges
in monikers are a shortened form of the Win32 privilege names. Thus
the moniker uses security , not SeSecurityPrivilege . This differs
from what we saw earlier
with regards to use of the Security_ object.
|
7. Managing resources
We are now well armed to tackle the task of actually using WMI to manage computer systems. This involves one or more of the following:
-
retrieving the object or objects that correspond to the managed resources of interest
-
reading properties from an object
-
modifying properties of an object
-
invoking actions on an object
7.1. Retrieving multiple objects
There are several mechanisms by which objects for managed resources might be obtained.
-
Using the
InstancesOf
method ofSWbemServices
-
Using the
Instances_
method of the class object -
Using a WMI Query Language (WQL) query
7.1.1. Retrieving objects using InstancesOf
Once a SWbemServices
object for a namespace is obtained as
previously described, its InstancesOf
method can be used to
retrieve the instances for any class.
We start off by connecting to the root/cimv2
namespace.
% set wbem_services [comobj_object winmgmts:root/cimv2]
→ ::oo::Obj1585
This gives us a SWbemServices
object just like before.
One of the most useful
methods of this object is InstancesOf
which can be used to return
all instances of any class in that namespace. In our example,
we specify the Win32_LogicalDisk
class corresponding to the drives
in the system.
The InstancesOf method can take additional optional parameters
which impact memory and performance and control what information
is returned. See WMI Reference for details.
|
% set disks [$wbem_services InstancesOf Win32_LogicalDisk]
→ ::oo::Obj1589
This gives us a SWbemObjectSet
, each element of which is
an SWbemObject
representing the logical drives in the system.
We know from reading the documentation for Win32_LogicalDisk
that two of its properties are Name
and Description
.
We can use the TWAPI COM -iterate
method to loop over the
SWbemObjectSet
.
% $disks -iterate disk {
puts "[$disk Name]: [$disk Description]"
$disk -destroy
}
→ C:: Local Fixed Disk
D:: Local Fixed Disk
% $disks -destroy
% $wbem_services -destroy
Or in a slightly shorter, but more obscure form, which negates the need
for explicitly managing the disks
object.
% set wbem_services [comobj_object winmgmts:root/cimv2]
→ ::oo::Obj1601
% $wbem_services -with { {InstancesOf Win32_LogicalDisk} } -iterate disk {
puts "[$disk Name] [$disk Description]"
$disk -destroy
}
→ C: Local Fixed Disk
D: Local Fixed Disk
% $wbem_services -destroy
See the TWAPI COM module documentation for how -with and -iterate work. |
7.1.2. Retrieving objects using Instances_
In WMI, as we shall see in detail later, classes themselves are
represented by an object. As an alternative to the above method,
we can retrieve the object for the class Win32_LogicalDisk
itself. This object is also a SWbemObject
and has the
Instances_
method which can be invoked to retrieve
Win32_LogicalDisk
instances in much the same manner as the first
case.
% set disk_class [comobj_object winmgmts:root/cimv2:Win32_LogicalDisk]
→ ::oo::Obj1617
% $disk_class -with Instances_ -iterate disk {
puts "[$disk Name] [$disk Description]"
$disk -destroy
}
→ C: Local Fixed Disk
D: Local Fixed Disk
% $disk_class -destroy
7.1.3. Retrieving objects using WQL
The WMI Query Language (WQL) is a subset of the Structured Query Language (SQL) for databases. It can be used to selectively retrieve a set of WMI objects through a query specification that has the form
SELECT properties FROM classname WHERE conditions
where
-
properties
is a comma-separated list of names of the properties that are to be retrieved with*
denoting all properties. -
classname
indicates the class whose instances are to be retrieved -
conditions
is an optional clause specifying the criteria that an instance should meet in order to be included in the returned collection.
The query can be passed to the ExecQuery
method of an
SWbemServices
object to retrieve matching objects.
% set wbem_services [comobj_object winmgmts:root/cimv2]
→ ::oo::Obj1632
% set disks [$wbem_services ExecQuery "SELECT * FROM Win32_LogicalDisk"]
→ ::oo::Obj1636
% $disks -iterate disk {
puts "[$disk Name] [$disk Description]"
$disk -destroy
}
→ C: Local Fixed Disk
D: Local Fixed Disk
% $disks -destroy
% $wbem_services -destroy
We will not go into WQL syntax and operators beyond what is used in the examples. It is for the most part easy to decipher from the examples. For detailed information see WQL Reference.
Contrasting WQL with other methods
The above sample using WQL may not look significantly different from the preceding ones. But WQL offers significant benefits in performance and ease when only a subset of the data is of interest.
-
WQL allows us to request only the properties of interest.
-
Similarly, WQL lets us restrict the returned collection to only those objects that match specified criteria by specifying the
WHERE
clause. This is usually a lot simple than iterating and and checking condition programmatically.
With large data sets like event logs, both features greatly improve memory usage and response time. When the WMI target is a remote system, network traffic is also greatly reduced.
The code prints the service name and process id only for services that are
in state Running
.
% set wbem_services [comobj_object winmgmts:root/cimv2]
→ ::oo::Obj1648
% set services [$wbem_services ExecQuery "SELECT Name,ProcessId FROM Win32_Service \
WHERE State='Running'"]
→ ::oo::Obj1652
% $services -iterate service {
puts "[$service Name]: [$service ProcessId]"
$service -destroy
}
→ AdobeARMservice: 4828
Appinfo: 5088
Apple Mobile Device Service: 4784
AppMgmt: 648
AppXSvc: 23480
...Additional lines omitted...
% $services -destroy
% $wbem_services -destroy
WQL allows strings to be delimited by either single quotes or double quotes. We prefer single quotes (as above) as using double quotes often entails extra escaping due to the fact that Tcl also uses double quotes as string delimiters. |
7.2. Retrieving a single object
In situations where only a specific resource is of interest, there
is no need to retrieve all instances and iterate looking for a match.
Each instance has one or more key properties
which can be used to directly access the object. For example, the
Name
field of a Win32_LogicalDrive
object is a key.
Again, there are a couple of ways to retrieve a specific object by its key property:
-
Using its moniker
-
Using the
Get
method ofSWbemServices
WQL is not listed here because it always returns a collection, not a single object, even if the collection contains a single instance. |
7.2.1. Retrieving an object using its moniker
A WMI moniker allows specification of a key property and the value
to be matched. The key property and value are separated from the
class name component by a .
.
Thus we can find the free space on a disk with the following script.
% set disk [comobj_object winmgmts:root/cimv2:Win32_LogicalDisk.DeviceId='C:']
→ ::oo::Obj2268
% puts "[$disk FreeSpace] bytes"
→ 37410193408 bytes
% $disk -destroy
Note that the DeviceId
property is a key for the class. Using
Name
instead would have caused the command to fail as that property
is not a key.
If you are not sure what the name of the key property is for a
class you can leave it out
(assuming you what the key value is, as in this case C: ).
So the above could also have been written as
|
set disk [comobj_object winmgmts:root/cimv2:Win32_LogicalDisk='C:']
7.2.2. Retrieving an object using Get
Using monikers is convenient but if several instances needs to be
processed, it is more efficient to use the Get
method of the
SWbemServices
object. This command takes the relative object
path of the instance as an argument as illustrated in the next
code fragment and returns the corresponding instance object.
% set wbem_services [comobj_object winmgmts:root/cimv2]
→ ::oo::Obj2272
% foreach service_name {rpcss eventlog} {
set service [$wbem_services Get "Win32_Service.Name='$service_name'"]
puts "$service_name: [$service State]"
$service -destroy
}
→ rpcss: Running
eventlog: Running
% $wbem_services -destroy
7.2.3. Retrieving a singleton class object
A singleton class is a class for which there is exactly one
instance. Examples includes Win32_WMISetting
, which contains the
operational settings for the WMI installation, and StdRegProv
which represents the entire contents of the Windows registry as
a single instance.
The command
set wmi_settings [comobj_object winmgmts:root/cimv2:Win32_WMISetting]
would retrieve the definition of the Win32_WMISetting
class, and
not the instance. To retrieve an instance of a singleton class,
the key value is specified as @
. For example,
set wmi_settings [comobj_object winmgmts:root/cimv2:Win32_WMISetting=@]
Note that the @
is not enclosed in quotes.
7.2.4. Getting a moniker from an object
Given an object, we can extract the moniker from it through its
Path_
property which returns a SWbemObjectPath
whose
DisplayName
property contains the moniker for the object.
% set service [comobj_object winmgmts:Win32_Service='rpcss']
→ ::oo::Obj2284
% $service -with Path_ DisplayName
→ WINMGMTS:{authenticationLevel=pktPrivacy,impersonationLevel=impersonate}!\\IO...
% $service -destroy
7.3. Working with properties
Properties contain the data associated with the managed resource
represented by a class instance;
for example, the state of a Windows service is contained in
the State
property of the Win32_Service
instance for that
service. Most of the code examples we have seen so far have dealt
with reading properties.
WMI also defines a set of system properties for
every class and instances. These are not related to the underlying
resource but rather reflect metadata; for example the CLASS
property contains the class to which an object belongs. System
property names, like system class names, always begin with .
We now discuss additional operations involving properties including
-
Enumeration of properties for an object
-
Using generic methods to read properties
-
Modifying property values
To start off, let us first retrieve an object that we will use for illustration purposes through the discussion.
% set disk [comobj_object winmgmts:root/cimv2:Win32_LogicalDisk='C:']
→ ::oo::Obj2294
7.3.1. Enumerating properties
WMI provides for fairly comprehensive introspection capabilities for classes and objects. This includes the ability to the operate on properties and their values without knowing the actual type of the object. This is important in several contexts. Properties defined for a particular class can change between WMI versions or may differ between systems. Hence it is useful to be able to determine whether a particular property is defined for a class. Introspection is also useful when writing general purpose WMI utilities such as WMI browsers.
Property introspection is facilitated by through the
Properties_
property that is defined for every
SWbemObject
. This contains a SWbemPropertySet
object which is
a collection of SWbemProperty
objects each of which completely
describes one property.
We can enumerate the names and values of
all the contained properties by iterating over this collection.
The SWbemProperty
object has properties
called Name
and Value
which contain the name and value of the
property it represents.
% set propset [$disk Properties_]
→ ::oo::Obj2299
% $propset -iterate prop {puts "[$prop Name] = [$prop Value]"; $prop -destroy}
→ Access = 0
Availability =
BlockSize =
Caption = C:
Compressed = 0
...Additional lines omitted...
% $propset -destroy
Similarly, the SystemProperties_
property contains the system
properties for the object. Paraphrasing the above a shorter form,
% $disk -with SystemProperties_ -iterate -cleanup prop {puts "[$prop Name] = \
[$prop Value]"}
→ __PATH = \\IO\root\cimv2:Win32_LogicalDisk.DeviceID="C:"
__NAMESPACE = root\cimv2
__SERVER = IO
__DERIVATION = CIM_LogicalDisk CIM_StorageExtent CIM_LogicalDevice CIM_Logica...
__PROPERTY_COUNT = 40
__RELPATH = Win32_LogicalDisk.DeviceID="C:"
__DYNASTY = CIM_ManagedSystemElement
__SUPERCLASS = CIM_LogicalDisk
__CLASS = Win32_LogicalDisk
__GENUS = 2
When you try the above command, you might notice that we retrieved
the property Properties_
from the $disk
object, it was not
actually listed anywhere! The reason is that
Properties_
only includes the properties defined by the
underlying managed resource object and system properties, not
the generic ones defined by SWbemObject
itself.
7.3.2. Reading properties
We have already seen many examples of reading properties. For example,
% $disk FreeSpace
→ 37410193408
SWbemPropertySet
provides the method Item
to retrieve the
SWbemProperty
object for a property based on its name.
So you could also write the above as
% $disk -with {
Properties_
{Item FreeSpace}
} Value
→ 37410193408
which is useful if you get paid per line of code.
Note that system properties cannot be read with the first direct method. They have to be read as in the second example.
% $disk -with {
SystemProperties_
{Item __CLASS}
} Value
→ Win32_LogicalDisk
7.3.3. Refreshing property values
Property values reflect the underlying managed
resource at the time the object was created and will diverge as
the state of resource changes. The values can be refreshed by
calling the object’s Refresh_
method. This is a generic method
defined by SWbemObjectEx
and can be invoked for any object.
% $disk Refresh_
% $disk FreeSpace
→ 37410193408
7.3.4. Writing properties
The hardest part of writing properties is finding a property that is not read-only! Most WMI providers implement methods to modify their data as opposed to using writable properties.
Writing a property value simply involves supplying a value after the property name. For example, to change the disk label,
% $disk VolumeName NEWVOLUME
However, you will find that Explorer does not show the new
volume label. This is because setting a property value only
changes the value in the SWbemObject
representing the
resource. To actually have the new value written back into the WMI
provider resource (the logical drive in this case), the
Put_
method of the SWbemObject
must be called.
% [$disk Put_] -destroy
You should now see the value written back to disk. The Put_
command returns a SWbemObjectPath containing the object path of
the written object. Since we have no use for it, we destroy it
right away.
Just like for reads, we can also write to properties through the
Properties_
property for the object.
% $disk -with { Properties_ {Item VolumeName} } Value NEWVOLUME % [$disk Put_] -destroy
7.4. Working with methods
If properties are data associated with objects, methods are
operations that can be invoked to take action on the
underlying managed resource. For example, invoking the
Shutdown
method on an instance of class Win32_OperatingSystem
will result in the system being shut down.
7.4.1. The Methods_ property
Similar to its Properties_
property which lists all the WMI provider
properties, a SWbemObject
also has the Methods_
property which
contains the definitions for all methods implemented for that
object by the WMI provider. In analogous fashion, this contains
a SWbemMethodSet
object which is a collection of SWbemMethod
objects describing the methods defined by the WMI provider for
that class.
7.4.2. Enumerating methods
Once we have the SWbemMethodSet
, we can enumerate the methods
for an object. For example,
% set regprov [twapi::comobj_object winmgmts://./root/default:StdRegProv]
→ ::oo::Obj2528
% $regprov -with Methods_ -iterate -cleanup method_obj {puts [$method_obj Name]}
→ CreateKey
DeleteKey
EnumKey
EnumValues
DeleteValue
...Additional lines omitted...
% $regprov -destroy
A short explanation of this one liner might be in order if you
have not read the chapter on TWAPI's COM client
interface. The -with Methods_
invokes Methods_
on the
regprov
object. The rest of the command, starting with
-iterate
, is passed to the result of this invocation, a
SWbemMethodSet
object, which iterates through the collection
printing the Name
property. The -cleanup
option is simply a
convenience so that the -destroy
method does not need to be
called on every method_obj
.
The StdRegProv class is an example of a singleton class - a
class for which there a single instance in the CIM. In this case
the single instance represents the entire Windows registry on
system. There are no classes or instances representing individual
registry keys and values. Contrast this with the CIM_DataFile
where each instance represents an individual file.
|
7.4.3. Invoking methods
Invoking methods is generally as straightforward as accessing
properties. We have already used methods through this chapter, for
example ConnectServer
and Put_
, without explicitly calling
them out. Here we illustrate a slightly more complex method - one
that takes multiple parameters one of which is an output parameter.
Direct invocation of methods
The example reads a value from the registry.
The return value from the method call is a status indicator and
the actual registry value is stored in variable regvalue
.
The example also illustrates one aspect of using COM from Tcl.
Tcl (actually TWAPI) and most scripting languages use COM
type libraries to figure out method signatures including parameter types.
One of the things to be aware of when calling WMI provider methods
is that there is no type information associated with
this. Consequently, the parameter types have to be determined
heuristically. On occasion, it is necessary to explicitly indicate
that a particular parameter is an output parameter. This is the
case in the example below where the outvar command tells the
TWAPI COM connector that the parameter is an output
parameter whose result value is to be stored in regvalue .
|
% set regprov [twapi::comobj_object winmgmts://./root/default:StdRegProv]
→ ::oo::Obj2617
% $regprov GetStringValue 0x80000002 "SOFTWARE\\Microsoft\\Wbem\\Scripting" \
"Default Namespace" [twapi::outvar regvalue]
→ 0
% puts [twapi::variant_value $regvalue 0 0 0]
→ root\cimv2
% $regprov -destroy
0x80000002 selects the HKEY_LOCAL_MACHINE registry hive | |
variant_value extracts the value from a COM output parameter. |
Method invocation using ExecMethod_
Just like properties, methods can also be invoked indirectly
through SWbemObject’s `ExecMethod_
method. Although there is rarely a
need to do this, we illustrate it here because the
recipe is a little more involved and can be confusing. It also
further shows how deep WMI’s introspection capabilities go. We
will call the same method as above as a point of comparison.
You will very rarely have to use ExecMethod_ from Tcl so you can
safely skip this section without impacting your understanding the
rest of the chapter.
|
We get the StdRegProv
instance in the same fashion. To avoid
having to deal
with intermediate objects, we use the -with
option to chain
methods. In essence,
-
We retrieve via
Methods_
aSWbemMethodSet
from which… -
…we use the
Item
indexing command to extract aSWbemMethod
containing the method definition for GetStringValue… -
…from where we invoke
InParameters
to get its input parameter definition… -
…whose
SpawnInstance_
method will return an input parameter container… -
…whose
Properties_
property gives us properties correspond to named parameter values. -
Phew
% set regprov [twapi::comobj_object winmgmts://./root/default:StdRegProv]
→ ::oo::Obj2620
% set inparams [$regprov -with {
Methods_
{Item GetStringValue}
InParameters
} SpawnInstance_]
→ ::oo::Obj2637
% set inprops [$inparams Properties_]
→ ::oo::Obj2640
Next, we set the values of the input parameters, again using Item
,
this time to index the parameter collection based on parameter name.
% $inprops -with {{Item hDefKey}} Value 0x80000002
% $inprops -with {{Item sSubKeyName}} Value "SOFTWARE\\Microsoft\\Wbem\\Scripting"
% $inprops -with {{Item sValueName}} Value "Default Namespace"
We now do the actual method invocation.
% set outparams [$regprov ExecMethod_ GetStringValue $inparams]
→ ::oo::Obj2658
The result is a OutParameters
object whose properties
contain the return value and
output parameters from the method call. We extract them using similar
our standard loop for properties.
% $outparams -with Properties_ -iterate -cleanup param {puts "[$param Name] = \
[$param Value]"}
→ ReturnValue = 0
sValue = root\cimv2
Happily, this matches the result from the direct call earlier.
As always, we need to clean up.
% comobj_destroy $regprov $inparams $inprops $outparams
You might wonder about the rationale behind ExecMethod_
and why
its needed. The truth of the matter is that in Tcl and other
dynamic languages, where you can construct a call “on the fly”,
it is easy enough to generate a direct call to a method with any
signature and ExecMethod_
is of limited, if any, use. However,
in a language like C/C++ where this is not possible, ExecMethod_
provides a means for
applications like WMI browsers and management tools to call any
method without a hardcoded the call for a specific purpose.
7.5. Working with associations
One of important features of the CIM is that it provides a means
to record and manage relationships between managed
resources. An example of a relation is the association between a
logical drive C:
, represented by an instance of the class
Win32_LogicalDisk
, and the corresponding physical disk partition to
which it is assigned, represented by an instance of
Win32_DiskPartition
.
These relationships are represented in the CIM by a special type
of class, called an association class
. An instance of an
association class holds references to instances of the two classes
representing the relation and any additional data pertaining to
that relation.
As is common in WMI, there are multiple ways to deal with
associations. Here we describe use of the the References_ and
Associators_ method of SWbemObject . The same functionality is
also avaible through the ReferencesOf and AssociatorsOf
methods of SWbemServices and the REFERENCES OF and
ASSOCIATORS OF statements of WQL. Working with these is not
described below, but is very similar.
|
To illustrate the use of associations,
we will start off with a Win32_LogicalDisk
object to use for
illustrative purposes.
% set disk [comobj_object winmgmts:root/cimv2:Win32_LogicalDisk='C:']
→ ::oo::Obj2673
7.5.1. Retrieving associations using References_
An object (or the underlying class) may have multiple types of
associations. For example, the Win32_LogicalDisk
is associated
with a physical disk partition, the computer system it is attached
to and so on.
We can use the References_
system method of a SWbemObject
to
enumerate these associations.
% set refs [$disk References_]
→ ::oo::Obj2677
% $refs -iterate -cleanup ref {puts [$ref -with Path_ Class]}
→ Win32_LogicalDiskRootDirectory
Win32_LogicalDiskToPartition
Win32_VolumeQuotaSetting
Win32_SystemDevices
We see that a logical disk is associated with three different
types of relations, each represented by a different WMI class.
Win32_LogicalDiskToPartition
for example ties the logical disk
with a physical disk partition.
We can print a little more detail to examine each association.
% $refs -iterate -cleanup ref {
puts [$ref -with Path_ Class]
$ref -with Properties_ -iterate -cleanup prop {
puts " [$prop Name] = [$prop Value]"
}
}
→ Win32_LogicalDiskRootDirectory
GroupComponent = \\IO\root\CIMV2:Win32_LogicalDisk.DeviceID="C:"
PartComponent = \\IO\root\cimv2:Win32_Directory.Name="C:\\"
Win32_LogicalDiskToPartition
Antecedent = \\IO\root\cimv2:Win32_DiskPartition.DeviceID="Disk #0, Partiti...
...Additional lines omitted...
% $refs -destroy
Each association class contains properties that refer to the
associated instances through their
object paths, for example
GroupComponent
/PartComponent
. In addition, the
Win32_LogicalDiskToPartition
association contains additional
information about disk extents in the form of properties
StartingAddress
/EndingAddress
.
7.5.2. Retrieving instances using Associators_
Suppose you needed to retrieve detailed information about the
partition on which a logical drive was located. You could use
the sequence in the previous section to the association
classes and use the object path in the returned properties
for the Win32_LogicalDiskToPartition
instance to retrieve
the appropriate Win32_DiskPartition
instance of interest.
A more direct way is to use the Associators_
method of
SWbemObject
which returns a SWbemObjectSet
containing
the associated instances themself
rather than the association instances.
% set partitions [$disk -callnamedargs Associators_ strResultClass \
Win32_DiskPartition]
→ ::oo::Obj2797
% $partitions -iterate -cleanup partition {
puts "[$partition Name]: [$partition Type]"
}
→ Disk #0, Partition #1: GPT: Basic Data
% $partitions -destroy
A couple of points to be noted regarding the above code:
-
Without any additional arguments,
Associators_
would have returned all types of association. Because we are only interested in the partition, we restricted the returned collection to belong to classWin32_DiskPartition
. -
Associators_
takes many parameters. To avoid having to specify all of them (and take the default values), we use-callnamedargs
which allows passing of arguments by name.
References_ and Associators_ both have several options
that are useful
for selecting the classes or instances returned in the
collection. These are not described here.
|
7.6. Commonly used classes
There are hundreds of classes defined in WMI that represent all kinds of resources. The full list with details is available in WMI Reference. We only list a few commonly used ones by category in Common WMI resource classes.
Unless stated otherwise, all classes lie in the root/cimv2
namespace.
|
8. Exploring the CIM
Having looked at using WMI to access managed resources, let us now
look at the CIM itself in more detail by interactively
exploring its structure. What allows us to do this is the fact
that the CIM, along with stored metadata, can be
queried in exactly the same fashion as any managed resource in it.
We have already seen this when we retrieved class property and method
definitions using Properties-
and Methods_
.
The introspection capabilities that WMI provides are useful in many ways:
-
Not all systems have all WMI classes installed, and the same WMI class may have different properties depending on version. Introspection allows writing of robust applications that work across diverse systems.
-
Management applications have the ability to support new resources by discovering their properties at runtime without needing to be updated.
-
Additional metadata about properties, such as localization information, allows applications to better format data for display.
-
Being able to interactively introspect the CIM greatly helps in understanding its structure leading to more concise and efficient programs.
8.1. Introspection of namespaces
Namespaces partition the CIM repository into nested, logical structure. When an application initiates access to WMI, it always starts out connecting, implicitly or explicitly, to a specific namespace in the repository.
The root of the nested namespace structure is, thankfully,
named root
. (We say thankfully' because as we shall see,
the namespace `default
is not the default namespace.)
8.1.1. Enumerating namespaces
A CIM repository may have any number of namespaces. How does one go about listing what namespaces are present on a system ?
We start off by connecting to the root
namespace.
% set root_ns [comobj_object winmgmts://./root]
→ ::oo::Obj2804
This gives us a SWbemServices
object as always but note that this time
we have connected to the toplevel root
namespace. Namespaces
are themselves represented as instances of the class __NAMESPACE
so we can use the InstancesOf
method to enumerate them.
% set namespaces [$root_ns InstancesOf __NAMESPACE]
→ ::oo::Obj2808
This gives us a SWbemObjectSet
each element of which is
a SWbemObject
corresponding to each child namespace.
We print out the namespace names contained in the Name
property.
% $namespaces -iterate ns {puts [$ns Name] ; $ns -destroy}
→ subscription
DEFAULT
CIMV2
msdtc
Cli
...Additional lines omitted...
% $namespaces -destroy
% $root_ns -destroy
You may notice that the above commands do not list any nested namespaces.
This is because the InstancesOf
method only returns instances
of the __NAMESPACE
class in the namespace root
corresponding
to the SWbemServices
object we connected to initially. To retrieve
nested namespaces, we need to connect to each child namespace
recursively and repeat.
The following proc
does exactly that.
proc list_namespaces {ns_path} {
set ns_obj [comobj_object winmgmts://./$ns_path]
try {
set children [$ns_obj InstancesOf __NAMESPACE]
$children -iterate child {
set child_path "$ns_path/[$child Name]"
puts $child_path
list_namespaces $child_path
}
} finally {
if {[info exists children]} {
$children -destroy
}
$ns_obj -destroy
}
}
We wrap the code in a try so we can release the ns_obj if an exception is raised |
And when we run it,
% list_namespaces root
→ root/subscription
root/subscription/ms_409
root/DEFAULT
root/DEFAULT/ms_409
root/CIMV2
...Additional lines omitted...
we see the nested namespaces.
Most classes used in Windows administration reside in the root/cimv2
namespace. A notable exception is that the classes pertaining to
the Windows registry are present in the root/default
namespace.
8.1.2. The default namespace
When the namespace component is left out in a moniker, or unspecified
in a ConnectServer
call, it defaults to the namespace stored in
the registry key as shown below.
% registry get {HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wbem\Scripting} {Default \
Namespace}
→ root\cimv2
The default namespace used when no namespace is specified is not
the namespace named root/DEFAULT ! This is a source of confusion.
(Hu’s on first?)
|
8.1.3. Namespace security
Access to namespaces is controlled with the standard Windows
security mechanisms. Security descriptors can be attached to
any namespace. Generally, each top level namespace, for example,
root/cimv2
has a security descriptor attached which is then
inherited by nested namespaces.
If you get access denied errors when trying the code in this chapter, verify that the account in which you are running Tcl has appropriate permissions taking UAC into consideration. You may also need to enable appropriate privileges as described in Setting privileges.
WMI security descriptors are normally set via the WMI Control plug-in for the Microsoft Management Console. You can also programmatically access them through the __SystemSecurity class which has a single instance in each namespace and contains the security descriptor settings for that namespace. We do not discuss this further here.
8.2. Introspection of classes
We now look at the structure of WMI classes in more detail. WMI classes fall into three categories:
-
System classes. These classes represent information about the WMI configuration and the CIM repository. We have already seen one of these -
__NAMESPACE
- earlier. System class names begin with two underscore characters. -
Core and common classes. These classes are abstract classes defined by the DMTF for common types of resources but independent of the actual implementation technology. For example, the
CIM_Process
class represents the abstract notion of a process. These classes have no instances as they are only used to derive extension classes. System classes generally begin with theCIM_
prefix. -
Extension classes. These are defined by vendors to represent concrete managed resources for a particular implementation technology. They are often derived from the core and common classes. For example, the
Win32_Process
class is derived fromCIM_Process
and its instances represent running processes on Windows systems. Most, but not all, extension classes defined by Microsoft have theWin32_
prefix.
8.2.1. Enumerating classes
Enumerating classes follows very much the same
sequence that we saw earlier for namespaces except that we have
to use the SubclassesOf
method instead of InstancesOf
.
This returns a SWbemObjectSet
that we can iterate over.
% set wbem_services [comobj_object winmgmts://./root/cimv2]
→ ::oo::Obj3969
% $wbem_services -with SubclassesOf -iterate -cleanup cls {
puts [$cls -with Path_ Class]
}
→ CIM_Indication
CIM_ClassIndication
CIM_ClassDeletion
CIM_ClassCreation
CIM_ClassModification
...Additional lines omitted...
This will enumerate classes within the namespace. If you only need
subclasses of a specific class, you can specify that to
SubclassesOf
to only include those. The following will only list
the WMI event classes (which all derive from __Event
).
% $wbem_services -with {{SubclassesOf __Event}} -iterate -cleanup cls {
puts [$cls -with Path_ Class]
}
→ __ExtrinsicEvent
Win32_DeviceChangeEvent
Win32_SystemConfigurationChangeEvent
Win32_VolumeChangeEvent
MSFT_WMI_GenericNonCOMEvent
...Additional lines omitted...
% $wbem_services -destroy
Alternatively, if you already have a class in hand, you can use
the Subclasses_
method.
% set event_class [comobj_object winmgmts://./root/cimv2:__Event]
→ ::oo::Obj14244
% $event_class -with Subclasses_ -iterate -cleanup cls {
puts [$cls -with Path_ Class]
}
→ __ExtrinsicEvent
Win32_DeviceChangeEvent
Win32_SystemConfigurationChangeEvent
Win32_VolumeChangeEvent
MSFT_WMI_GenericNonCOMEvent
...Additional lines omitted...
% $event_class -destroy
You can also enumerate class definitions with WQL using a SELECT
query. An optional
WHERE
clause can be specified so that we only retrieve
subclasses of Event
in this example as we did in the
previous. Note the use of the special class meta_class
and
property this
.
% set wbem_services [comobj_object winmgmts://./root/cimv2]
→ ::oo::Obj15218
% set classes [$wbem_services ExecQuery "SELECT * FROM meta_class WHERE __this ISA \
'__Event'"]
→ ::oo::Obj15222
% $classes -iterate -cleanup cls {puts [$cls -with Path_ Class]}
→ __Event
__ExtrinsicEvent
Win32_DeviceChangeEvent
Win32_SystemConfigurationChangeEvent
Win32_VolumeChangeEvent
...Additional lines omitted...
% comobj_destroy $classes $wbem_services
8.2.2. Class qualifiers
Class qualifiers provide additional information about a class. For
example, the Privileges
qualifier contains a list of
the privileges required to access the class. Extracting qualifiers
is very similar to extracting properties except that we use the
Qualifiers_
method to retrieve the collection.
Some commonly used class qualifiers are shown in Common class qualifiers.
All qualifiers are optional and need not be present in a class definition. |
Qualifier | Description |
---|---|
|
If present, is always set to |
|
If present, is always set to |
|
Contains a human readable description of the class. |
|
If present, is always set to |
|
Specifiies the privileges required to access the class. In the
above example |
|
Name of the WMI provider implementing the class. |
|
If present, is always set to |
Let us look at some examples of class qualifiers.
Because we will often print qualifiers in our examples in this and later sections, we will define a couple of procedures for the purpose. .
proc print_qualifiers {obj} {
$obj -with Qualifiers_ -iterate -cleanup qual {
puts "[$qual Name] = [$qual Value]"
}
}
proc print_qualifier_value {obj name} {
puts [$obj -with {Qualifiers_ {Item $name}} Value]
}
% set eventlog_class [comobj_object winmgmts:Win32_NtLogEvent]
→ ::oo::Obj16199
% print_qualifiers $eventlog_class
→ dynamic = 1
EnumPrivileges = SeSecurityPrivilege
Locale = 1033
Privileges = SeSecurityPrivilege
provider = MS_NT_EVENTLOG_PROVIDER
...Additional lines omitted...
% print_qualifier_value $eventlog_class Privileges
→ SeSecurityPrivilege
% $eventlog_class -destroy
We see that the SeSecurityPrivilege
is required for access to
the class.
8.2.3. Getting the textual definition of a class
It is often useful to get the textual definition of a class for pedagogical as well as practical purposes. For example, one can export the definition of a class for importing into the CIM repository on another system.
This is done via the GetObjectText_
method which returns the
class definition in MOF format suitable for import into a repository.
% set process_class [comobj_object winmgmts:Win32_Process]
→ ::oo::Obj16242
% $process_class GetObjectText_
→ [dynamic: ToInstance, provider("CIMWin32"): ToInstance, SupportsCreate, Creat...
class Win32_Process : CIM_Process
{
[Override("KernelModeTime")] uint64 KernelModeTime = NULL;
[read: ToSubClass, Override("Priority"): ToSubClass, MappingStrings{"...
...Additional lines omitted...
% $process_class -destroy
An alternative is the GetText_
method which returns the
definition in several different XML formats.
GetObjectText_ and GetObject_ can be used not only with the
class definitions but with class instances as well. In that case
the returned text includes values for the instance properties in
the appropriate format.
|
8.3. Introspection of properties
Delving deeper into the CIM structure, let us now look at property definitions for a class.
We have already seen introspection with properties of instances where we described enumeration of properties. Exactly the same method can be used with class definitions. The only difference is that in the case of properties the values will be empty since it is a class definition and not an instance.
% set bios [comobj_object winmgmts:Win32_BIOS]
→ ::oo::Obj16246
% $bios -with Properties_ -iterate -cleanup prop {puts [$prop Name]}
→ BiosCharacteristics
BIOSVersion
BuildNumber
Caption
CodeSet
...Additional lines omitted...
% $bios -destroy
8.3.1. The SWbemProperty object
The collection returned by Properties_
is a collection of SWPropertyObject
objects each describing a
property of the class. Let us print some information associated
with a property.
% set event_class [comobj_object winmgmts://./root/cimv2:Win32_NtLogEvent]
→ ::oo::Obj16377
% set data_property [$event_class -with Properties_ Item Data]
→ ::oo::Obj16384
% puts "[$data_property Name] : CIMType=[$data_property CIMType], \
IsArray=[$data_property IsArray]"
→ Data : CIMType=17, IsArray=1
% comobj_destroy $event_class $data_property
Here we have listed two additional pieces of information about the
Win32_NtEventLog.Data
. The value of IsArray
is a boolean
indicates that the Data
property is actually an array while the
CIMType
value gives
the base type. Both pieces are useful and necessary when writing
code that is generic and does not know the real class to which an
object belongs.
We do not discuss the possible values of CIMType here as the next
section describes another, potentially more useful, way to get the
type information for a property.
|
Other information about a property is also available through
SWbemProperty
. We examine one item, Qualifiers_
, in the next
section. For the rest, refer to WMI Reference.
8.3.2. Property qualifiers
Similar to class qualifiers, property qualifiers provide additional information about a property. The commonly used ones are summarised in Common class qualifiers. The full list is much larger and specified in WMI Reference.
You can retrieve property qualifiers from a class instance as well. However, the set of qualifiers for a property retrieved from an instance may not include all those available from the property qualifiers in the class definition. |
Let us examine some of the property qualifiers for the
Win32_Service
class. Since class and property qualifiers are
both retrieved as SWbemQualifierSet
instances,
We can use the same print_qualifiers
and
print_qualifier_value
procedures we used
earlier.
% set service_class [comobj_object winmgmts:Win32_Service]
→ ::oo::Obj16390
% set service_type_property [$service_class -with Properties_ Item ServiceType]
→ ::oo::Obj16397
% print_qualifiers $service_type_property
→ CIMTYPE = string
MappingStrings = {Win32API|Service Structures|QUERY_SERVICE_CONFIG|dwServiceT...
read = 1
ValueMap = {Kernel Driver} {File System Driver} Adapter {Recognizer Driver} {...
% comobj_destroy $service_class $service_type_property
We make the following observations about the ServiceType
property based on the output:
-
The presence of the
ValueMap
qualifier means the property value is restricted to being one of the strings listed. -
The presence of the
Read
qualifier means the property is read-only and cannot be modified. -
The
MappingStrings
qualifier gives some indication of how the value for the property is retrieved by the WMI provider. In this case, we see the property comes from thedwServiceType
field of theQUERY_SERVICE_CONFIG
structure retrieved through a native Win32 API call. -
Perhaps the most useful is the
CIMTYPE
qualifier which we discuss below.
When writing generic code, for example, to display a table of instances, it can be useful to know the underlying type. This can be useful for correct formatting of numbers, displaying hyperlinks and so on.
For example, consider the
Win32_LogicalDiskToPartition
class we saw earlier which
represents an association between a logical drive and a physical
disk partition. Let us retrieve an instance of this class.
% set wbem_services [wmi_root]
→ ::oo::Obj16422
% $wbem_services -with {{InstancesOf Win32_LogicalDiskToPartition}} -iterate \
instance break
% $instance Dependent
→ \\IO\root\cimv2:Win32_LogicalDisk.DeviceID="C:"
% set property [$instance -with Properties_ Item Dependent]
→ ::oo::Obj16437
% print_qualifier_value $property CIMType
→ ref:Win32_LogicalDisk
% comobj_destroy $property $instance $wbem_services
Places the first element in the collection into instance and aborts the loop |
Notice that Dependent
appears to return a string. However by
examining the CIMType
qualifer, in a real application you can
recognize it is actually a reference to a Win32_LogicalDisk
object
and display a live link.
The CIMTYPE qualifier should not be confused with the
SWbemProperty CIMType property discussed in the previous
section although they are both related and there is a one-to-one
correspondence between the two. The latter is an integer
value specifying the type. The former is a string and provides
some more detail. In particular, in the case of reference types, it
provides the type of the target reference as in the example below.
|
8.4. Introspection of methods
The final piece of introspection that remains to be explored pertains to methods.
8.4.1. The SWbemMethod object
All information pertaining to a method is encapsulated in a SWbemMethod object.
We have already described enumeration of methods and retrieving method definitions including parameter information so we will not repeat that here. We only examine other information about a method that is available through method qualifiers.
8.4.2. Method qualifiers
Like classes and properties, methods also have qualifiers that provide further information about the method. A few commonly encountered qualifiers are shown in Common method qualifiers.
For illustrative purposes, let use examine two methods two methods
of the Win32_Process
class - Create
, which creates a new
process, and Terminate
, which terminates an existing process.
We start off by retrieving the Win32_Process
class and
extracting the two method definitions of interest.
% set process_class [comobj_object winmgmts:Win32_Process]
→ ::oo::Obj16449
% set create_method [$process_class -with Methods_ Item Create]
→ ::oo::Obj16456
% set terminate_method [$process_class -with Methods_ Item Terminate]
→ ::oo::Obj16462
Qualifiers for methods are retrieved the same way as for classes
and properties so we can once again use the print_qualifiers
we defined earlier.
% print_qualifiers $create_method
→ Constructor = 1
Implemented = 1
MappingStrings = {Win32API|Process and Thread Functions|CreateProcess}
Privileges = SeAssignPrimaryTokenPrivilege SeIncreaseQuotaPrivilege SeRestore...
Static = 1
ValueMap = 0 2 3 8 9 21 ..
% print_qualifiers $terminate_method
→ Destructor = 1
Implemented = 1
MappingStrings = {Win32API|Process and Thread Functions|TerminateProcess}
Privileges = SeDebugPrivilege
ValueMap = 0 2 3 8 9 21 ..
% comobj_destroy $process_class $create_method $terminate_method
Some points to note from the above example:
-
the presence of the
Constructor
andDestructor
qualifiers onCreate
andTerminate
respectively. Methods that neither create nor destroy instances will not have either qualifier. -
the
Static
qualifier onCreate
. Obviously, you cannot invoke a method on an instance that is yet to be created. ThusCreate
is a class method and to be invoked on theWin32_Process
class itself, not on one of its instances. Contrast this with theTerminate
method which does not have this qualifier and has to be invoked on the instance to be terminated. -
the
MappingStrings
qualifier for both hints at the Win32 API calls used for implementation. -
the different privileges required for invoking the methods.
9. WMI Events
The discussion up to this point has solely been about managing resources and WMI infrastructure. We now look at another important capability provided by WMI - notifications of events.
The term event is overutilized. WMI events should not be confused with events from the Windows event log or Event Tracing for Windows (ETW). |
An event in the WMI world may reflect a change in some state or a happening, either within WMI itself or in the real-world being managed through WMI.
9.1. Temporary and Permanent consumers
Consumers of WMI event notifications can be of two types - temporary or permanent.
A temporary consumer is an application that subscribes to WMI notifications and receives them as long as it is running. A permanent consumer is a COM object that need not be running but can be invoked on the occurence of an event.
Tcl does not currently support permanent consumers so this chapter
only describes the use of temporary consumers.
Windows itself does provide some built-in permanent
consumers, CommandLineEventConsumer
in particular, that can run
external programs, including Tcl, in response to an
event. However, these have limited function and are not discussed here.
9.2. Synchronous and Asynchronous notifications
Event notifications can be received by an application in synchronous or asynchronous mode. In the former case, the application makes blocking calls waiting for an event notification. In the latter case, callbacks are delivered through a callback mechanism.
For ease of exposition, most of the examples in this section use synchronous mode. Asynchronous event delivery is described in Receiving event notifications asynchronously.
9.3. Intrinsic and Extrinsic events
Events are classified as intrinsic or extrinsic.
Intrinsic events reflect changes in the internal structure of the
CIM or data such as addition of a new class or a change in the
data for a class instance.
For example, a service stopping will be
reflected as a change in the State
property of the corresponding
Win32_Service
instance and is considered an intrinsic
event. Intrisic events are represented by system defined classes,
such as __InstanceModificationEvent
which might be generated for
the previous service scenario.
Extrinsic events are generated by WMI providers in response to real world events, such as shutdown. The WMI provider for the Windows registry also generates an extrinsic event when a registry key is modified. Note that this is considered and extrinsic event because the registry contents are not part of the CIM database.
We will describe monitoring intrisic events in the next section. Monitoring of extrinsic events follows a similar pattern but differs in some respects. These differences are highlighted in Consuming extrinsic events.
9.4. Event subscription quotas
Like many WMI operations, providing and consuming events can be
WMI therefore sets quotas on a per-user and system-wide
basis. These quotas include number of concurrent subscriptions,
memory usage and polling instruction limits and are controlled
through the _ArbitratorConfiguration
object described in
WMI resource limits.
9.5. Consuming intrinsic events
Just like all other objects, events are represented in WMI as
classes. Intrinsic events, because they reflect the CIM model,
all have the same structure and are defined through a small set
of system classes derived from the __Event
class. These are
shown in Intrinsic event classes. They reflect the fact
that intrinsic event notifications may pertain to three types
of objects - namespaces, classes and class instances -
and may be generated for creation, deletion and
modification of the respective types.
Subscribing to intrinsic events involves the following steps:
-
Connecting the WMI service on the target system
-
Running a query requesting events belonging to one of the classes shown in Consuming intrinsic events.
-
Reading events from the query result
Our example code illustrates these steps by subscribing to event notifications for processes starting up. Subscriptions for other intrisic events follow the same exact model differing only in the query used.
One more time, we start of by connecting to the WMI service to get
a SWbemServices
object.
% set wbem_services [comobj_object winmgmts:root/cimv2]
→ ::oo::Obj16520
We now submit a query that asks to be notified whenever a new process starts up.
% set event_source [$wbem_services ExecNotificationQuery "SELECT * FROM \
__InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process'"]
→ ::oo::Obj16525
Let us consider each component of the query in turn.
SELECT * FROM __InstanceCreationEvent
This follows the same form that we have seen for a WQL query
earlier. We ask the query to return instances of class
__InstanceCreationEvent
with each instance containing all
relevant properties. As summarized in
Intrinsic event classes, selecting from this class means
-
events related to deletion and modification will not notified
-
events related to classes and namespaces will not be notified
Obviously, the event instances returned are only for new events. They are not persisted.
The next clause is
WITHIN 1
If a WMI provider itself does not implement a more efficient event
notification mechanism, WMI itself will do a poor man’s emulation
by taking these snapshots of the specified class
(in this case Win32_Processes
) instances at regular
intervals. It then compares consecutive snapshots and generates
creation, deletion and modification events based on the
comparison. The WITHIN 1
clause directs the snapshots to be
taken at 1 second intervals. There is a tradeoff here.
Choosing a short interval means more frequent snapshots and
comparisons leading to greater system load. On the other hand,
increasing the interval increases the chances that an event will
be missed. In our example, if a process started and exited between
two snapshots, it would not result in a notification being
generated. In general, applications should not rely on
snapshot based notifications unless they are confident that the
state change for the resource will be last longer than the
snapshot interval.
The WITHIN clause must be specified if the relevant WMI
provider relies on this snapshot mechanism. WMI will return an
error on the call if the clause is not specified in such a case.
|
The final clause
WHERE TargetInstance ISA 'Win32_Process'
is included because we are not interested in all instance
creation notifications but only those for Win32_Process
as they
represent process creation events. We could have even
qualified it further based on a property of the target class. Thus
WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name='notepad.exe'
would result in notifications only when Notepad
was started and
not for any other process.
As we see later, TargetInstance
is a property of the
__InstanceCreationEvent
class which will hold an instance of
Win32_Process
for the newly created process.
Having set up the query, we wait a short interval (for the snapshot) and then start a process just to generate a notification.
% after 1000
% set pid [exec notepad.exe &]
→ 24088
We can now read the event from the event source using NextEvent
which returns an instance of __InstanceCreationEvent
.
The NextEvent
method is a synchronous method and thus the
application will stay blocked until a notification arrives
or the call times out.
You can specify a timeout of 0 to implement a polling model.
Alternatively, you can use the
asynchronous method using callbacks described
later.
|
% set event [$event_source NextEvent 2000]
→ ::oo::Obj16530
Call raises an exception if an event is not received within 2000 milliseconds |
__InstanceCreationEvent
contains three properties:
-
SECURITY_DESCRIPTOR
, which is the security descriptor attached to the event, if any -
TIME_CREATED
, which is a timestamp for the event -
TargetInstance
, which is an instance of the class we are monitoring, in this caseWin32_Process
.
We print out the properties of the process we just created.
% puts "Created new process with properties"
→ Created new process with properties
% $event -with {
TargetInstance
Properties_
} -iterate -cleanup prop {puts " [$prop Name] = [$prop Value]"}
→ Caption = notepad.exe
CommandLine = C:\WINDOWS\SYSTEM32\notepad.exe
CreationClassName = Win32_Process
CreationDate = 20201009105449.912038+330
CSCreationClassName = Win32_ComputerSystem
...Additional lines omitted...
% comobj_destroy $event $event_source
The above code sequence can be adapted for any target and event class.
The event classes in Intrinsic event classes all
have the same structure except that the ones pertaining to
modification events have an additional
field - PreviousInstance
- which contains the
target instance before the modification event. You can use this
to detect the nature of the modification.
You can use
|
9.6. Consuming extrinsic events
Extrinsic events are events that are not directly reflected in
the CIM data model. For example, since the content of the
Windows registry is not part of the CIM, changes to a registry key
cannot be notified through the intrinsic events. For such cases, a
WMI provider, StdRegProv
for the Windows registry, has to
implement its own classes for event notifications, such as the
RegistryKeyChangeEvent
class used for registry key changes.
The code sequence for subscribing to extrinsic events is very similar to that for intrinsic events. The differences are:
-
The query selects from a provider-specific class, like
RegistryKeyChangeEvent
instead of from one of the intrinsic classes. -
There is no requirement for a
WITHIN
clause in the query since the event generation does not need to be based on snapshots
We previously illustrated intrinsic events using process startup
notifications. Because the Windows provider also implements
extrinsic event classes, Win32_ProcessTrace
,
Win32_ProcessStartTrace
and Win32_ProcessStopTrace
for these,
we can use the same for extrinsic events as well.
Using these extrinsic classes for process start notifications is to be preferred because they do not rely on snapshots and are therefore both more efficient and reliable. |
Since we are only interested in processes starting, we will query
for Win32_ProcessStartTrace
instances and read from an event.
% set event_source [$wbem_services ExecNotificationQuery "SELECT * FROM \
Win32_ProcessStartTrace"]
→ ::oo::Obj16720
% set pid [exec notepad.exe &]
→ 7184
% set event [$event_source NextEvent]
→ ::oo::Obj16724
Note we have not specified the optional time out here so the call will block forever until an event is received. |
Extrinsic event classes, like intrinsic classes, are also derived
from the root __Event
class and have the SECURITY_DESCRIPTOR
and TIME_CREATED
properties described earlier. The other
properties are specific to the event class.
These process trace event classes have nothing to do with the
Win32_Process class. To get full details of a process, you would
have to extract the process name and PID from the event and use
them to retrieve the corresponding Win32_Process instance.
|
Here we just print the process name and PID.
% puts "Process [$event ProcessName] (PID [$event ProcessID]) started."
→ Process notepad.exe (PID 7184) started.
% comobj_destroy $event $event_source $wbem_services
9.7. Receiving event notifications asynchronously
So far we have used NextEvent
to retrieve events. This method
has some drawbacks because it is a synchronous blocking call which
will not return until an event notification is available:
-
The application cannot do any other work while it is waiting for an event.
-
Only one event query can be monitored at a time.
We now describe how event notifications can be received in asynchronous fashion to remove these limitations.
Note however, that asynchronous event notifications have the same
security issues as any asynchronous method calls as described in
[sect_wmi_method_call_modes] so you may still prefer to use
the polling mode using NextEvent with a zero timeout value
that was described earlier.
|
Asynchronous WMI event notifications are implemented through the Windows COM connection points. The basic steps are:
-
Write a callback procedure that should be invoked when a notification is received.
-
Create a event sink object, an instance of the
SWbemSink
class -
Bind the callback procedure to the sink object
-
Pass the sink object to a query for the events of interest
We repeat out earlier demonstration of process startup notifications, this time using asynchronous notifications. Although we are demonstrating with extrinsic events here, the same steps would be used for intrinsic events as well.
We start off by writing a callback procedure that will be invoked by the sink when a notification is received.
proc sink_callback {reason args} {
switch $reason {
OnObjectReady {
lassign $args event_interface context_interface
set event [comobj_idispatch $event_interface true]
puts "Process [$event ProcessName] (PID [$event ProcessID]) \
started."
$event -destroy
}
OnCompleted {
lassign $args winerror error_interface context_interface
puts "Notifications ended. Reason: [map_windows_error $winerror]"
set ::notifications_ended 1
}
}
return
}
Only used to terminate the event loop in our demonstration. A real program would not need this. |
A few, or perhaps more than a few, words are in order about the callback:
-
The first argument passed to the callback determines the reason for the callback. We need to deal with only two types,
OnObjectReady
andOnCompletion
. Other values are gracefully ignored. -
Remaining arguments are variable, depending on the reason value.
-
A reason of
OnObjectReady
indicates a new notification is being passed to the callback. In this case there are two additional arguments passed. The second is an optional context pointer which we do not use and hence ignore. The first additional argument is an interface pointer to the event, of classWin32_ProcessStartTrace
in our case. This is a raw pointer, which we need to convert to acomobj
COM object throughcomobj_idispatch
. We can then use it as we did in previous examples to print out theProcessName
andProcessID
properties. -
A reason of
OnCompleted
indicates that the query has been completed. This may be because it was ended under application control or because of some error. The first additional argument indicates the error code. The remaining arguments provide additional information and context which we do not use and ignore.
Note that the second argument in the comobj_idispatch call is
true . This is important as explained in the chapter
The Component Object Model. In a nutshell, our COM comobj wrapper will
decrement the reference count on the passed-in COM interface as
part of the -destroy call. Passing true as the second
parameter to comobj_idispatch does a corresponding increment on
the reference count. Without this increment, the event sink caller
will be left with a dangling pointer as the decrement from
-destroy will cancel the reference the caller was holding.
|
We then create an event sink and bind the callback procedure to it.
Creating a sink is straightforward, but note that unlike most other WMI objects, a sink is independent of the target system WMI provider.
% set sink [comobj WBemScripting.SWbemSink]
→ ::oo::Obj16727
% set sink_id [$sink -bind ::sink_callback]
→ 11
Binding the callback gives a handle that we store in
sink_id
. This is used later to unbind the callback.
All that remains to be done is to start the notifications using
ExecNotificationQueryAsync
.
% set wbem_services [comobj_object winmgmts:root/cimv2]
→ ::oo::Obj16731
% $wbem_services ExecNotificationQueryAsync $sink "SELECT * FROM \
Win32_ProcessStartTrace"
Any new processes would now result in our notification callback being invoked. As proof, we do a demonstration by firing up a couple of processes.
% exec cmd /c exit
% exec ipconfig
→
Windows IP Configuration
Ethernet adapter vEthernet (vEthernet (Real):
...Additional lines omitted...
We will hang around for a second and then cancel the notifications.
% after 1000 [list $sink Cancel]
→ after#2
% vwait ::notifications_ended
→ Process unsecapp.exe (PID 12188) started.
Process cmd.exe (PID 5072) started.
Notifications ended. Reason: Call cancelled
We see the output from the callback showing processes being started as well as the cancellation of the notifications.
All that’s left to do is the unbind our callback script using the handle that was returned to use earlier and clean up.
% $sink -unbind $sink_id
% comobj_destroy $sink $wbem_services
And we’re done.
10. WMI Configuration
There are several settings associated with the WMI installation on a system that control directory structure, enabling of certain features, resource limits and other operational parameters.
Here we only describe how these can be accessed and modified. For a description of what the individual parameters do, please refer to WMI Reference.
The individual settings are properties in certain system classes and are accessed in the same manner we have seen throughout this chapter.
10.1. WMI settings
Operational parameters for WMI are accessible through the singleton instance of class Win32_WMISetting.
% set settings [comobj_object winmgmts:Win32_WMISetting=@]
→ ::oo::Obj16745
% $settings -with Properties_ -iterate -cleanup prop {puts "[$prop Name] = [$prop \
Value]"}
→ ASPScriptDefaultNamespace = root\cimv2
ASPScriptEnabled =
AutorecoverMofs = {%windir%\system32\wbem\cimwin32.mof} {%windir%\system32\wb...
AutoStartWin9X =
BackupInterval =
BackupLastTime =
BuildVersion = 19041.1
Caption =
DatabaseDirectory = C:\WINDOWS\system32\wbem\repository
DatabaseMaxSize =
Description =
EnableAnonWin9xConnections =
EnableEvents = 1
EnableStartupHeapPreallocation = 0
HighThresholdOnClientObjects =
HighThresholdOnEvents = 20000000
InstallationDirectory = C:\WINDOWS\system32\wbem
LastStartupHeapPreallocation =
LoggingDirectory = C:\WINDOWS\system32\wbem\Logs\
LoggingLevel = 0
LowThresholdOnClientObjects =
LowThresholdOnEvents = 10000000
MaxLogFileSize = 65536
MaxWaitOnClientObjects =
MaxWaitOnEvents = 2000
MofSelfInstallDirectory =
SettingID =
% $settings -destroy
Note @ signifies the singleton instance |
10.2. WMI resource limits
WMI often deals with huge data sets, access to which can be
an expensive affair in terms
of system resources. Windows therefore places limits on the
resources used by WMI calls. These limits are defined through the
properties of the __ArbitratorConfiguration
singleton class in
the root
namespace.
% set arbitrator [comobj_object winmgmts://./root:__ArbitratorConfiguration=@]
→ ::oo::Obj16860
% $arbitrator -with Properties_ -iterate -cleanup prop {puts "[$prop Name] = \
[$prop Value]"}
→ OutstandingTasksPerUser = 30
OutstandingTasksTotal = 3000
PermanentSubscriptionsPerUser = 1000
PermanentSubscriptionsTotal = 10000
PollingInstructionsPerUser = 1000
PollingInstructionsTotal = 10000
PollingMemoryPerUser = 5000000
PollingMemoryTotal = 10000000
QuotaRetryCount = 10
QuotaRetryWaitInterval = 15000
TaskThreadsPerUser = 3
TaskThreadsTotal = 30
TemporarySubscriptionsPerUser = 1000
TemporarySubscriptionsTotal = 10000
TotalCacheDisk = 1048576
TotalCacheDiskPerTask = 51250
TotalCacheDiskPerUser = 102500
TotalCacheMemory = 10240
TotalCacheMemoryPerTask = 1024
TotalCacheMemoryPerUser = 2048
TotalUsers = 50
% $arbitrator -destroy
Note the class is in the root namespace |
These properties can be accessed just like properties for any other object.
11. A Final Word about WMI
WMI offers tremendous benefits for management of computer systems:
-
By design, it is flexible enough to support management of a wide variety of computing resources and applications.
-
Remote systems can be managed in practically the same fashion as the local system.
-
Some information, for example BIOS configuration, is only exposed through WMI on some versions of Windows.
-
Because of its generalized interfaces and introspection capabilities, new applications and resources can be managed with very few changes and without having to write custom code.
-
Use of WQL can reduce the amount of code that needs to written for complex queries.
However, there are a couple of caveats to all this goodness:
-
WMI comes with an associated processing cost. When available, equivalent native interfaces are often an order of magnitude more efficient in CPU utilization and memory usage. Many native interfaces even support remote invocation. Moreover, if a script results in too much CPU or memory usage, aborting it often does not help as the bulk of the processing is actually taking place in the WMI service process.
-
The reality of WMI often lags the theory. Earlier implementations were often unstable and of variable quality. Newer versions of Windows are much better in this regard. Nevertheless, you will sometimes run into situations where the implemented methods and properties do not reflect what is promised by runtime schema queries. Applications should always be prepared to deal with properties being unavailable or being null.
For this reason, use of WMI outside of management applications should be carefully considered if native interfaces are available for the same purpose.
12. References
- TUNS2002
-
Developing WMI Solutions, Tunstall, Cole, Addison-Wesley, 2002. Provides a comprehensive guide to the WMI architecture as well as practical development of WMI applications and providers in C++ and .Net.
- SCR2003
-
Microsoft Windows 2000 Scripting Guide, Microsoft Press, 2003. Provides a comprehensive introduction to WMI from a scripting perspective. The Visual Basic examples can be translated to Tcl in straightforward fashion.
- WMIREF
-
WMI Reference, Windows SDK documentation, http://msdn.microsoft.com/en-us/library/aa394582(v=vs.85).aspx.
- WQLREF
-
WQL Reference, Windows SDK documentation, http://msdn.microsoft.com/en-us/library/aa394552(v=vs.85).aspx.