|
. NET System Management Services Authors: Golomshtok A. Published year: 2005 Pages: 30/79 |
To facilitate all aspects of WQL query handling, the designers of the .NET system management framework outfitted the System.Management namespace with several query support types. Besides aiding with query-based data retrieval, these types provide backing for other operations such as query assembly, parsing, and analysis. The remainder of this chapter is a comprehensive overview of the WQL query support functionality that is afforded by the System.Management namespace types.
ManagementObjectSearcher is the centerpiece of the WQL query handling machinery that is available within the System.Management namespace. This type is very straightforward and easy to use because it has a single purpose—to execute queries. To see how trivial it is to execute an arbitrary WQL query, take a look at the following snippet of code:
ManagementObjectSearcher ms = new ManagementObjectSearcher( "SELECT * FROM Win32_Process"); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
This code first creates an instance of ManagementObjectSearcher by invoking one of its constructors, which takes a WQL query string as a parameter. Once the ManagementObjectSearcher is constructed and ready to use, the code calls its Get method and iterates through the returned ManagementObjectCollection using the foreach loop.
The WQL query, when passed as an argument to the ManagementObjectSearcher constructor, does not have to be a SELECT statement— the ASSOCIATORS OF and REFERENCES OF queries are handled in exactly the same fashion:
ManagementObjectSearcher ms = new ManagementObjectSearcher("ASSOCIATORS OF {Win32_Process=100}"); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
The same goes for schema queries. The only thing that sets a schema query apart from its data counterpart is the query statement itself, as well as the fact that the ManagementObjectCollection , which is returned by the Get method, will contain classes rather than instances:
ManagementObjectSearcher ms = new ManagementObjectSearcher( "SELECT * FROM meta_class WHERE __this ISA 'Win32_Process'"); foreach(ManagementClass mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
As I briefly mentioned before, WQL queries themselves may not include any information, that specifies the location of a WMI class or object of interest. In other words, the only piece of class or object identification allowed in a query is either the relative object path or the class name . Therefore, the earlier examples would always query the \root\CIMV2 namespace on the local machine, which is not always the behavior you want. One way to overcome this limitation is to use the DefaultPath static property of the ManagementPath type to change the global default namespace path (see Chapter 2 for details):
ManagementPath.DefaultPath = new ManagementPath(@"\BCK_OFFICE\root\CIMV2"); ManagementObjectSearcher ms = new ManagementObjectSearcher( "SELECT * FROM Win32_Process"); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
This code does achieve the objective of producing a list of all the processes that are running on the remote system BCK_OFFICE . This approach, however, is less than perfect because the default namespace setting is changed at the global level and will affect not only this query, but all subsequent operations as well. Although you can save the initial default setting and restore it when the query completes, doing something like that is not only error-prone , but also just plain ugly.
There is a much better solution, which comes in a form of an overloaded constructor method for the ManagementObjectSearcher type. This constructor takes two string parameters: a scope, which is essentially a namespace path, and a WQL query. Hence, the previous example can be rewritten as follows :
ManagementObjectSearcher ms = new ManagementObjectSearcher(@"\BCK_OFFICE\root\CIMV2", "SELECT * FROM Win32_Process"); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
The namespace path can also be set using the Scope property of the ManagementObjectSearcher type. This property refers to an object of type ManagementScope , which is used to control the WMI connection establishment process. Since it is not my intention to discuss the functionality afforded by the ManagementScope type until Chapter 8, I will just say that it allows you to set the namespace path for subsequent operations. Thus, the following snippet of code produces exactly the same results as the previous example:
ManagementObjectSearcher ms = new ManagementObjectSearcher( "SELECT * FROM Win32_Process"); ms.Scope.Path = new ManagementPath(@"\BCK_OFFICE\root\CIMV2"); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
As you may remember from our discussion of the WQL SELECT statement, queries that explicitly specify object properties to be selected return partially populated objects. Thus, certain system properties of objects returned by such query will not be populated . Let us take a look at the following code:
ManagementObjectSearcher ms = new ManagementObjectSearcher( "SELECT ExecutablePath FROM Win32_Process WHERE Handle = 100"); foreach(ManagementObject mo in ms.Get()) { foreach(PropertyData pd in mo.SystemProperties) { Console.WriteLine(" ", pd.Name, pd.Value); } }
The output shows that all system properties that identify the location of a returned object, such as __SERVER , __NAMESPACE , and __PATH , are blank.
You can alter this default behavior by supplying an instance of the EnumerationOptions type to the constructor of the ManagementObjectSearcher . You may recall that the EnumerationOptions type allows you to control different aspects of an enumeration operation such as the block size of the read operation, the inclusion of localized information in the output, and more. Although most of the functionality afforded by this type has already been discussed in Chapter 2, one of its properties, EnsureLocatable , has an interesting effect on the results of a WQL query. By setting this option to the TRUE value, you may instruct WMI to always populate those system properties that identify the location of an object. Hence, the following code correctly prints out the values of location-specific system properties:
EnumerationOptions eo = new EnumerationOptions(); eo.EnsureLocatable = true; ManagementObjectSearcher ms = new ManagementObjectSearcher(null, "SELECT ExecutablePath FROM Win32_Process WHERE Handle = 100", eo); foreach(ManagementObject mo in ms.Get()) { foreach(PropertyData pd in mo.SystemProperties) { Console.WriteLine(" ", pd.Name, pd.Value); } }
Here the instance of the EnumerationOptions with the EnsureLocatable flag turned on is passed as a last argument to the constructor of the ManagementObjectSearcher . Note that this version of the constructor takes three parameters: a scope string that contains the namespace path, the query string, and the EnumerationOptions object. When you are connecting to a default namespace on a local machine, a null value can be substituted for the scope string.
You can achieve the same result slightly more easily by using the Options property of the ManagementObjectSearcher , which refers to an object of type EnumerationOptions . Thus, the previous example can be simplified as follows:
ManagementObjectSearcher ms = new ManagementObjectSearcher( "SELECT ExecutablePath FROM Win32_Process WHERE Handle = 100"); ms.Options.EnsureLocatable = true; foreach(ManagementObject mo in ms.Get()) { foreach(PropertyData pd in mo.SystemProperties) { Console.WriteLine(" ", pd.Name, pd.Value); } }
Finally, just like the other .NET system management types, the ManagementObjectSearcher has built-in provisions for processing WQL queries in an asynchronous fashion. The usage pattern for an asynchronous query execution is similar to that of a direct object retrieval—it revolves around the ManagementOperationObserver object, passed as a parameter to the overloaded version of the Get method. For example, the following code asynchronously retrieves all instances of the Win32_Process class:
class Monitor { bool bComplete = false; public bool Completed { get { return bComplete; } set { bComplete = value; } } public void OnCompleted(object sender, CompletedEventArgs ea) { Completed = true; } public void OnObjectReady(object sender, ObjectReadyEventArgs ea) { Console.WriteLine(ea.NewObject["__PATH"]); } public static void Main(string[] args) { Monitor mo = new Monitor(); ManagementOperationObserver ob = new ManagementOperationObserver(); ob.Completed += new CompletedEventHandler(mo.OnCompleted); ob.ObjectReady += new ObjectReadyEventHandler(mo.OnObjectReady); ManagementObjectSearcher ms = new ManagementObjectSearcher( "SELECT * FROM Win32_Process"); ms.Get(ob); while(!mo.Completed) { System.Threading.Thread.Sleep(1000); } } }
This code creates an instance of ManagementOperationObserver , sets up the handlers for its Completed and ObjectReady events, and then invokes the asynchronous version of the ManagementObjectSearcher Get method. The results of the SELECT query are delivered one-by-one to the ObjectReady event handler and are accessed through the NewObject property of the ObjectReadyEventArgs object.
Besides aiding with query execution, the System.Management namespace provides several types for query parsing and analysis. At the root of the type hierarchy there is an abstract type, called ManagementQuery , which is designed to serve as a basis for deriving more specialized management query types. In addition to a few protected methods for parsing the query text, ManagementQuery defines two public properties: QueryLanguage and QueryString . These two properties are all that ManagementObjectSearcher needs to process the query. In fact, if you look at the signature of the IWbemServices::ExecQuery method that is called internally by ManagementObjectSearcher , you will notice that this method expects the language identifier and the query text as its first two parameters. Thus, it seems like implementing a custom query type is a fairly simple matter:
class MyQuery : ManagementQuery { public MyQuery(string lang, string query) { QueryLanguage = lang; QueryString = query; } public override object Clone() { return this.MemberwiseClone(); } }
Note that you need to supply an implementation for Clone method of ICloneable interface, since ManagementQuery , which includes the interface, leaves this method unimplemented.
Theoretically, it should be possible to pass this custom query object to the overloaded constructor ManagementObjectSearcher instead of just the plain query string:
MyQuery query = new MyQuery("WQL", "SELECT * FROM Win32_Process"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query);
Although ManagementObjectSearcher does have a constructor that takes a query object rather than the plain query string, the code above will not compile. The problem is that this constructor, rather than accepting an object that descends from ManagementQuery directly, expects an instance of ObjectQuery type, which is a subclass of ManagementQuery .
As you may remember, WMI supports three general categories of queries: data, schema, and event queries. ManagementObjectSearcher is designed to handle only those queries that return objects or class definitions—data or schema queries—and is unsuitable for processing event queries. Thus, the System.Management namespace offers two specialized subclasses of ManagementQuery : ObjectQuery and EventQuery . These subclasses are intended to handle object and event queries respectively. To enforce this separation of duties , the constructor of ManagementObjectSearcher expects the parameter of type ObjectQuery , while the constructors of ManagementEventWatcher (a type that is dedicated to handling WMI events) accept parameters of type EventQuery . The details of event handling as well as the functionality afforded by EventQuery and ManagementEventWatcher types, will be discussed in the next chapter.
ObjectQuery is a very simple type that is similar to the MyQuery type shown earlier. Besides the QueryLanguage and QueryString properties that it inherited from ManagementQuery , it has three constructor methods: a parameterless one, one that takes a query string parameter, and another one that takes a string parameter, which represents the query language, as well as the query string. The following snippet of code illustrates how ObjectQuery can be used with ManagementObjectSearcher :
ObjectQuery query = new ObjectQuery("WQL", "SELECT * FROM Win32_Process"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
Note that passing the WQL language identifier to the ObjectQuery constructor is optional. To save yourself some typing, you may choose to use a single-parameter constructor, which defaults the language to WQL:
ObjectQuery query = new ObjectQuery("SELECT * FROM Win32_Process"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
Interestingly, the ability to specify the query language has little value because the only supported language for querying WMI is WQL. It is conceivable, however, that future releases of WMI will support alternative query languages, such as XPath, for instance. Until then, explicitly setting the query language is not required and is best avoided. In fact, the only logical reason for having a generic, language- agnostic object query type seems to be the flexibility of being able to provide specialized, language-specific query types. In other words, ObjectQuery is not intended to be used directly and instead, it should serve as a base class for specialized types, that incorporate features, pertinent to a specific query language. Currently, the only available specialization of ObjectQuery is WqlObjectQuery , but it is entirely possible that in the near feature the System.Management namespace may include something like XPathObjectQuery .
WqlObjectQuery is not much different from its parent and does not offer any new features. Instead, it restricts the functionality of ObjectQuery by making its QueryLanguage property read-only, thus preventing a user from overriding the default value WQL set by the constructor of ManagementQuery . With that in mind, you can use WqlObjectQuery to rewrite the previous as follows:
WqlObjectQuery query = new WqlObjectQuery("SELECT * FROM Win32_Process"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
As you can see, neither ObjectQuery nor WqlObjectQuery really aid with query parsing or analysis, and as a result, they seem to add little value to query handling. You may find this somewhat puzzling since there is no obvious advantage in using these types versus a plain query string. The reason for their use, once again, is specialization—these types are not intended to be used directly, rather they are designed to serve as base classes for other types that zero in on particular categories of WQL queries.
As mentioned earlier, WQL is a simple language consisting of just three statements. The syntactic structure of two of them, ASSOCIATORS OF and REFERENCES OF , is very similar, so similar, in fact, that you can use a common algorithm to parse. The SELECT statement, on the other hand, is strikingly different and necessitates a special parsing logic. Thus, rather than trying to shoehorn the parsing code for the entire WQL language into a single class, the designers of the System.Management namespace created three separate subclasses of WqlObjectQuery — SelectQuery , RelatedObjectQuery , and RelationshipQuery — each of which is responsible for handling a specific WQL statement.
As its name implies, the first of these three types, SelectQuery , is dedicated to handling WQL SELECT queries. SelectQuery makes parsing the query text and breaking it into individual components extremely easy:
SelectQuery query = new SelectQuery( "SELECT * FROM Win32_Process WHERE Handle = 100"); Console.WriteLine("Class Name: ", query.ClassName); Console.WriteLine("Condition: ", query.Condition); Console.WriteLine("Selected Properties:"); foreach(string prop in query.SelectedProperties) { Console.WriteLine(" ", prop); } Console.WriteLine("Schema Query: ", query.IsSchemaQuery);
Here, as soon as the constructor of SelectQuery is invoked, it calls the setter routine for the QueryString property; this in turns calls the internal method ParseQuery , which is responsible for breaking the query text into individual components. Once the SelectQuery object is constructed, these components are accessible through its ClassName , Condition , SelectedProperties , and IsSchemaQuery properties. The ClassName and Condition properties are self-explanatory—they contain the name of the WMI class and the query WHERE criteria respectively. IsSchemaQuery is just a Boolean flag that indicates whether it is a data (FALSE) or schema (TRUE) query. Finally, SelectedProperties is a string collection that houses the names of the WMI object properties that are returned by the query. With that in mind, the output, produced by the code above, should come as no surprise:
Class Name: Win32_Process Condition: Handle = 100 Selected Properties: Schema Query: False
Actually, let me take that back—the output is somewhat surprising because the SelectedProperties collection seems to be empty. The query ues the * placeholder so that it is logical to expect the SelectedProperties collection to contain the names of all properties of Win32_Process class. The problem is that the SelectQuery type does not interact with WMI by itself and, therefore, it is not capable of fetching the class definition in order to expand the * placeholder. Thus, it is safe to assume that the empty SelectedProperties collection implies that all of the object properties are to be returned by the query. Conversely, if you explicitly mention the WMI object properties in the SELECT property list, the SelectedProperties collection will be correctly populated:
SelectQuery query = new SelectQuery( "SELECT Handle, ExecutablePath FROM Win32_Process WHERE Handle = 100"); Console.WriteLine("Selected Properties:"); foreach(string prop in query.SelectedProperties) { Console.WriteLine(" ", prop); }
Thus, this code will produce the following output:
Selected Properties: Handle ExecutablePath
The string parameter, taken by the constructor of the SelectQuery type, does not necessarily have to be a WQL query. Supplying just a class name will yield the same results, hence the following code will return all instances of Win32_Process class:
SelectQuery query = new SelectQuery("Win32_Process"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
If you construct an instance of SelectQuery using a class name and then read its QueryString property, you will see a valid WQL query:
SelectQuery query = new SelectQuery("Win32_Process"); Console.WriteLine(query.QueryString);
This code will produce the following output:
select * from Win32_Process
Judging from the output, you may assume that the constructor of the SelectQuery type always assembles a valid WQL SELECT statement when it is invoked with the class name parameter. This, however, is not quite the case; the constructor simply sets the ClassName property. In fact, the SELECT statement only gets built whenever the QueryString property is referenced—it is the get property routine of the QueryString that invokes the internal BuildQuery method that is responsible for assembling a valid SELECT statement from individual components.
A query may not necessarily need to be assembled in order for ManagementObjectSeracher to process it. Whenever the Get method of ManagementObjectSearcher is invoked, it analyzes a query to establish the optimum execution strategy. It does this by following these steps:
First, it checks whether a query has selection criteria. In other words, it checks whether the query's Condition property is populated.
Then it looks at the SelectedProperties collection to find out whether it is empty (whether all or just some WMI object properties are requested ).
Finally, it checks the EnumerationOptions object associated with ManagementObjectSeracher to determine if the EnumerateDeep flag is set. In the context of a data query, this flag indicates whether an enumeration operation returns instances of all subclasses of a given class or just the instances of its immediate subclasses.
If a query satisfies all three of these criteria—it has no condition, it selects all properties, and it has its EnumerateDeep flag set to TRUE—the Get method does not even attempt to execute it via IWbemServices::ExecQuery . Instead, it calls the IWbemServices::CreateInstanceEnum method. This method, which expects a class name rather than a WQL statement, is just a more efficient way to achieve the same results. In effect, using this method means that even if a SelectQuery object is instantiated with a valid WQL SELECT statement, this SELECT is not used, unless it violates one of the three criteria just mentioned.
Thus, you have probably realized that using data queries without the WHERE clause is not such a good idea. There are more efficient and certainly cleaner ways of producing the same results. For instance, you may remember the GetInstances method of ManagementClass type that was described in the previous chapter. This method does essentially the same thing as the query I was just discussing—it retrieves all instances of a particular WMI class. Interestingly, its implementation is also very similar: it calls IWbemServices::CreateInstanceEnum passing it the name of the WMI class. Hence, the following code fragment is functionally identical to the previous example, based on SelectQuery , but it is much cleaner and a bit more efficient:
ManagementClass mc = new ManagementClass("Win32_Process"); EnumerationOptions eo = new EnumerationOptions(); eo.EnumerateDeep = true; foreach(ManagementObject mo in mc.GetInstances(eo)) { Console.WriteLine(mo["__PATH"]); }
Here the intention is clear because the code is much more readable. A small performance gain is achieved since there is no need to parse and analyze the query string. Note that you must pass the EnumerationOptions object with EnumerateDeep property set to TRUE to the GetInstances method to correctly emulate the behavior of a WQL SELECT query. By default, data queries always return instances of all the subclasses of a given class.
You may think that setting the QueryString property to a class name rather than a WQL statement will have an effect similar to that of calling the constructor of SelectQuery with a class name parameter. This, however, is not the case, and the following code will throw ArgumentException :
SelectQuery query = new SelectQuery(); query.QueryString = "Win32_Process";
Here the exception is thrown by the internal method ParseQuery that is invoked by the property set routine for QueryString . Apparently, a query has to be validated in order to be parsed and a mere class name does not constitute a valid WQL statement. It is, however, perfectly legal to use the ClassName property in the same fashion. Thus, the following code will work, correctly retrieving all instances of the Win32_Process class:
SelectQuery query = new SelectQuery(); query.ClassName = "Win32_Process"; ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
This effectively means that you can easily construct a query from its individual components without resorting to ugly text manipulation. This may come in very handy, especially when you are building graphical applications where certain controls on the GUI correspond to the elements of a WQL SELECT statement. The following code builds a valid and fairly complex WQL query:
SelectQuery query = new SelectQuery(); query.ClassName = "Win32_Service"; query.Condition = "StartMode = 'Auto' AND State = 'Stopped'"; query.SelectedProperties.Add("Name"); query.SelectedProperties.Add("DisplayName"); query.SelectedProperties.Add("StartMode"); Console.WriteLine(query.QueryString);
The output, produced by this code will be the following:
select Name, DisplayName, StartMode from Win32_Service where StartMode = 'Auto' AND State = 'Stopped'
For convenience, SelectQuery provides a constructor that takes two parameters: the name of the WMI class and the query condition. Thus, the process of assembling a query can be simplified as follows:
SelectQuery query = new SelectQuery("Win32_Service", "StartMode = 'Auto' AND State = 'Stopped'"); Console.WriteLine(query.QueryString);
Yet, another constructor allows you to specify a list of selected properties in addition to the class name and condition:
SelectQuery query = new SelectQuery("Win32_Service", "StartMode = 'Auto' AND State = 'Stopped'", new string[] {"Name", "DisplayName", "StartMode"}); Console.WriteLine(query.QueryString);
Contrary to what you may think, SelectQuery does not automatically detect schema queries. This is mildly disappointing since all it takes is scanning the query text for the presence of the meta_class keyword. Unfortunately, if you intend to construct a schema query, it has to be stated explicitly:
SelectQuery query = new SelectQuery(true, "__this ISA 'CIM_Process'"); Console.WriteLine(query.QueryString);
Here the first parameter to the constructor is a Boolean flag that indicates that a schema query is required. The second argument is not a query, rather it is a query selection criteria. Note that if you attempt to pass a full query instead of just the condition, SelectQuery will assemble an invalid WQL statement since it does not check whether the second argument starts with SELECT .
A schema query does not have to be marked as such in order to produce the expected results. The following code fragment will correctly return all class definitions for the CIM_Process and its descendants, although the IsSchemaQuery property of the SelectQuery object will return FALSE:
SelectQuery query = new SelectQuery( "SELECT * FROM meta_class WHERE __this ISA 'CIM_Process'"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementClass mc in ms.Get()) { Console.WriteLine(mc["__PATH"]); }
A query can also be marked as a schema query by setting its IsSchemaQuery property to TRUE. This very interesting effect is illustrated by the following code snippet:
SelectQuery query = new SelectQuery("CIM_Process"); query.IsSchemaQuery = true; Console.WriteLine(query.QueryString);
You may rightfully expect to see a query assembled as follows:
SELECT * FROM meta_class WHERE __this ISA 'CIM_Process'
Curiously, the output, produced by this code, is totally different and may not seem very logical at first:
SELECT * FROM meta_class
It turns out that once a query is marked as schema query, the class name passed to the constructor is essentially ignored. Moreover, even if you pass a valid WQL statement with no WHERE criteria to the constructor, it will be ignored as well. Thus, the following code will produce exactly the same output:
SelectQuery query = new SelectQuery("SELECT * FROM Win32_Process"); query.IsSchemaQuery = true; Console.WriteLine(query.QueryString);
The most interesting thing, however, happens if you create a SelectQuery object by passing a WQL SELECT with a WHERE clause to its constructor. Consider the following code:
SelectQuery query = new SelectQuery( "SELECT * FROM Win32_Process WHERE Handle = 100"); query.IsSchemaQuery = true; Console.WriteLine(query.QueryString);
Surprisingly, this code is going to output the following query:
select * from meta_class where Handle = 100
This one is not even a valid schema query and it will generate an exception if you attempt to execute it. However, if a SelectQuery object is created with a valid schema query, the output will be a valid schema query:
SelectQuery query = new SelectQuery( "SELECT * FROM meta_class WHERE __this ISA 'CIM_Process'"); query.IsSchemaQuery = true; Console.WriteLine(query.QueryString);
Whenever the IsSchemaQuery property of the SelectQuery object is assigned, its set property routine calls an internal function BuildQuery , that is responsible for generating a query string. When invoked with the IsSchemaQuery property set to TRUE, BuildQuery simply strings together the constant string select * from meta_class and the contents of the Condition property of the SelectQuery object. So, you should be careful when you are creating schema queries because the query string that is assembled by the SelectQuery type may not always be what you expect. Perhaps, the safest way to achieve the right results is by using a SelectQuery constructor, which takes a Boolean IsSchemaQuery flag and the query condition as parameters.
Finally, if you are interested in the internals of query processing, you should know that schema queries are processed similarly to data queries. If a query satisfies the same three criteria—it has no WHERE clause, it selects all object properties, and it is processed with the EnumerateDeep flag set to TRUE—it will not be executed via the IWbemServices::ExecQuery method. Just as it is the case with data queries, for the sake of efficiency, ManagementObjectSearcher will call the IWbemServices::CreateClassEnum method. The IWbemServices::CreateClassEnum takes a class name parameter, which specifies a name of the superclass for all returned classes. Thus, theoretically, you should be able to set the ClassName property of a schema query object and then retrieve just the subclasses of a given class. In other words, the following code should output just the subclasses of CIM_Process :
SelectQuery query = new SelectQuery(); query.IsSchemaQuery = true; query.ClassName = "CIM_Process"; ManagementObjectSearcher ms = new ManagementObjectSearcher(query); ms.Options.EnumerateDeep = true; foreach(ManagementClass mc in ms.Get()) { Console.WriteLine(mc["__PATH"]); }
However, if you run this code, you will see that it outputs all classes in the current namespace, just as a condition-less schema query does. It turns out that a constructor of ManagementObjectSearcher clones the query object by invoking its Clone method. Clone checks the IsSchemaQuery property of its object and ensures that a cloned instance has a blank ClassName property in case it is a schema query. This phenomena is illustrated by the following code:
SelectQuery query = new SelectQuery(); query.IsSchemaQuery = true; query.ClassName = "CIM_Process"; ManagementObjectSearcher ms = new ManagementObjectSearcher(query); query = (SelectQuery)ms.Query; Console.WriteLine(query.ClassName);
The bottom line is that when the Get method of ManagementObjectSearcher is invoked, it calls IWbemServices::CreateClassEnum with a blank class name parameter; this results in all classes being returned by the enumeration. As strange as it may sound, this is done to ensure that the behavior is consistent with the normal behavior of a schema query without a WHERE clause. After all, if you want to retrieve just the subclasses of a given class, you should turn to the GetSubclasses method of the ManagementClass type that was described in the previous chapter.
The SelectQuery type is, perhaps, the most interesting of the three types representing the respective WQL statements. The remaining two types— RelatedObjectQuery and RelationshipQuery —are fairly simple and self-explanatory, mostly due to the more rigid syntax of the ASSOCIATORS OF and REFERENCES OF statements. Both of these types are structured in such a way that their properties map one-to-one to the similarly named syntax elements of their respective WQL statement.
RelatedObjectQuery represents the WQL ASSOCIATORS OF statement. Just like a SelectQuery , an instance of RelatedObjectQuery can be created by passing a query string to its constructor:
RelatedObjectQuery query = new RelatedObjectQuery( @"ASSOCIATORS OF {Win32_Service='WinMgmt'} WHERE ResultClass = Win32_ComputerSystem"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
Once a query object is created, the query text is parsed and corresponding object properties are set accordingly . Thus, if you attempt to print out the SourceObject and RelatedClass properties of the preceding RelatedObjectQuery , the output will look like the following:
Related Class: Win32_ComputerSystem Source Object: Win32_Service='WinMgmt'
Note that, unlike SelectQuery , the RelatedObjectQuery type does not have a Condition property. Instead, the query criteria is broken down into individual elements, such as RelatedClass , RelatedQualifier , RelatedRole , and so o. The QueryString , QueryLanguage , and IsSchemaQuery properties are still available. This time, however, since ASSOCIATORS OF schema queries are unambiguously identified by the SchemaOnly keyword, the IsSchemaQuery property will be correctly set because the query string represents a schema query. Thus, the following code will print TRUE on the console:
RelatedObjectQuery query = new RelatedObjectQuery( "ASSOCIATORS OF WHERE SchemaOnly"); Console.WriteLine(query.IsSchemaQuery);
This also holds true for other query elements such as ClassDefsOnly . The following code sample will print TRUE on the console as well:
RelatedObjectQuery query = new RelatedObjectQuery( "ASSOCIATORS OF {Win32_Service='WinMgmt'} WHERE ClassDefsOnly"); Console.WriteLine(query.ClassDefinitionsOnly);
Conversely, the ASSOCIATORS OF query can be assembled out of individual components as follows:
RelatedObjectQuery query = new RelatedObjectQuery(); query.SourceObject = "Win32_Service='WinMgmt'"; query.RelatedClass = "Win32_ComputerSystem"; Console.WriteLine(query.QueryString);
This code will produce the following output, which represents perfectly a valid ASSOCIATORS OF query:
associators of {Win32_Service='WinMgmt'} where resultclass = Win32_ComputerSystem
There is more than one way to create an instance of RelatedObjectQuery type. For your convenience, this type offers a few overloaded constructors, which allow you to build a query from its individual components:
public RelatedObjectQuery ( string sourceObject , string relatedClass )
where the parameters are defined as follows:
sourceObject : A string parameter that represents a relative path to the source object to which the current query applies.
relatedClass : A string parameter that indicates that all returned objects must belong to a given class or a class derived from a given class.
public RelatedObjectQuery ( string sourceObject , string relatedClass , string relationshipClass , string relatedQualifier , string relationshipQualifier , string relatedRole , string thisRole , bool classDefinitionsOnly )
where
sourceObject : A string parameter that represents the relative path to the source object to which the current query applies.
relatedClass : A string parameter that indicates that all the returned objects must belong to a given class or a class derived from a given class.
relationshipClass : A string parameter that contains the name of the association class. If this parameter is specified, it indicates that the object to which the query applies ( sourceObject ) and the resulting objects must be connected via an association object of a given class or a class derived from a given class.
relatedQualifier : A string parameter that contains the name of the qualifier. If this parameter is specified it indicates that all the returned objects must belong to a class that includes a given qualifier.
relationshipQualifier : A string parameter that contains the name of the qualifier. If this parameter is specified it indicates that the object to which the query applies ( sourceObject ) and the resulting objects must be connected via an association object that belongs to a class that includes a given qualifier.
relatedRole : A string parameter that contains the name of the role. If this parameter is specified it indicates that all the returned objects must play a particular role in the association. The role is defined as a name of the property of an association class, which is a reference property that points to a given object.
thisRole : An optional string parameter that contains the name of the role. If this parameter is specified it indicates that the object to which the query applies ( sourceObject ) must play a particular role in the association. The role is defined as a name of the property of an association class, which is a reference property that points to a given object.
classDefinitionsOnly : A Boolean parameter. If this parameter is specified, it indicates that the method should return schema information (TRUE) or data (FALSE).
and
public RelatedObjectQuery ( bool isSchemaQuery , string sourceObject , string relatedClass , string relationshipClass , string relatedQualifier , string relationshipQualifier , string relatedRole , string thisRole )
where
isSchemaQuery : A Boolean parameter that, if set to TRUE value, indicates that a query is a schema and as such is only supposed to return schema information.
sourceObject : A string parameter that represents the relative path to the source object to which the current query applies.
relatedClass : A string parameter that indicates that all returned objects must belong to a given class or a class derived from a given class.
relationshipClass : A string parameter that contains the name of the association class. If this parameter is specified, it indicates that the object to which the query applies ( sourceObject ) and the resulting objects must be connected via an association object of a given class or a class derived from a given class.
relatedQualifier : A string parameter that contains the name of the qualifier. If this parameter is specified, it indicates that all the returned objects must belong to a class that includes a given qualifier.
relationshipQualifier : A string parameter that contains the name of the qualifier. If this parameter is specified the object to which the query applies ( sourceObject ) and the resulting objects must be connected via an association object that belongs to a class, which includes a given qualifier.
relatedRole : A string parameter that contains the name of the role. If this parameter is specified, all the returned objects must play a particular role in the association. The role is defined as the name of the property of an association class, which is a reference property that points to a given object.
thisRole : An optional string parameter that contains the name of the role. If this parameter is specified, the object to which the query applies ( sourceObject ) must play a particular role in the association. The role is defined as the name of the property of an association class, which is a reference property that points to a given object.
The remaining query type. RelationshipQuery , which embodies REFERENCES OF statement, is very similar to RelatedObjectQuery . Its properties are a one-to-one reflection of the corresponding syntax elements of the REFERENCES OF statement, and its behavior is conceptually the same as the behavior of RelatedObjectQuery .
You can create a fully functional instance of the RelationshipQuery type by calling one of its constructors, which takes a string parameter that represents the REFERENCES OF query:
RelationshipQuery query = new RelationshipQuery( "REFERENCES OF {Win32_Process=100} WHERE ResultClass = CIM_ProcessExecutable"); ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
Once such an instance is created, the query text is parsed and the object properties are set appropriately to reflect respective syntax elements of the query. Thus, if you print out the SourceObject and RelationshipClass properties of the preceding RelationshipQuery object, you will see the following output:
SourceObject: Win32_Process=106 RelationshipClass: CIM_ProcessExecutable
You can assemble a valid query object by assigning its properties to the appropriate values. Thus, the following code example is equivalent of the previous one:
RelationshipQuery query = new RelationshipQuery(); query.SourceObject = "Win32_Process=100"; query.RelationshipClass = "CIM_ProcessExecutable"; ManagementObjectSearcher ms = new ManagementObjectSearcher(query); foreach(ManagementObject mo in ms.Get()) { Console.WriteLine(mo["__PATH"]); }
Finally, the RelationshipQuery type offers several convenience constructors that allow you to build a query from individual components:
public RelationshipQuery ( string sourceObject , string relationshipClass )
where the parameters are defined as follows:
sourceObject : A string parameter that represents a relative path to the source object to which the current query applies.
relationshipClass : A string parameter that contains the name of the association class. It indicates that the returned associations must be of a given class or a class derived from a given class.
public RelationshipQuery ( string sourceObject , string relationshipClass , string relationshipQualifier , string thisRole , bool classDefinitionsOnly )
where
sourceObject : A string parameter that represents a relative path to the source object to which the current query applies.
relationshipClass : A string parameter that contains the name of the association class. It indicates that the returned associations must be of a given class or a class derived from a given class.
relationshipQualifier : A string parameter that contains the name of the qualifier. If this parameter is specified, it indicates that the returned association classes must include a given qualifier.
thisRole : A string parameter that contains the name of the role. If this parameter is specified, it indicates that the object to which the query applies ( sourceObject ) must play a particular role in the association. The role is defined as the name of the property of an association class, which is a reference property that points to a given class.
classDefinitionsOnly : A Boolean parameter. If this parameter is specified, it indicates whether the method should return schema information (TRUE) or data (FALSE).
and
public RelationshipQuery ( bool isSchemaQuery , string sourceObject , string relationshipClass , string relationshipQualifier , string thisRole )
where
isSchemaQuery : A Boolean parameter that, if set to TRUE, indicates that a query is a schema query and as such is only supposed to return schema information.
sourceObject : A string parameter that represents a relative path to the source object to which the current query applies.
relationshipClass : A string parameter that contains the name of the association class. It indicates that the returned associations must be of a given class or a class derived from a given class.
relationshipQualifier : A string parameter that contains the name of the qualifier. If this parameter is specified, it indicates that the returned association classes must include a given qualifier.
thisRole : A string parameter that contains the name of the role. If this parameter is specified, it indicates that the object to which the query applies ( sourceObject ) must play a particular role in the association. The role is defined as a name of the property of an association class, which is a reference property that points to a given class.
When it comes to parsing queries, RelatedObjectQuery and RelationshipQuery types are very useful. However, in my opinion, these types have little value for the purposes of retrieving the data. Although it is entirely possible to read and analyze just about any kind of relationship information using either RelatedObjectQuery or RelationshipQuery along with ManagementObjectSearcher , there are better ways of achieving the same goal. For instance, you may remember the GetRelated and GetRelationships methods of the ManagementObject type, as well as the GetRelatedClasses and GetRelationshipClasses methods of the ManagementClass type, described in the previous chapter. Consider the following example:
ManagementObject mo = new ManagementObject("Win32_Process=100"); foreach(ManagementObject o in mo.GetRelationships("CIM_ProcessExecutable")) { Console.WriteLine(o["__PATH"]); }
Functionally, this code is equivalent to the previous example involving RelationshipQuery and ManagementObjectSearcher types. However, the code here is much clearer and easier to read. Interestingly, all four methods— GetRelated , GetRelationships , GetRelatedClasses and GetRelationshipClasses —are implemented in terms of ASSOCIATORS OF and REFERENCES OF WQL queries, although these implementation details are hidden from a casual user. Basically, unless you need to parse an existing query, consider using the relationship methods of either the ManagementObject or ManagementClass types before you resort to queries. You will end up with much simpler code, or at least save yourself some typing.
|
. NET System Management Services Authors: Golomshtok A. Published year: 2005 Pages: 30/79 |