The WMI Schema

Overview

Strictly speaking, the WMI schema is a logical grouping of classes and instances that constitute a particular managed environment. Thus, the WMI SDK comes, equipped with two schemas: the CIM schema and the Win32 schema. The former includes class and instance definitions for those managed entities that are common to all managed environments regardless of the underlying hardware and OS platform. As you may remember from Chapter 1, the CIM schema essentially covers two layers of CIM model: the core model and the common model. The Win32 schema contains classes and instances specific to Win32 environments; and, in CIM parlance, is said to be an extension model schema.

Extension schemas are the cornerstones of the WMI extensibility model. In fact, the only two things that are necessary to make WMI aware of vendorspecific extensions to a managed environment are an appropriate extension schema, which describes custom managed entities, and a provider, which is responsible for handling the underlying management data. Thus, there is a very clear parallel between the WMI schema and COM interface definitions. Both schemas and component interface specifications describe the capabilities of a particular component, or, in case of WMI, a provider. Both essentially represent a contract that a particular component or provider agrees to fulfill on behalf of its clients. Finally, the process of extending WMI is very similar to that of building a COM server—the first step always involves designing the interface or, in other words, devising the schema.

Contrary to what you may expect, this chapter is not about the extension schemas. It is about the schema in general. As opposed to the formal definition of the term "schema," I tend to use it to refer not to a particular set of WMI classes and instances, but rather to a general mechanism for defining the properties of an arbitrarily managed environment. Just like any serious COM developer must be ultimately familiar with the Interface Definition Language (IDL), anyone planning to become a WMI guru must thoroughly understand the WMI schema and have a working knowledge of the tools and facilities used to manage the schema elements. It is, therefore, a goal of this chapter to acquaint you with the Managed Object Format (MOF) Schema Definition Language that is used to describe the schema elements; to provide an overview of the schema management facilities that are available through the .NET System.Management namespace; and, finally, to discuss some of the alternate schema definitions and management techniques, such as XML representation for management information and the XML encoder component.

Managed Object Format Basics

The CIM specification prescribes that any management information be described in the Managed Object Format (MOF) language. MOF is a derivative of Interface Definition Language (IDL), and as such, it utilizes essentially the same syntactic structure. Just like IDL, MOF is declarative rather than procedural—its primary purpose is to define classes and instances, which represent the managed entities. Therefore, it is not possible to produce any executable code with MOF; the language simply does not include any syntactic elements that support procedural coding.

Compiling MOF

MOF is a compiled language. Typically, MOF class and instance definitions are saved in a text file subsequently processed by a MOF Compiler. The compiler parses the MOF file and adds the appropriate classes and objects to a CIM Repository. MOF definitions that make up such MOF file can be encoded in either Unicode or UTF-8. To avoid localization-related problems, all MOF files distributed with WMI are encoded in Unicode. In fact, it is encouraged that Unicode be used at all times for all MOF definitions.

The first two bytes of a Unicode MOF file must contain a valid signature of either U+FFFE or U+FEFF; these identify the byte ordering of a file as littleor big-endian respectively. All keywords and punctuation symbols used within MOF definitions are not locale-specific and must fall into the Unicode character range of U+0000 to U+007F. The identifiers, such as namespace, class, and property names must either start with an alphabetic character or an underscore (these must be the Unicode values of U+005F, U+0041 to U+005A, U+0061 to U+007A, U+0080 to U+FFEF). All the following characters of the identifier must be alphabetic characters, Arabic numerals (0 through 9), or underscores (previous set of Unicode ranges plus U+0030 to U+0039). All nonidentifier string values must be surrounded with the delimiter Unicode character U+0027 (double quote), and the characters within the quotation marks (U+0027) may fall into the Unicode character range of U+0001 to U+FFEF.

All these encoding rules may sound terribly complicated at first, but luckily, most Windows tools have built-in Unicode support. Thus even the ultra-popular notepad.exe has the appropriate provisions for creating and editing Unicode files in a manner that is nearly transparent to the user. However, if for some sentimental reason you insist on using an ASCII-based text editor you still will not have a problem. The MOF Compiler utility mofcomp.exe, which ships as part of WMI SDK, is perfectly capable of processing plain ASCII files just as well as Unicode files. mofcomp.exe is designed to run manually from the command line so that the name of the MOF file to be compiled is passed as a parameter. Thus, the following command will compile and install the regevent.mof file, which contains the definitions for all the classes and instances required by the Registry Provider:

mofcomp.exe C:WINNTSYSTEM32WBEM egevent.mof

The operation of the mofcomp.exe can be controlled via a number of command-line switches; these are listed in Table 6-1.

Table 6-1: mofcomp.exe Command-Line Switches

COMMAND-LINE SWITCH

DESCRIPTION

-autorecover

Instructs mofcomp.exe to add the file to the list of files automatically compiled during the repository recovery.

-check

Instructs mofcomp.exe to perform a syntax check only and print out the appropriate error messages. This switch cannot be used in combination with any other command-line option.

-N:

Instructs the compiler to load the contents of the file into a namespace specified by the parameter. By default, the contents of an arbitrary MOF file are loaded into the rootdefault namespace, unless instructed otherwise by either the command-line switch or the #pragma namespace directive contained in the MOF file. If both the command-line switch and the #pragma namespace are specified, the latter takes precedence.

-class:createonly

Instructs the compiler not to make any changes to classes that already exist in the CIM Repository. The compilation terminates when it encounters a class that already exists in the repository.

-class:forceupdate

Instructs mofcomp.exe to proceed with updating a class when conflicting child classes exist. If, for instance, a base class tries to add a qualifier that already exists on one of its child classes, the compiler resolves the conflict by removing the child class qualifier. Such an update only succeeds if a child class has no instances.

-class:safeupdate

Allows for updates of base classes that have child classes as long as there are no conflicts. For instance, this option will permit the compiler to add a new property to a base class even though there are existing child classes. Such an update only succeeds if a child class has no instances.

-class:updateonly

Instructs mofcomp.exe not to create any new classes. If this flag is specified, compilation terminates when the compiler encounters a class that does not already exist in the CIM Repository.

-instance:updateonly

Requests that the compiler not create any new instances. If this flag is specified, compilation terminates when the compiler encounters an instance that does not already exist in the CIM Repository.

-instance:createonly

Instructs the compiler not to make any changes to existing instances. If this flag is specified, compilation terminates when the compiler encounters an instance that already exists in the CIM Repository.

-B:

Instructs mofcomp.exe to create a binary version of the MOF file and save it into a file identified by the filename parameter. When this flag is specified, no changes are made to the CIM Repository.

-WMI

Instructs the compiler to perform a WMI syntax check when it is building binary MOF files for use by WDM device drivers. This switch must be used together with the –B switch. When the –WMI switch is supplied, mofcomp.exe invokes a separate binary MOF file checker after the binary version of MOF file is generated.

-U:

Specifies the name of the user to use when logging on to WMI.

-P:

Specifies the password to use when logging on to WMI.

-A:

Specifies the domain name to use when logging on to WMI.

-AMENDMENT:

Instructs the compiler to split the MOF file into language-neutral and language-specific versions. The language-neutral version has all MOF definitions with all localized qualifiers removed. The localized version, which is saved into a separate file with the MFL extension, contains the class definitions localized for the locale, which is identified by the locale parameter. The locale parameter is a hexadecimal string that represents the Windows LCID. For more information on WMI Localization, see Chapter 2.

-MOF:

This switch is used together with the –AMENDMENT switch and specifies the name of the MOF file that will contain language-neutral MOF definitions.

-MFL:

This switch is used together with the –AMENDMENT switch and specifies the name of the MOF file that will contain locale-specific MOF definitions.

The first phase of the MOF compilation is a syntax check. If any syntax errors are encountered, the compiler prints an error message on the console and terminates the compilation. To indicate the success or failure of a compilation when used in batch files, mofcomp.exe produces an exit code; the possible exit codes are shown in Table 6-2.

Table 6-2: mofcomp.exe Exit Codes

EXIT CODE

DESCRIPTION

0

Successful compilation.

1

Compiler is unable to connect to WMI. This may be because the version of mofcomp.exe and the version of the CIM Repository are not compatible. The same exit code will also be produced if the WMI service is not running.

2

Usage error. Indicates that mofcomp.exe has been invoked with command-line arguments that are not valid.

3

A MOF syntax error has been detected.

There are cases when a MOF file parses correctly, but some of the repository updates, warranted by the MOF code, conflict with command-line arguments passed to mofcomp.exe. For instance, if the repository already contains the classes and instances that are generated as a result of a compilation of regevent.mof mentioned earlier, attempting to invoke a compiler for this file with the –class:createonly switch will result in error. Let us see how this works:

mofcomp.exe -class:createonly C:WINNTSYSTEM32WBEM egevent.mof

The command above will produce the following message on the console:

Parsing MOF file: C:winntsystem32wbem egevent.mof MOF file has been successfully parsed Storing data in the repository... An error occurred while processing item 1 defined on lines 4 - 6 in file C:winntsystem32wbem egevent.mof: Error Number: 0x80041019, Facility: WMI, Description: Object or property already exists Compiler returned error 0x80041001

Contrary to what you may expect, the exit code produced by the compiler will not fall into the range of values listed in Table 6-2. Instead, a WMI error code will be returned. In this particular case, mofcomp.exe will output the exit code of 0x80041001 (WBEM_E_FAILED).

The compiler has no built-in rollback capabilities. If a compilation aborts for whatever reason, the repository may be left in an undefined state. Thus, in the example above, any classes or instances defined in the MOF file prior to the point where the compiler detected a conflict would be created and stored in the repository. In such case, the repository may have to be rebuilt from scratch. To accomplish this, the WMI service (winmgmt.exe) has to be stopped and then run manually to restore the Repository from the latest backup file:

winmgmt.exe /restore backup.rep

Note that those MOF files, which were loaded after the last backup was taken, might not be restored correctly. As you may remember, only the files on the autorecover list are automatically recompiled whenever the repository is rebuilt. To place a file on the autorecover list, either invoke mofcomp.exe with the –autorecover switch, or specify #pragma autorecover in the MOF file itself.

As a convenient alternative to command-line compilation, there is a graphical MOF Compiler wizard that is accessible through the CIM Studio. This simple tool, shown in Figure 6-1, can be used to specify the compilation options in a hassle-free fashion, so you do not have to remember the usage syntax of the command-line utility.

Figure 6-1: MOF Compiler Wizard

The MOF Compiler Wizard is not a separate compiler but rather a graphical interface to the same command-line mofcomp.exe.

MOF Intrinsic Data Types

Just like IDL, MOF offers a wide range of intrinsic data types for defining the properties of managed classes and instances. Table 6-3 contains a complete list of these data types.

Table 6-3: MOF Intrinsic Data Types

DATA TYPE

DESCRIPTION

uint8

Unsigned 8-bit integer.

sint8

Signed 8-bit integer.

uint16

Unsigned 16-bit integer.

sint16

Signed 16-bit integer.

uint32

Unsigned 32-bit integer.

sint32

Signed 32-bit integer.

uint64

Unsigned 64-bit integer, encoded as string that is compliant with ANSI C decimal/hexadecimal number encoding format.

sint64

Signed 64-bit integer, encoded as string that is compliant with ANSI C decimal/hexadecimal number encoding format.

real32

IEEE 4-byte floating-point number.

real64

IEEE 8-byte floating-point number.

boolean

Boolean logical TRUE or FALSE value.

char16

Single 16-bit Unicode character in Universal Character Set-2 (UCS-2) format.

string

Unicode character string.

datetime

Fixed length date-time string that is compliant with the DMTF date/time format. Example:

yyyymmddhhmmss.mmmmmmsutc

where:

yyyy—4-digit year

mm—2-digit month

dd—2-digit day

hh—2-digit hour (24-hour clock)

mm—2-digit minute

ss—2-digit second

mmmmmm—6-digit number of milliseconds

s—+ or – sign of the offset from the Universal Time Coordinates (UTC)

utc—Offset from UTC in minutes

void

Used in method declarations to indicate that a method has no return value. Cannot be used for property declarations.

Additional type semantics can be expressed via qualifiers. Thus, rather than treating the popular management types such as Gauge and Counter as separate intrinsic types, the MOF specification prescribes that the type usage be expressed via appropriate qualifiers at a time of property declaration:

class TypeSemanticsExample { [counter] uint32 SamlpeCounter; [gauge] uint32 SampleGauge; };

This approach is very logical because both Gauge and Counter are just simple unsigned integers with clearly defined semantic properties.

MOF Embedded Objects

In certain cases, instances of management classes can be contained within other objects. Such contained objects are often referred to as embedded objects. MOF syntax fully supports declarations of embedded classes and instances.

Class properties, referring to embedded objects, can be declared as strongly or weakly typed. A property that references an embedded object in a strongly typed fashion has a data type that corresponds to the class that is being referred to:

class InnerClass { uint32 prop1; uint32 prop2; }; class OuterClass { InnerClass embedProp1; InnerClass embedProp2; };

Here, the properties embedProp1 and embedProp2 of class OuterClass reference the embedded objects of type InnerClass.

A weakly typed embedded object is declared using the object keyword in place of the class name:

class OuterClass { object embedProp1; object embedProp2; };

Here, the properties embedProp1 and embedProp2 of class OuterClass refer to an embedded object of an unspecified class.

Note that at the time of this writing, MOF does not support embedding objects within other embedded objects. In other words, only one level of embedding is allowed.

MOF References

Those interobject relationships that do not fall into the category of containment are expressed in MOF using the ref data type. A property of the ref data type essentially contains a string that represents an object path—a symbolic or "soft" reference to an object that exists independently somewhere in the CIM Repository (see Chapter 1 for an exhaustive explanation of object path syntax). In fact, this is how all WMI association classes are implemented. As you may remember, an association object represents a link between two arbitrary instances of WMI classes. Thus, an association class would typically contain at least two properties, each of an appropriate ref data type, so that each property references a single WMI object.

Just as it is the case with embedded objects, MOF supports strongly and weakly typed references. A strongly typed reference points to an object of a specific class, which in MOF syntax is expressed via a combination of the ref keyword and an appropriate class name. Thus, to declare a class property as a reference to class Foo (assuming that such class Foo exists) you may use the following MOF code:

class Bar { // refClassFoo is a strongly typed reference Foo ref refClassFoo; ... };

A weakly typed reference points to an object of an unspecified class. In MOF, this is expressed via a combination of the ref and object keywords. For example, the following code declares a class property as a weak reference, which may point to an object of any class:

class Bar { // refAnyClass is a weakly typed reference object ref refAnyClass; ... };

Very often, especially when it comes to defining association classes, properties of reference type act as object keys. In such case, WMI uses the string value of the reference property rather than the contents of the referenced object to identify an instance.

MOF Arrays

In addition to the intrinsic data types, embedded object types, and references, MOF syntax supports different types of arrays. An array can be declared using notation similar to that of ANSI C—square brackets following the property identifier. An array can be of fixed or variable length. Fixed-length arrays are identified by an unsigned integer constant within the square brackets that represents the total length of the array. If the length specification is omitted, an array is assumed to be of variable length:

uint32[10] Array1; // 10-element uint32 array uint32[] Array2; // variable-length uint32 array

The current CIM standard supports only one-dimensional arrays, although it is possible that support for n-dimensional array types will be added in the near future.

Semantically, arrays can act as bags and ordered or indexed lists. The array type is designated by the ARRAYTYPE qualifier, which may take the values of Bag, Ordered, or Indexed to indicate the respective semantics of the array. In case the ARRAYTYPE qualifier is omitted, an array is considered to be of type Bag—an unordered list of values that allows duplicates. By definition, the order of elements is not defined for a bag-type array, meaning that accessing such an array twice with the same index may not yield the same element. By the same token, there is no guarantee that the array elements will always be returned in the same order when iterating through the array.

An ordered list array is really just a special case of a bag. It also allows for duplicate values, but, unlike the bag, it preserves the order of elements. Thus, if no elements are added or deleted, an array access through the same index will always yield the same element. The same applies to iterating through an array—elements are always returned in the same order.

Finally, an indexed array always maintains a correlation between individual element values and their positions so that accessing an array via an index or iterating through it will always yield the same elements in the same order. Elements of an indexed array can be overwritten but cannot be deleted.

The following code snippet illustrates how different types of arrays are declared using the ARRAYTYPE attribute:

class ArrayExample { // Bag array - implicit declaration uint32 BagArray1[]; // Another bag array - explicit declaration [ArrayType("Bag")] uint32 BagArray2[]; // Ordered list array [ArrayType("Ordered")] uint32 OrderedArray[]; // Indexed Array [ArrayType("Indexed")] uint32 IndexedArray[]; };

Curiously, the CIM Documentation states that only the arrays of basic types are valid. WMI, however, supports arrays of embedded objects and references just fine. Thus, the following MOF code should compile without any problems:

class ComplexArrayExample { // Array of references object ref RefArray[]; // Array of embedded objects of class Foo Foo EmbedObjArray[]; };

The only tricky thing about reference and object arrays is providing the default initialization values. Ordinarily, an array of basic types can be initialized as follows:

// initialized uint32 array uint32 IntArray[] = {1,2,3,4,5,6,7,8,9,10}; // initialized string array string StringArray[] = {"first", "second", "third"};

Here the initialization is accomplished by supplying a list of integral values within the curly brackets as part of the array declaration. The problem with object and reference types, however, is that it is unclear how to represent instances of such types within the initialization list. As you may remember, references are really just strings that represent the appropriate object paths. Therefore, an arbitrary array of references to objects of type Win32_Process, can be initialized as follows:

Win32_Process ref RefArray[] = { "Win32_Process.Handle=100", "Win32_Process.Handle=101"};

Here, the initialization list contains a sequence of quoted object path strings that refer to valid instances of the Win32_Process class.

The situation is similar with embedded object arrays, although the initialization syntax is just a bit more complex. Since objects are not strings and cannot really be represented as strings, a special keyword, instance of, has to be used to convert an arbitrary object path into an appropriate object. Thus, the following code initializes an array of embedded Win32_Process objects:

#pragma namespace("\\.\Root\CIMV2") ... Win32_Process EmbedObjArray[] = { instance of Win32_Process{Handle="100";}, instance of Win32_Process{Handle="101";} };

Be careful when you are supplying default initialization values for arrays of embedded objects. Since references to objects used as initialization values are resolved at the time of compilation, these instances have to be accessible from the namespace into which a given MOF code is being compiled. In the preceding example, because Win32_Process objects used to initialize the array reside in the rootCIMV2 namespace, the pragma namespace compiler directive is added to ensure that the code is compiled into the same namespace.

Although MOF arrays are homogeneous and cannot contain elements of different types at the same time, it is entirely valid to construct an array of objects that belong to completely unrelated classes. To achieve this, an array has to be declared of type object:

object ObjArray[] = { instance of Win32_Process{Handle="100";}, instance of Win32_Service{Name=" WinMgmt";} };

Similarly, if an array is declared to be of a specific type, it may contain objects that belong not only to this class, but also to any of its subclasses. Thus, an array of CIM_Service objects may house instances of Win32_Service and Win32_SystemDriver, as shown here:

CIM_Service ObjArray[] = { instance of Win32_SystemDriver{Name=" atapi";}, instance of Win32_Service{Name=" WinMgmt";} };

Class Declarations

A management class can be declared using the MOF class keyword followed by the name of the class and an optional list of properties and methods enclosed in curly brackets. Thus, the most basic class declaration may look like the following:

class SimpleClass { };

The name of the class is case-insensitive and must start with a letter. The rest of the class name may be composed of letters, digits, and underscores. Note that no leading or trailing underscores are allowed, although embedded underscores are used quite often. It is, in fact, a common practice to compose a class identifier from two components—the name of the schema, which the class belongs to, and the actual class name—separated by an underscore.

The CIM model relies heavily on class inheritance and it is conceivable that a typical extension schema would also enforce some kind of hierarchical structure by deriving specialized classes from common ancestors. Declaring a derived class in MOF is extremely easy; you just define a subclass by including the name of a base class, preceded by a colon, immediately following the class name:

class MyManagedElement : CIM_ManagedSystemElement { };

Here a new class MyManagedElement is declared as a subclass of CIM_ManagedSystemElement. Note that a class, used as a base, must either be already registered in the namespace where the derived class is being defined, or it must precede the declaration of the derived class in the same MOF file.

Class definitions may optionally contain property and method declarations. A property declaration consists of a data type, a property identifier, and an optional default value, followed by a semicolon. As discussed above, array property declarations must include square brackets and an optional array size designation as part of the property identifier. The rules for choosing the property identifiers are the same as those for class names—an identifier is case insensitive and must start with a letter, followed by any number of letters, digits, and underscores. The following code snippet illustrates how class properties are defined:

class PropertyDeclarationExample { uint32 IntProperty; // uint32 property string StrProperty; // string property uint32 ArrProperty[]; // variable-length array property };

A property may optionally be assigned a default initialization value. To assign a property of basic type a default value, simply follow the property identifier with an equal sign and the appropriate representation of the value:

// default uint32 value uint32 IntProperty = 128; // default string value string StrProperty = "STRING"; // default boolean value boolean BoolProperty = true; // default object value object ObjProperty = instance of Win32_Process{Handle=100;}; // default reference value object ref RefProperty = "Win32_Process.Handle=100";

As discussed earlier, setting the default value for array properties requires specialized syntax:

string ArrProperty[] = {"ONE", "TWO", "THREE"};

Whenever a class is instantiated, its properties are automatically assigned the appropriate default values, unless the instance declaration explicitly overrides the default value specification. Instance declarations will be covered in the "Instance Declarations" section later in this chapter.

Besides properties, a class definition may include method declarations. A method declaration consists of a method identifier, a return type, and a possibly empty parameter list enclosed in parentheses. Both the return type and the method parameters can be of any basic MOF data type, although arrays constitute an exception. Although it is entirely legal to use an array as a method parameter, declaring a method that returns an array is not supported. Thus, a method return type should always be one of the integral, non-array data types. If a method does not return a value, a special keyword, void, can be used as the return type.

The following MOF code is an example of method declaration:

// parameterized method returning uint32 uint32 Method1(uint32 parm1, string parm2); // parameterless method not returning a value void Method2();

Although the preceding declaration fails to do so, all method parameters must be marked as either input or output using the In or Out qualifiers. Thus, the declaration for Method1 should be rewritten as follows:

uint32 Method1([In]uint32 parm1, [Out]string parm2);

Here, parm1 is designated as input and parm2 is designated as output. In certain cases, a parameter may act as both input and output. As the following example shows, this can be achieved in a couple of different ways:

uint32 Method1([In,Out]uint32 parm1); uint32 Method2([In]uint32 parm1, [Out]uint32 parm1);

The declaration for Method1 is fairly self-explanatory—a parameter is simply marked with both In and Out qualifiers to designate it as input/output. Method2, however, is a bit trickier. Here, the same parameter parm1 is listed twice: once with the In qualifier and once with Out. This does not mean that the method has two parameters: it just indicates that parm1 is used for both input and output. Note that if separate input and output declarations for a single parameter are used, all aspects of such declarations must match exactly, including the identifier, data type, and number and type of all other qualifiers. The other requirement is that the ID qualifier is explicitly supplied for both input and output declarations. Normally, the ID qualifier, which is used to uniquely identify each parameter's position within the method parameter list, is automatically assigned by the compiler. However, when you are using separate input and output parameter declaration, it is your responsibility to mark the parameters appropriately. For instance, you would rewrite the declaration for Method2 shown in the preceding code snippet as follows:

uint32 Method2([In, ID(0)]uint32 parm1, [Out, ID(0)]uint32 parm1);

Here the ID qualifier with the value of zero (first parameter) is explicitly added to both input and output parameter definitions, stating that the same parameter is being defined in both cases.

Instance Declarations

An instance of a class is declared using the instance of keyword followed by the name of the class, curly brackets that enclose an optional list of property value assignments, and a semicolon. Thus, an instance of an arbitrary class Foo can be created as follows:

instance of Foo { };

When an instance is created, it contains all properties of its immediate class as well as all superclass properties. Ordinarily, only those properties, which have default values assigned within the class declaration, are initialized at a time of instance creation. However, you can supply an initialization value or even override the default initialization value within the instance of statement. Consider the following example:

class SampleClass { [key] uint32 Prop1; boolean Prop2 = true; string Prop3[] = {"ONE", "TWO", "THREE"}; }; instance of SampleClass { Prop1 = 128; Prop3 = {"UNO", "DUE", "TRE", "QUATTRO"}; };

Here the class SampleClass has three properties: Prop1, Prop2, and Prop3, where the latter is assigned a default initialization value within the class declaration. At a time of instance creation, Prop1 is assigned a new integer value of 128, Prop2 gets to keep its default initialization value of true, and the default value of Prop3 is overridden.

While it is legal to supply a new value or override a default initialization value at a time of instance creation, the value assignment should conform to the data type of the property to which it is being assigned. For instance, if you attempt to initialize an integer property with a string value, mofcomp.exe will flag that as an error. Thus, if you rewrite the preceding example as follows, the compiler will abort the compilation and spit out an error message:

instance of SampleClass { Prop1 = "ONE TWENTY EIGHT"; Prop3 = {"UNO", "DUE", "TRE", "QUATTRO"}; };

If you actually try to compile this code, you are likely to see output, similar to the following:

An error occurred while creating object 2 defined on lines 10 - 13: 0X80041005 The value specified for property 'Prop1' conflicts with the declaration of the property. Compiler returned error 0x80041001

Properties do not have to be assigned a value during instance creation. Even when certain properties do not have a default initialization value, it is perfectly legal to create an instance without initializing such properties. Uninitialized properties would simply contain a special NULL value, which essentially indicates the absence of value. There is, however, one exception. Unless a class is marked as a singleton, it must have a key property, which has to be assigned a unique value for every instance of the class. As you may remember, a property is designated as a key by attributing it with the key qualifier within the class declaration. Thus, in the example above, Prop1 is a key property, and failure to supply a unique value for it when creating an instance of SampleClass will cause the compiler to abort the compilation complaining about an illegal NULL value.

System properties of a class constitute another special case. As you may remember, these properties cannot be explicitly declared within a class definition. Instead, WMI automatically adds them whenever a class is registered, and provides the appropriate initialization values when a class is instantiated. Consequently, you cannot assign system properties a value when you are creating instances. If you attempt to do so, this action will be flagged as an error by the compiler.

Instance Aliasing

When initializing object references, you often need to type lengthy object paths; this process is not only tedious but also error prone. Fortunately, MOF offers a convenient feature, called instance aliasing, which is designed to simplify the process of reference initialization. An instance alias is just a symbolic reference to an instance declared somewhere else within the same MOF file. You can create an instance alias by adding a special keyword, as, followed by the alias name, to the instance declaration:

instance of SampleClass as $SampleInstanceAlias { ... };

The rules for choosing an alias name are the same as those for the class and instance identifier with one exception—an alias name always has to be prefixed with a dollar sign.

Once an alias is declared, you can use it in place of an explicit object path to initialize a reference. Consider the following example:

class Foo { [key] uint32 KeyProp; string OtherProp; }; class FooRef { [key] uint32 KeyProp; Foo ref RefProp; }; instance of Foo as $FooInstance { KeyProp = 100; }; instance of FooRef { KeyProp = 200; RefProp = $FooInstance; };

Here, an alias $FooInstance is established for an instance of class Foo. This alias is then used to initialize a reference property RefProp of the instance of class FooRef. This reference property can also be initialized with a conventional object path as follows:

instance of FooRef { KeyProp = 200; RefProp = "Foo.KeyProp=100"; };

However, using aliases is much more convenient, especially if a certain object is referred to more than once throughout the MOF file.

An alias can be declared not only for instances, but for classes as well. In fact, the following code is perfectly legal and will compile just fine:

class Foo as $FooClass { [key] uint32 KeyProp; string OtherProp; }; class FooRef { [key] uint32 KeyProp; Foo ref RefProp; }; instance of FooRef { KeyProp = 200; RefProp = $FooClass; };

Conceptually, an alias can be viewed as an immutable string variable that is initialized to a path of the object being aliased. Therefore, it is valid to use an alias to initialize a string rather than a reference property:

class Foo as $FooClass { [key] uint32 KeyProp; string OtherProp; }; class FooRef { [key] uint32 KeyProp; string StrProp; }; instance of FooRef { KeyProp = 200; StrProp = $FooClass; };

When the FooRef object is instantiated, its StrProp property will be set to a string value of "\. ootdefault:Foo", assuming that the MOF code is loaded into rootdefault namespace.

Note that aliases are only valid within the scope of a single MOF file. Thus, it is impossible to alias a class or instance that is not defined within the same compilation unit as an alias. It is, however, legal to use forward referencing—an alias can be referenced at a point in a MOF file that precedes the alias declaration.

Qualifiers

Qualifiers are special attributes that can be used to associate certain semantics with managed classes, instances, methods, and properties. MOF is a fairly generic declarative language and often a class, method, or property definition alone does not convey enough information about the purpose or behavior of a particular managed entity.

Recall the example of the Counter or Gauge properties, presented earlier in this chapter. A definition for such a property would have a data type of uint32, which alone is not sufficient to express its behavioral characteristics. Alternatively, a naming convention may be used to provide a hint regarding the purpose of the property. The problem, however, is that the standard does not prescribe any definitive property naming conventions (besides the identifier naming rules, outlined earlier in the "Compiling MOF" section), so that making any assumptions regarding the property purpose based solely on its name is, at best, unreliable. The solution is to decorate such a property with some additional attributes that fully describe the intentions of the designer. Thus, the Counter or Gauge qualifiers act as metadata and express the special semantics associated with an arbitrary property.

All qualifiers are broadly divided into four categories: meta, standard, optional, and user-defined. Meta-qualifiers are used to clarify the usage of a particular class or property, declared with MOF. For instance, all association classes must be marked with the Association qualifier to indicate the intended usage. Currently, there are only two meta-qualifiers, defined by the CIM standard: Association and Indication. These qualifiers must be declared for those CIM classes that are used as associations or indications, respectively.

Standard qualifiers are those that all CIM-compliant implementations are required to support. This is the largest category, and it includes most of the qualifiers described so far in this book. The Counter and Gauge qualifiers just described, are the examples of standard qualifiers. The Abstract qualifier, used to mark CIM classes as abstract, is another example of a standard qualifier. The usage of standard qualifiers is governed by a set of rules that outline the applicability of qualifiers to certain CIM model constructs as well as other constraints. The complete list of standard qualifiers along with extensive usage notes can be found in the CIM Specification Version 2.2 (www.dmtf.org).

Optional qualifiers are designed to account for various situations that are not common to all CIM-compliant implementations. Such implementations, therefore, are not required to handle optional qualifiers and, for the most part, simply ignore them. In fact, the idea behind optional qualifiers is to prevent the proliferation of random user-defined qualifiers, which address some fairly common, but not very generic aspects of CIM modeling. An example of such a qualifier is Expensive, which is used to indicate that accessing an arbitrary property or class may involve substantial computations. Again, the complete list of currently defined optional qualifiers is a part of CIM Specification Version 2.2.

Finally, a CIM designer is free to define custom qualifiers, although, for obvious reasons, it is not recommended. Declaring a qualifier involves determining certain characteristics that a qualifier should possess. First, just like properties, qualifiers have a data type. Table 6-4 presents a complete list of the CIM data types that can be used with qualifiers. Note that homogeneous arrays of the following types are also supported as qualifier data types.

Table 6-4: Qualifier Data Types

CIM DATA TYPE

DESCRIPTION

string

CIM string

uint16

Unsigned 16-bit integer

uint32

Unsigned 32-bit integer

sint32

Signed 32-bit integer

uint64

Unsigned 64-bit integer

sint64

Signed 64-bit integer

real32

IEEE 4-byte floating-point number

real64

IEEE 8-byte floating-point number

boolean

Boolean

Besides the data type, every qualifier has a clearly defined scope. Scope indicates to which elements of the schema the qualifier is applicable. A scope specification may identify a qualifier as relevant to classes, properties, methods, associations, and indications.

Finally, qualifiers have flavors that describe certain qualifier characteristics, such as localizability, propagation, and overriding rules. Qualifier flavors are described in detail in Chapter 2 (see Table 2-7).

Once all features of a qualifier are determined, it can be declared using the qualifier keyword. For instance, the following code snippet declares a qualifier, called MyQualifier:

qualifier MyQualifier :string = null, scope(class), flavor(DisableOverride);

This qualifier, which has a data type of string, applies to classes only and cannot be overridden by subclasses. Note that the qualifier is assigned a default value of null. This ability to specify a default qualifier value may come in very handy because MOF syntax allows a qualifier to be specified without an explicit value. For instance, the Association qualifier is usually used as follows:

[Association] class FooAssoc { ... };

Since, this qualifier is Boolean, its value is automatically assumed to be TRUE. A similar principle applies to qualifiers of other data types—numeric and string qualifiers are implicitly set to NULL and arrays are, by default, empty.

Although the ability to declare qualifiers as well as the qualifier keyword is part of the CIM standard, they are not supported under the current release of WMI. If you attempt to compile a MOF file, which contains a qualifier declaration, you are likely to see the following error message:

error SYNTAX 0X00af: CIM V2.2 feature not currently supported for qualifier declaration

This, however, does not mean that custom qualifiers cannot be declared in WMI. In fact, creating a WMI user-defined qualifier is surprisingly easy—all you have to do is use the qualifier within a MOF declaration in the same way you would use a standard qualifier. WMI is smart enough to figure out the qualifier scope and the data type based on the placement of the qualifier within a MOF construct and the specified qualifier value.

Normally, a qualifier is enclosed in square brackets and placed right before the MOF keyword or identifier to which it applies. If multiple qualifiers apply, you can place them within a single set of square braces with a comma acting as a separator. Thus, the following code creates and utilizes a Boolean qualifier, MyQualifier, which applies to classes:

[MyQualifier] class Foo { ... };

Here, the value for the qualifier is not supplied explicitly, therefore, WMI makes an assumption that MyQualifier is a Boolean, set to TRUE. Optionally, a qualifier can be declared with an explicit value, as shown here:

[MyStringQualifier("CUSTOM QUALIFIER")] //creates string qualifier class Foo { ... }; [MyIntQualifier(1024)] //creates integer qualifier class Foo { ... };

The data type of a qualifier is determined based on the data type of the value, which is supplied as part of the qualifier declaration. Thus, in the preceding example, MyStringQualifier is of string data type, while MyIntQualifier is sint32.

Similarly, a qualifier can be declared as an array of values. In this case, the list of values is enclosed in curly brackets. The underlying data type is determined based on the data type of the supplied values, which implies that all of these values should be of the same type. Thus, the following fragment declares qualifiers that are typed as string and integer arrays:

//creates string array qualifier [MyStringArrayQualifier{"VALUE 1", "VALUE 2", "VALUE 3"}] class Foo { ... }; //creates integer array qualifier [MyIntArrayQualifier{1024, 2048, 4096}] class Foo { ... };

In the absence of formal qualifier declarations, required qualifier flavors must be included in the qualifier specification as follows:

[MyFlavoredQualifier("VALUE") : DisableOverride ToSubclass] class Foo { ... };

Note that it is legal to include multiple qualifier flavors in the specification, in which case the flavor identifiers must be separated by one or more blanks.

Not having to declare qualifiers explicitly in WMI creates a bit of a problem. It is entirely possible for a user-defined qualifier to collide with one of the standard or optional WMI qualifiers and cause undesirable side effects. Unfortunately, WMI makes such problems hard to detect because a qualifier, intended as user-defined, will most likely be interpreted as an existing one. It is, therefore, recommended that you prefix custom qualifier names with the name of the schema of which the qualifier is a part.

Compiler Directives

Although most of the mofcomp.exe behavior can be controlled via commandline switches, the CIM standard provides for an implementation-independent way of supplying the directives for compilation. Such directives, referred to as preprocessor commands or pragmas, are introduced into the MOF file by means of the pragma keyword, prefixed by a hash mark (#). Most pragmas may only appear at the beginning of the MOF file, preceding all other MOF constructs, but there are exceptions. Thus, pragma namespace may be specified at any point within a MOF compilation unit. The pragma directive has the following syntax:

#pragma[()]

Table 6-5 lists all available pragma commands.

Table 6-5: MOF Pragma Commands

COMMAND

DESCRIPTION

amendment(locale)

Instructs a MOF compiler to separate the MOF file into the locale-neutral and local-specific portions. The locale value is an identifier of a locale that is used to create a language-specific MOF file. For details of WMI Localization, see

autorecover

Instructs the compiler to add the MOF file to the list of files that are automatically compiled during the repository recovery. This autorecover file list is stored in the system registry under the key HKLMSoftwareMicrosoftWBEMCIMOM Autorecover MOFS.

classflags(flag)

Affects the way WMI classes are created and updated. The flag value may be one of the following: createonly, forceupdate, safeupdate, or updateonly. The effect of each of these flags is described in detail in Table 6-1.

deleteclass(name,failflag)

Instructs the compiler to delete a class, specified by the name parameter. The failflag controls the behavior of the compiler in case the specified class does not exist in the repository. If set to FAIL, the compiler aborts the compilation and prints an error message. If set to NOFAIL, the compilation continues. Note, if a target class has instances, these instances are deleted along with the class definition.

include(moffile)

Causes the compiler to include the contents of the MOF file, specified by the moffile parameter, into the current compilation unit.

instanceflags(flag)

Affects the way WMI instances are created and updated. The flag value may be one of the following: createonly, or updateonly. The effect of each of these flags is described in detail in Table 6-1.

namespace(path)

Instructs the compiler to save classes and instances into the namespace specified by the path parameter. By default, all classes and instances are saved into the rootdefault namespace. Note, if this directive appears in the middle of a MOF file, it does not affect the classes and instances that are defined prior to the command.

Interestingly, all of these commands, with the exception of include and namespace, are WMI specific and are not a part of CIM standard. Conversely, the CIM Specification defines a few other pragmas, which do not seem to be supported under WMI. For a complete list of standard pragma commands, see CIM Specification Version 2.2 (www.dmtf.org).

Comments

Similarly to most other computer languages, MOF allows you to specify comments. Comments may appear anywhere within a MOF compilation unit and are introduced via either a pair of leading forward slashes (//), or by a pair of matching /* and */ sequences.

A // comment spans a single line in a MOF file and does not have to be terminated explicitly. Instead, a carriage return provides an implicit termination. Thus, the following is an example of a single-line comment:

// This is a single-line comment.

A pair of /* and */ sequences is used to introduce a multiline comment. The following text illustrates how multiple lines are commented at once:

/* This is a first line of a multi-line comment. This is a second line of a multi-line comment. This is a last line of a multi-line comment. */

WMI Schema and System Management Namespace

Although mofcomp.exe is the ultimate tool for manipulating the WMI Schema, sometimes you may want to carry out some schema modifications programmatically. Perhaps, the main reason for programmatic manipulation of the schema is the relative complexity of the MOF syntax, which is often enough to make schema administration a challenge for less experienced or less sophisticated system managers and administrators. Instead, you can develop a user-friendly GUI interface to obtain greater control over the schema extension and modification. This would allow you to carry out the required administrative tasks in a graphical fashion, thus, obviating the need to learn the MOF grammar.

One such GUI tool, the already familiar CIM Studio, comes as a part of WMI SDK. CIM Studio offers a convenient and simple graphical interface, which makes the task of managing WMI classes and instances very easy and straightforward. Nevertheless, you may find that, under certain circumstances, neither CIM Studio nor mofcomp.exe is an ideal solution for schema management.

For instance, some management applications may, restrict the schema management activities such that only a subset of operations is allowed. Others may simply require you to build a schema administration tool into the applications UI. Under these conditions, the developers will need to program directly against the schema management API offered by WMI. Fortunately, the System.Management namespace comes well equipped with features that effectively hide the low-level details of schema manipulation APIs while working in concert with the rest of the .NET system management services.

Extracting MOF Definitions

You will often need to extract an arbitrary MOF definition from the CIM Repository. This may have to be done for several reasons. For example, when you extend the schema, sometimes you may find it easier to extract the definition of an existing class, alter it, and load it back into the repository, rather than, say, define a class from scratch. Alternatively, you may want to implement a custom backup tool, which would allow you to create logical, classor instance-level backups of the schema. Finally, you may just be curious to find out how the definition of a particular class looks.

There are a few ways you can go about extracting the MOF code for a given instance or class. Perhaps, the easiest is to use the MOF Generation Wizard, accessible though the CIM Studio GUI. A bit more complicated approach involves using the wbemdump.exe command-line utility. This utility is a multipurpose tool, capable of not only displaying the class and instance information, but also running queries and executing methods. In order to print out a MOF definition of an arbitrary class, you have to invoke wbemdump.exe with the /M switch, the namespace path, and the name of the class. Thus, the following command will dump the MOF source for the Win32_Process class in the rootCIMV2 namespace:

wbemdump.exe /m rootCIMV2 Win32_Process

You can find the complete description of the functionality afforded by wbemdump.exe can in WMI SDK documentation.

Finally, the most flexible way to extract the MOF source from the CIM Repository is programmatically using the WMI API. The COM API offers two different ways to obtain the MOF text for a class or instance: by using the IWbemClassObject::GetObjectText method or the IWbemObjectTextSrc interface.

The GetObjectText method of the IWbemClassObject interface has the following definition:

HRESULT IWbemClassObject::GetObjectText( LONG lFlags, BSTR *pstrObjectText );

The lFlags parameter controls several aspects of MOF generation. There are two allowable values for this parameter: WBEM_FLAG_NO_FLAVORS and WBEM_FLAG_NO_SEPARATORS. The former requests that the returned MOF text not include the qualifier propagation or flavor information. The latter suppresses the trailing semicolon after the class or instance declarations. The resulting MOF text is placed into a location, pointed to by pstrObjectText parameter, which must be initialized to NULL prior to calling the method.

In the System.Management namespace, the GetObjectText method is mapped to the GetText method of the ManagementBaseObject type. This method has a single parameter of type enumeration TextFormat. Currently, the TextFormat enumeration contains a single member TextFormat.Mof, which instructs the method to return a MOF-formatted representation of a WMI object. [1]

Retrieving the MOF source with the GetText object is very straightforward. For instance, the following code, extracts and prints on the console the MOF declaration of WMI class Win32_Process:

ManagementClass mc = new ManagementClass("Win32_Process"); Console.WriteLine(mc.GetText(TextFormat.Mof));

Extracting instance declarations is also extremely simple:

ManagementObject mo = new ManagementObject("Win32_Process.Handle=100"); Console.WriteLine(mo.GetText(TextFormat.Mof));

If you examine the output of these code fragments, you will notice that all the qualifiers are listed along with their respective flavor and propagation information, and all class and instance declarations are terminated with a semicolon. When you look at the disassembly of the System.Management.dll more closely, you will see that the GetText implementation unconditionally sets the lFlags parameter of the IWbemClassObject::GetObjectText method to zero, thus making it impossible to refine the aspects of MOF generation mentioned previously.

At this point, you may be asking, "What is the purpose of the GetText method's TextFormat parameter?" Since the TextFormat enumeration contains a single member, Mof, you cannot produce a textual representation of a WMI object in a format other than MOF. Moreover, if you look at the disassembly of GetText, you will definitely notice that the TextFormat parameter is ignored altogether. Again, this is easy to explain because the underlying IWbemClassObject::GetObjectText method does not offer you a choice of output formats. So why have a parameter that seemingly serves no purpose?

The answer is easy to guess. The parameter is there for the sake of future expansion. Not just hypothetical future expansion, but something, which, hopefully, will be a part of System.Management namespace in the very near future. Starting with Windows XP, WMI offers an alternative interface for generating textual representations of management objects. The interface, IWbemObjectTextSrc, is designed specifically to retrieve and format the declarations of WMI entities, and as a result it allows for more than one output format.

Again, the question is what other formats or encoding standards can be used to represent the structure of management information? After all, MOF is designed specifically for describing the managed entities and their interrelationships, but what are the alternatives? Today, when just about everything is encoded in XML, it would be surprising if somebody did not at least try to come up with a suitable XML representation for CIM objects and classes. Indeed, the first draft of the specification, entitled "XML As a Representation for Management Information," was produced and published by DMTF quite some time ago—in September of 1998 (www.dmtf.org/standards/xmlw.php). Microsoft, driven by their commitment to the XML initiative, rushed to produce an XML encoder for WMI entities, which became a part of the Windows XP distribution. The encoder, accessible through the above-mentioned IWbemObjectTextSrc interface, is capable of representing WMI objects using either the CIM-compliant or the extended WMI DTD.

Unfortunately, the IWbemObjectTextSrc interface is not utilized by the current release of FCL and is not accessible through any elements in the System.Management namespace. [2] As the disassembly of System.Management.dll shows, there is a wrapper for IWbemObjectTextSrc, but it does not appear to be used in any way, even on Windows XP systems. Nevertheless, the intentions of Microsoft developers seem to be clear, which probably means that XML encoding capabilities will make it into the next release of FCL.

Altering the Schema

As I mentioned previously, the System.Management namespace contains enough features to carry out just about any modification to the WMI schema, without ever resorting to MOF. For instance, to create a brand new class, you may simply use the Derive method of the ManagementClass type as follows:

ManagementClass mc = new ManagementClass("CIM_ManagedSystemElement"); ManagementClass nc = mc.Derive("My_ManagedElement"); nc.Put();

This method has to be invoked on the instance of the ManagementClass type that is bound to an existing WMI class. When this operation completes successfully, the method returns an instance of the ManagementClass type, bound to a newly created class definition, derived from the class for which the method is invoked, and named according to the string parameter. Thus, the code above will create the My_ManagedElement class as a subclass of CIM_ManagedSystemElement. Note that the new class definition has to be committed to the CIM Repository using the Put method of ManagementClass type.

At first, the Derive method of the ManagementClass type may appear as the only way to create a new class definition with System.Management. This may seem to be a gap in functionality because Derive does not provide for creating a class that does not have any superclasses. After all, the necessity of resorting to MOF every time you need to create a root-level class, apart from simply being annoying, may severely cripple the functionality of management applications developed with .NET. Therefore, you may expect to see some kind of static ManagementClass.Create method, which would return a blank instance of the WMI class ready to be populated and saved into the CIM Repository. Unfortunately, no such method exists. It is also not possible to alter the behavior of the Derive method and force it to create a root-level class. Internally, Derive calls the IWbemClassObject::SpawnDerivedClass method, which is designed specifically to create subclasses of a given class rather than root-level classes.

Nevertheless, if you examine WMI COM API documentation, you will find out that it is indeed possible to create a root-level class programmatically using the IWbemServices::PutClass method. As you may remember, IWbemServices::PutClass is invoked by the Put method of the ManagementClass type and is used primarily to save changes to the CIM Repository. Thus, in theory, it should be possible to produce a root-level class definition as long as you can obtain a blank, unbound instance of a ManagementClass type. Coincidentally, such an unbound ManagementClass object is returned by the parameterless version of the ManagementClass constructor:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Put();

This code works just fine, although it is necessary to explicitly name the newly created class by setting its system property __CLASS. In fact, assigning the __CLASS property is the easiest way to copy a class definition. For instance, the following code fragment will create a new class, My_Process, by copying the definition of the existing Win32_Process WMI class:

ManagementClass nc = new ManagementClass("Win32_Process"); nc.SystemProperties["__CLASS"].Value = "My_Process"; nc.Put();

Note that the resulting class definition will be absolutely identical to the definition of Win32_Process with the exception of the class name. The values of system properties other than __CLASS will remain the same so that My_Process will share the same parent class and derivation tree as Win32_Process.

Apparently, a class is fairly useless unless it is decorated with qualifiers, properties, and methods. Adding a class qualifier is just a matter of invoking the Add method of the QualifierDataCollection type, exposed through the Qualifiers property of ManagementClass. Thus, the following code will mark the newly created class as singleton by adding the Singleton qualifier:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Qualifiers.Add("Singleton", true); nc.Put();

The simplest version of the Add method takes two parameters: the string qualifier name and the object qualifier value. Since Singleton is a Boolean qualifier, the code above sets the second parameter to TRUE. As simple as it is, this version of Add has one downside—it does not allow you to specify qualifier flavors. That is why the QualifierDataCollection type exposes an overloaded version of Add, which takes not only the qualifier name and value parameters, but also a slew of Boolean values that indicate whether the qualifier is amended, whether it propagates to instances and subclasses, and whether it is overridable:

public virtual void Add( string qualifierName, object qualifierValue, bool isAmended, bool propagatesToInstance, bool propagatesToSubclass, bool isOverridable );

For example, this code marks the Singleton qualifier as nonamended and nonoverridable and allows propagation to instances and subclasses:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Qualifiers.Add("Singleton", true, false, true, true, false); nc.Put();

You can add an amended qualifier, although the results may not be exactly what you would expect. Thus, the following code sample adds a localizable Description qualifier to the definition for the My_ManagedElement class:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Qualifiers.Add("Description", "Sample Class", true, true, true, false); nc.Put();

Although this code correctly creates a definition for My_ManagedElement and stores it in the CIM Repository, it does not automatically produce a definition for an amendment class. The Description qualifier is stored along with the class definition in the rootCIMV2 namespace and, although it is marked as amended, it is not really localized. Thus, in order to produce a truly localized version of a class, you would have to create the language-specific class definition separately and save it into the localization namespace as described in Chapter 2.

Adding properties to a class is similar to adding qualifiers. The PropertyDataCollection type, exposed through the Properties property of ManagementClass, offers an overloaded Add method, which is suitable for creating just about any kind of class property. The first version of Add takes three parameters: a string property identifier, a member of the CimType enumeration that specifies the property data type, and a Boolean that indicates whether a property is an array:

public void Add( string propertyName, CimType propertyType, bool isArray );

For example, the following code snippet adds two properties to a definition of the My_ManagedElement class: a string Property1 and an unsigned 32-bit integer array Property2:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Properties.Add("Property1", CimType.String, false); nc.Properties.Add("Property2", CimType.UInt32, true); nc.Put();

The resulting class definition, expressed in MOF, will look like the following:

class My_ManagedElement { string Property1; uint32 Property2[]; };

One obvious problem with this method is that it does not have any provisions for specifying the default property values. Of course, it is possible to manually set the value, once the property is added to the class definition:

nc.Properties.Add("Property1", CimType.String, false); nc.Properties.Add("Property2", CimType.UInt32, true); nc.Properties["Property1"].Value = "DEFAULT VALUE";

Nevertheless, this approach requires at least one extra line of code and, under certain circumstances, it may not be the most desirable way of doing things. Conveniently, there is another version of Add that takes a parameter of type object housing the default value for the property:

public void Add( string propertyName, object propertyValue, CimType propertyType );

Thus, the code above can be simplified as follows:

nc.Properties.Add("Property1", "DEFAULT VALUE", CimType.String); nc.Properties.Add("Property2", CimType.UInt32, true);

Unfortunately, it looks as if there is another problem—the second version of Add does not seem to allow you to mark a property as an array. In fact, this statement is not true because the type of the property is derived from the propertyValue parameter. Thus, passing an array of values to the Add method will automatically mark the property as an array:

nc.Properties.Add("Property2", new int[] {10, 15, 20, 25}, CimType.UInt32);

Note that values passed to Add through the propertyValue parameter of type object must be compatible with the CimType value specification. As a result, the following code will generate an exception since negative integer values cannot be coerced into UInt32:

nc.Properties.Add("Property2", new int[] {-10, 15, -20, 25}, CimType.UInt32);

Finally, there is another, even simpler overload of the Add method that takes a string property name parameter and a property value of type object:

public void Add( string propertyName, object propertyValue );

In this case, the WMI property data type is guessed, based on the type of value, passed as a parameter. For instance, the following code will create a property, typed as an array of SInt32:

nc.Properties.Add("Property2", new int[] {10, 15, 20, 25});

When using this method, you should always supply a valid, non-null value of the type that can be converted to one of the CIM data types. See the WMI SDK documentation for conversion guidelines between CIM and Automation data types.

Adding properties of reference or object type is technically the same, although some special considerations apply. To add a reference property, use the Reference member of the CimType enumeration for the property data type as follows:

nc.Properties.Add("RefProperty", CimType.Reference, false);

Only weakly typed references can be created programmatically. Therefore, the preceding code will result in the following property definition:

class My_ManagedElement { object ref RefProperty; };

Even if you supply a default value that indicates the type of the reference, this value would only be used for initialization purposes and its data type will be ignored. In other words, this code

ManagementObject mo = new ManagementObject("Win32_Process.Handle=100"); nc.Properties.Add("RefProperty", mo, CimType.Reference);

will generate the following property definition:

class My_ManagedElement { object ref RefProperty = "Win32_Process.Handle=100"; };

Adding properties that refer to embedded objects is very much the same. Just as it is not possible to create a strongly typed reference, it is impossible to define a strongly typed embedded object property. Take a look at the following snippet of code:

ManagementObject mo = new ManagementObject("Win32_Process.Handle=100"); nc.Properties.Add("ObjProperty", mo, CimType.Object);

The definition, produced by this code, will resemble the following:

class My_ManagedElement { object ObjProperty = instance of Win32_Process { Handle = "100"; ... }; };

In all, if you need to create strongly typed reference or object properties, MOF seems to be the only choice.

Similarly to classes, properties may have qualifiers. The process of adding a property qualifier is the same as it is for adding a class qualifier; the only difference is that property qualifiers are added to the QualifierDataCollection, which is exposed through the Qualifiers property of the PropertyData type. For instance, to decorate a property with the Description qualifier, you may use code similar to the following:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Properties.Add("Property1", CimType.String, false); nc.Properties["Property1"].Qualifiers.Add("Description", "Qualified Property"); nc.Put();

Finally, a class can be outfitted with methods. As you may remember, methods are accessible through the Methods property of the ManagementClass type, which houses a collection of MethodData objects. This collection of type MethodDataCollection also exposes two variations of the Add method, thus allowing you to create method definitions programmatically. The simplest version of Add takes a single string parameter that denotes the name of the method to be added to the class definition:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Methods.Add("Method1"); nc.Put();

The method definition, produced by the preceding code, will have no parameters and a return type of void:

class My_ManagedElement { void Method1(); };

To add a method with parameters, use another version of Add, which takes two additional parameters of type ManagementBaseObject:

public virtual void Add( string methodName, ManagementBaseObject inParameters, ManagementBaseObject outParameters );

The two instances of the ManagementBaseObject type represent input and output parameter collections respectively. Thus, it seems that all you need to do is create two ManagementBaseObjects and add the appropriate properties to represent the parameters. In other words, if you want to add a method with a string input parameter and an integer output parameter, the following code sequence should work just fine:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; ManagementBaseObject inp = new ManagementBaseObject(); inp.Properties.Add("StrParm", CimType.String, false); ManagementBaseObject outp = new ManagementBaseObject(); outp.Properties.Add("IntParm", CimType.SInt32, false); nc.Methods.Add("ParameterizedMethod", inp, outp); nc.Put();

Unfortunately, it does not. There are a few reasons for this. First, the parameterless constructor of ManagementBaseObject type is private and therefore, it cannot be used here. Using the ManagementObject type instead is not a very good option because you cannot add properties to an instance. In fact, your only option is to use the ManagementClass type, but, even if you change the code as follows, it will still not work:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; ManagementBaseObject inp = new ManagementClass(); inp.Properties.Add("StrParm", CimType.String, false); ManagementBaseObject outp = new ManagementClass(); outp.Properties.Add("IntParm", CimType.SInt32, false); nc.Methods.Add("ParameterizedMethod", inp, outp); nc.Put();

Instead, it will throw ManagementException and complain about missing parameter IDs. As you may remember, each of the method parameters must have an ID qualifier, which uniquely identifies the parameter's position within the method signature. It is, therefore, necessary to decorate each parameter with the ID qualifier as follows:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; ManagementBaseObject inp = new ManagementClass(); inp.Properties.Add("StrParm", CimType.String, false); inp.Properties["StrParm"].Qualifiers.Add("ID", 0); ManagementBaseObject outp = new ManagementClass(); outp.Properties.Add("IntParm", CimType.SInt32, false); outp.Properties["IntParm"].Qualifiers.Add("ID", 1); nc.Methods.Add("ParameterizedMethod", inp, outp); nc.Put();

The preceding code will produce the correct definition for ParameterizedMethod:

class My_ManagedElement { void ParameterizedMethod([in] string StrParm, [out] sint32 IntParm); };

Note, the parameter IDs must be consecutive numbers starting from zero. If you attempt to use nonconsecutive ID qualifiers, you will get a ManagementException. However, if a parameter is used as both input and output, it may be added to both input and output ManagementBaseObjects with the same value of ID qualifier:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; ManagementBaseObject inp = new ManagementClass(); inp.Properties.Add("StrParm", CimType.String, false); inp.Properties["StrParm"].Qualifiers.Add("ID", 0); ManagementBaseObject outp = new ManagementClass(); outp.Properties.Add("StrParm", CimType.String, false); outp.Properties["StrParm"].Qualifiers.Add("ID", 0); nc.Methods.Add("ParameterizedMethod", inp, outp); nc.Put();

This will produce the following method definition:

class My_ManagedElement { void ParameterizedMethod([in,out] string StrParm); };

In this case, the name and data type of the parameter properties for input and output ManagementBaseObjects must match exactly, otherwise WMI will complain about duplicate parameters.

So far you have seen the technique for adding method parameters; however, there doesn't appear to be a way to specify the method return value. In fact, all the methods that I have defined up until now, had the return type of void. As you may remember from Chapter 2, the return value of a method is identified by a special ReturnValue property, which is a member of output parameters collection. Thus, to produce a definition for a method that takes a string parameter and returns an integer, you may use the following code:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; ManagementBaseObject inp = new ManagementClass(); inp.Properties.Add("StrParm", CimType.String, false); inp.Properties["StrParm"].Qualifiers.Add("ID", 0); ManagementBaseObject outp = new ManagementClass(); outp.Properties.Add("ReturnValue", CimType.SInt32, false); nc.Methods.Add("ParameterizedMethod", inp, outp); nc.Put();

Since the return value parameter is not a part of the method parameter list, it may not be decorated with the ID qualifier. If you attempt to add this qualifier, WMI will throw ManagementException.

Methods and method parameters may have qualifiers. You already know how to add a method parameter qualifier since the preceding code dealt extensively with parameter IDs specified by ID qualifiers. Adding a method qualifier is just as trivial. The Qualifiers collection of the MethodData type houses an already familiar QualifierDataCollection object, which offers several overloads of the Add method, described earlier. Thus, the following code adds a definition for a QualifiedMethod method and decorates it with the Description qualifier:

ManagementClass nc = new ManagementClass(); nc.SystemProperties["__CLASS"].Value = "My_ManagedElement"; nc.Methods.Add("QualifiedMethod"); nc.Methods["QualifiedMethod"].Qualifiers.Add("Description", "Qualified Method"); nc.Put();

As a final comment on generating method definitions, I must mention that using the parameterless constructor of the ManagementClass type to initialize the input and output ManagementBaseObjects is probably not such a great idea. It works just fine, but there is a certain danger in using an anonymous, unbound class. This is because it is entirely possible that future releases of WMI may require bound parameter classes in order to operate correctly, which will certainly break some legacy code. As you may remember, the CIM Repository contains a special system class __PARAMETERS, designed specifically to represent method parameter definitions. Using an instance of ManagementClass type, bound to the definition for __PARAMETERS class is, therefore, a superior and certainly more portable approach:

ManagementBaseObject inp = new ManagementClass("__PARAMETERS");

If you attempted to compile and run the earlier code examples, by now, your CIM Repository probably contains quite a few useless classes. You can fire up CIM Studio and delete these classes manually one by one, but you can also accomplish the same thing programmatically. Deleting a class is extremely simple: you just create an instance of ManagementClass that is bound to a given WMI class definition, and then call its Delete method. For example, to delete the previously created definition for the My_ManagedElement class from the CIM Repository, use the following code:

ManagementClass mc = new ManagementClass("My_ManagedElement"); mc.Delete();

The Delete method, inherited from the ManagementObject type, may also be invoked in an asynchronous fashion by calling it with an instance of the ManagementOperationObserver type as a parameter. Yet another overload of Delete accepts a parameter of type DeleteOptions, which lets you have some control over the delete operation. Both asynchronous programming and the DeleteOptions type are described in Chapter 2.

Note, Delete does not have to be followed by a call to Put because internally, it invokes the IWbemServices::DeleteClass method, which updates the CIM Repository instantly. If a class has instances, these instances are deleted along with the class definition.

Schema Management Miscellany

Once a class definition is created and saved into the CIM Repository, it can be used to create instances. An instance is created by invoking the CreateInstance method on a ManagementClass object that is bound to an arbitrary WMI class:

ManagementClass mc = new ManagementClass("My_ManagedElement"); ManagementObject o = mc.CreateInstance(); o.Put();

To save a newly created instance into the repository, you must call its Put method, which would internally invoke the IWbemServices::PutInstance method. There are a few things to watch out for when you are committing a new instance to the repository. Generally, every WMI object must either have a unique identity or be a single instance of a singleton class. Thus, the code above will actually fail unless the class is marked with a Singleton qualifier. Interestingly, if you attempt to create multiple instances of a singleton class, your code will most likely execute just fine; however, upon completion, the repository will contain a single instance. Such behavior is understandable because object identity is ignored for singletons, so once the first instance is created, every subsequent call to IWbemServices::PutInstance will be assumed to be an update to that instance.

In order to successfully create an instance of a non-singleton class, the class must have a property that is decorated with the Key qualifier. Moreover, before saving an instance to the repository, this key property should be assigned a value, which uniquely identifies an object. Thus, assuming that My_ManagedElement class has a key property, KeyProp, of type string, its instance can be created as follows:

ManagementClass mc = new ManagementClass("My_ManagedElement"); ManagementObject o = mc.CreateInstance(); o.Properties["KeyProp"].Value = "element1"; o.Put();

Strictly speaking, assigning an identity value to a key property is not mandatory. In a case where a class has a property marked with the Key qualifier, and such a property has a data type of string, IWbemServices::PutInstance will automatically generate a GUID to be used as an object identity as long as the property is set to NULL. The upside of this approach is, of course, the simplicity and guaranteed unique identity of a new object; but unfortunately, it comes at the price of usability. GUIDs are not smart identifiers and do not convey any information about an object, which makes them quite difficult to memorize and use. Just imagine constructing an object path, which is based on a GUID—not only is it extremely error prone, it also involves typing many meaningless hexadecimal digits. Thus, it is probably a good idea to always assign proper identity values to a key property manually before saving the object to the repository. Moreover, if a key property is of a type other than string, WMI will not attempt to generate an identity value for it. If such property is left NULL, Put will trigger an exception stating that the object's key property contains an illegal null value.

An existing instance can be deleted by invoking the Delete method of the ManagementObject type. The following code deletes the previously created My_ManagedElement object:

ManagementObject o = new ManagementObject("My_ManagedElement.KeyProp='element1'"); o.Delete();

Again, the call to Delete does not have to be followed by Put because it invokes IWbemServices::DeleteInstance internally, which automatically updates the CIM Repository.

Classes and instances are not the only elements of the WMI schema that can be deleted. Class properties and methods, as well as class, property, and method qualifiers can be deleted as well. However, instead of providing the Delete method, PropertyDataCollection, QualifierDataCollection, and MethodDataCollection types offer the Remove method. For instance, the following code fragment removes a property, a qualifier and a method of My_ManagedElement type, assuming that such schema elements exist:

ManagementClass mc = new ManagementClass("My_ManagedElement"); // removes Property1 from class definition mc.Properties.Remove("Property1"); // removes Singleton qualifier from class definition mc.Qualifiers.Remove("Singleton"); // removes Method1 from class definition mc.Methods.Remove("Method1"); mc.Put();

In order to save the changes to a class definition to the CIM Repository, the invocation of Remove has to be followed by a call to Put.

Besides Add and Remove methods, PropertyDataCollection, QualifierDataCollection, and MethodDataCollection types provide the CopyTo method, which allows you to copy the respective PropertyData, QualifierData, or MethodData objects into another collection object. CopyTo has two overloads: one that allows you to copy the elements of the respective collection into a generic Array object, and another, which uses an array of PropertyData, QualifierData, or MethodData objects as a target.

For example, the following snippet of code copies all properties of the Win32_Process class into an array of objects that iterates through this array and prints out property names and values:

ManagementClass mc = new ManagementClass("Win32_Process"); object[] arr = new object[mc.Properties.Count]; mc.Properties.CopyTo(arr, 0); foreach(PropertyData pd in arr) { Console.WriteLine("{0} = {1}", pd.Name, pd.Value); }

The last parameter to CopyTo is an index into the target array at which copying is to start. Thus, it is possible to, say, combine properties of several classes into one array and then iterate through it once:

ManagementClass mc1 = new ManagementClass("Win32_Process"); ManagementClass mc2 = new ManagementClass("Win32_Service"); object[] arr = new object[mc1.Properties.Count + mc2.Properties.Count]; mc1.Properties.CopyTo(arr, 0); mc2.Properties.CopyTo(arr, mc1.Properties.Count);

The same operation can be performed on methods and qualifiers—the only difference is that CopyTo will be applied to the MethodDataCollection or QualifierDataCollection objects respectively.

An alternative version of CopyTo is not much different. For example, the following code works in the same fashion as the previous code fragment: it prints out all property names and values for the Win32_Process class:

ManagementClass mc = new ManagementClass("Win32_Process"); PropertyData[] arr = new PropertyData[mc.Properties.Count]; mc.Properties.CopyTo(arr, 0); foreach(PropertyData pd in arr) { Console.WriteLine("{0} = {1}", pd.Name, pd.Value); }

Similar to the previously shown version of CopyTo, the last parameter is an index into the target array at which the copy operation is to start.

Apart from being occasionally useful for combining properties, methods, and qualifiers of multiple objects into a single collection for subsequent iteration, CopyTo is not very interesting and has limited value when it comes to schema manipulation.

[1]The TextFormat enumeration distributed with the latest version of .NET framework and Visual Studio .NET code named "Everett" includes two additional members: TextFormat.CimDtd20 and TextFormat.WmiDtd20. These flags are used to produce XML-formatted representations of WMI objects compliant with either CIM or WMI DTD, respectively.

[2]The latest version of FCL distributed with "Everett" utilizes the IWbemObjectTextSrc interface on Windows XP systems. This interface is engaged by the ManagementBaseObject.GetText method if this method is called with a parameter other than TextFormat.Mof.

Summary

This chapter provided a fairly detailed and comprehensive overview of the schema management facilities available through WMI and the

System.Management namespace. Having read the material and worked through the example code, you should now be familiar with the following:

With this kind of information in your pocket, you should be well positioned to define and deploy custom extension schemas or manipulate the elements of the existing management schemas.

Категории