Introduction
Windows Management Instrumentation (WMI) is a C&C infrastructure that is integrated within Windows. It provides three primary capabilities:
- Exposing state information regarding a configurable entity
- Invoking control methods on a configurable entity
- Publishing events from a configurable entity
These facilities are a complete instrumentation solution for any Windows application, and multiple system components expose information through the use of WMI providers. This information can be consumed from a multitude of languages and technologies by WMI consumers, using a standard query language (WQL).
Managed code support for WMI providers has been extended in .NET 3.5, enabling every capability a native WMI provider can support. This functionality resides in the System.Management and System.Management.Instrumentation assemblies, and is generally referred to as the WMI Provider Extensions for .NET.
The primary advantage to using WMI in favor of the multitude of communication technologies that are abound is that WMI is a standardized C&C mechanism which can be consumed by numerous existing C&C frameworks. Another advantage is that most Windows components expose C&C information using WMI, and it is preferable that a single C&C framework is used instead of reinventing a C&C framework for each individual component. This makes a single C&C tool suitable for a variety of configurable and controllable entities.
There is a significant number of resources demonstrating what kind of tasks can be accomplished using WMI: WMI Tasks for Scripts and Applications, WMI and .NET in the MSDN Magazine, WMI Code Creator Spotlight ... However, there is no single resource outlining the exact details of creating every kind of provider supported in the WMI Provider Extensions for managed code.
This article provides guidance and walkthrough steps for implementing a WMI provider in managed code and consuming the information exposed by that provider from managed code.
Hosting Model
WMI providers can be hosted in a separate application, or within the WMI host service. Separately hosted providers are also called decoupled providers. Providers hosted within the WMI host service are also called in-process providers.
Decoupled providers expose their information only as long as the application in which they are hosted is running. In-process providers expose their information as long as the WMI infrastructure on the machine is functioning properly.
Basic Setup Steps
An assembly that contains a WMI provider must be decorated with the [WmiConfiguration]
attribute, specifying the WMI namespace for the provider and the hosting model the provider chooses to use.
The following code demonstrates setting up an assembly that exposes a WMI provider in the “root\MyApplication” namespace, with a decoupled hosting model:
[assembly: WmiConfiguration(@"root\MyApplication",
HostingModel = ManagementHostingModel.Decoupled)]
The following code demonstrates setting up an assembly that exposes a WMI provider in the "root\MyApplication" namespace, with an in-process hosting model requiring the provider to run under the NetworkService account:
[assembly: WmiConfiguration(@"root\MyApplication",
HostingModel = ManagementHostingModel.NetworkService)]
To inform the WMI infrastructure that the assembly contains a WMI provider, the assembly must contain an installer class that can be run by the InstallUtil.exe tool. This installer class must derive from the DefaultManagementInstaller
class and be decorated with the [RunInstaller]
attribute. It does not require any additional functionality.
The following code demonstrates the installer class that must be present in the WMI provider's assembly:
[RunInstaller(true)]
public class MyApplicationManagementInstaller :
DefaultManagementInstaller { }
With these setup steps in place, all that is left is to implement the actual WMI provider (described in the subsequent sections). Once the assembly is complete, it must be registered using the InstallUtil.exe tool. From a Visual Studio command prompt, the following command registers the assembly with the WMI infrastructure:
InstallUtil MyAssembly.dll
To unregister the assembly, the following command can be executed from a Visual Studio command prompt:
InstallUtil /u MyAssembly.dll
Read-Only Information
The most rudimentary kind of WMI provider is a WMI provider that exposes read-only information regarding a configurable entity.
The managed model for this kind of WMI provider consists of implementing a class that exposes the information as read-only properties. The class itself must be decorated with the [ManagementEntity]
attribute, and the properties must be decorated with the [ManagementProbe]
attribute.
When applying the [ManagementEntity]
attribute, you can choose whether the WMI class you are exposing will be a singleton entity, or whether it can have multiple instances. If your WMI class can have multiple instances, one or more of the read-only properties that you provide must be decorated with the [ManagementKey]
attribute. These properties will allow WMI consumers to determine a consistent identity for the instances of your class that they are querying.
A multi-instance WMI provider which does not expose at least one property decorated with the [ManagementKey]
attribute will not function properly at runtime. Any attempt to access the provider will result in an ExecutionEngineException
in the hosting process. The exception details can be examined in the Application Event Log.
A singleton WMI provider which attempts to expose a property decorated with the [ManagementKey]
attribute will fail to register during the installation phase.
Finally, the constructor of your class or a static method on your class must be decorated with the [ManagementBind]
attribute. This specifies that the creation of an object instance when requested by the WMI infrastructure will be directed at the constructor or the static method, respectively.
The following code demonstrates a singleton WMI class that exposes the number of processors available on the machine as a read-only property:
[ManagementEntity(Singleton=true)]
[ManagementQualifier("Description",
Value="Obtain processor information.")]
public class ProcessorInformation
{
[ManagementBind]
public ProcessorInformation()
{
}
[ManagementProbe]
[ManagementQualifier("Descriiption",
Value="The number of processors.")]
public int ProcessorCount
{
get { return Environment.ProcessorCount; }
}
}
To publish the instance to the WMI infrastructure, the hosting process can use the following code:
InstrumentationManager.Publish(new ProcessorInformation());
This simple class can now be registered using InstallUtil.exe and then queried from a WMI consumer. Note the use of the [ManagementQualifier]
attribute to add description information to the class and property levels.
The following code demonstrates a multi-instance WMI class that exposes the thread count in each process running on the system as a read-only property.
[ManagementEntity]
public class ProcessInformation
{
Process _theProcess;
[ManagementBind]
public ProcessInformation(
[ManagementName("Id")] int id)
{
_theProcess = Process.GetProcessById(id);
}
[ManagementKey]
public int Id
{
get { return _theProcess.Id; }
}
[ManagementProbe]
public int ThreadCount
{
get { return _theProcess.Threads.Count; }
}
}
To publish the process instances to the WMI infrastructure, the hosting process can use the following code:
foreach (Process p in Process.GetProcesses())
{
InstrumentationManager.Publish(
new ProcessInformation(p.Id));
}
Changes to object instances made by InstrumentationManager.Publish
(publishing an object instance) or by InstrumentationManager.Revoke
(revoking an object instance) are often not effective immediately, but with a delay of a few seconds. When consuming WMI instances, a retry or timeout mechanism should be implemented when the object is initially published, to alleviate this delay.
Consuming the information exposed by the provider developed in the previous section can be performed using the ManagementObjectSearcher
, ManagementObject
, and other types. Additionally, there are numerous tools to aid in the development and testing of WMI providers, such as the WMIC built-in tool, or the GUI WMI Tools package. The Visual Studio Server Explorer can also generate a managed class wrapping a WMI provider.
Opening an administrator command prompt and executing the following command will yield a detailed list of ProcessInformation
instances defined in the previous section:
wmic /namespace:\\root\MyApplication path ProcessInformation
The WMIC tool supports query syntax for specifying conditions, as well:
wmic /namespace:\\root\MyApplication path ProcessInformation where Id=4
Alternatively, the following managed code can output the ProcessInformation
objects:
ManagementObjectSearcher searcher =
new ManagementObjectSearcher(@"root\MyApplication",
"SELECT * FROM ProcessInformation");
foreach (ManagementObject obj in searcher.Get())
Console.WriteLine("Id: " + obj["Id"] +
", ThreadCount: " + obj["ThreadCount"]);
If you’re looking for a strongly-typed accessor, there’s no need to generate it manually. Within Visual Studio’s Server Explorer, expand your server’s node, right click the Management Classes node, and choose Add Classes. Navigate to the root\MyApplication namespace and choose the ProcessInformation
class, click Add, and dismiss the dialog. Now, right-click the ProcessInformation node and select Generate Managed Class. Visual Studio automatically adds to your project a Component
-derived class that enables strongly-typed access to the WMI provider. Now, the following code can replace the ManagementObjectSearcher
-based approach:
foreach (ROOT.MYAPPLICATION.ProcessInformation info in
ROOT.MYAPPLICATION.ProcessInformation.GetInstances())
{
Console.WriteLine("Id: " + info.Id +
", ThreadCount: " + info.ThreadCount);
}
Note that the underlying accessor still uses the weakly-typed ManagementObject
, so using the accessor does not provide performance benefits – only correctness and ease of use. Throughout the rest of this text, the weakly-typed approach will not be demonstrated at all for brevity purposes.
Read-Write Information
In the previous section, we have looked into implementing a rudimentary WMI provider which exposes read-only information. In this section, we will make our provider more interesting by exposing read-write information.
Read-write information is exposed by defining a read-write property and decorating it with the [ManagementConfiguration]
attribute. The [ManagementEntity]
, [ManagementBind]
, and [ManagementKey]
requirements described in the previous post must still hold.
The following code demonstrates a WMI provider which exposes a read-write property controlling the brightness of the screen. For demonstration purposes, the code to modify screen brightness has been omitted from the sample.
[ManagementEntity]
public class BrightnessController
{
[ManagementBind]
public BrightnessController()
{
Id = 1; BrightnessLevel = 500;
}
[ManagementKey]
public int Id { get; private set; }
[ManagementConfiguration]
public int BrightnessLevel { get; set; }
}
Due to limitations in the current WMI Provider Extensions for .NET implementation, a singleton WMI provider cannot expose any updateable properties. Attempting to update a property exposed by a singleton WMI provider will result in an ExecutionEngineException at runtime.
The WMIC tool has the capability of writing values to writeable properties of a WMI provider. The following command executed from a command prompt will modify the BrightnessLevel
value depicted in the previous section.
wmic /namespace:\\root\MyApplication path BrightnessController.Id=1
set BrightnessLevel=400
Note the use of the .Id=1 syntax to specify which brightness controller instance we are updating. Alternatively, query syntax can also be used:
wmic /namespace:\\root\MyApplication path BrightnessController where Id=1
set BrightnessLevel=400
Modifying updateable information from managed code generated by Visual Studio is as easy as writing a value to a writeable property. After generating the managed class, the following code will update the brightness controller’s brightness level:
foreach (ROOT.MYAPPLICATION.BrightnessController
controller in
ROOT.MYAPPLICATION.BrightnessController.GetInstances(
"Id = 1"))
{
controller.BrightnessLevel = 400;
}
Methods
In the previous section, we have exposed read-write properties from a WMI provider. Setting a property value is similar to invoking a method; however, similarly to the semantic difference between properties and methods in .NET, there is a difference between properties and methods in the WMI taxonomy.
In this section, we will define a method exposed by our WMI provider by decorating it with the [ManagementTask]
attribute. The [ManagementEntity]
, [ManagementBind]
, and [ManagementKey]
requirements described in the previous sections must still hold.
The following code demonstrates a WMI provider which exposes a PrintToConsole
method which outputs the specified string to the console. This would clearly be a bad idea for an in-process provider (which has no console), but will work just fine for a decoupled provider hosted in a console application.
[ManagementEntity]
public class Printer
{
[ManagementBind]
public Printer([ManagementName("Id")] int id)
{
Id = id;
}
[ManagementKey]
public int Id;
[ManagementTask]
public void PrintToScreen(string s)
{
Console.WriteLine(s);
}
}
Due to limitations in the current WMI Provider Extensions for .NET implementation, a singleton WMI provider cannot expose any methods. Attempting to call a method exposed by a singleton WMI provider will result in an ExecutionEngineException at runtime.
The WMIC tool has the capability of invoking methods exposed by a WMI provider. The following command executed from a command prompt will invoke the PrintToScreen
method depicted in the previous section.
wmic /namespace:\\root\MyApplication path Printer.Id=1 call PrintToScreen Hello!
Invoking provider methods from managed code generated by Visual Studio is as easy as calling a method on an object. After generating the managed class, the following code will invoke the PrintToScreen
method:
foreach (ROOT.MYAPPLICATION.Printer printer in
ROOT.MYAPPLICATION.Printer.GetInstances("Id = 2"))
{
printer.PrintToScreen("Hello!");
}
Events
The previous sections described various mechanisms of communication from the WMI consumer to the WMI provider – including read-only properties, read-write properties, and methods. However, publishing events from a WMI provider is the only scalable option of providing changing contextual information as it occurs. Pulling the information on demand is not an option because it doesn’t scale.
In this post, we will use the BaseEvent
class or the [InstrumentationClass]
attribute to publish events from a WMI provider.
Publishing events from a WMI provider is not supported by the WMI Provider Extensions for .NET 3.5. It is only supported by the .NET 2.0 implementation. Therefore, in order to publish events from your application, the [Instrumented]
assembly-level attribute from .NET 2.0 must be placed on the assembly, and another installer class derived from DefaultManagementProjectInstaller
(as opposed to DefaultManagementInstaller
) must be added to the project. The following code demonstrates the assembly-level attribute and installer class required to support events from a WMI provider:
[assembly: Instrumented(@"root\MyApplication")]
[RunInstaller(true)]
public class OldInstaller :
DefaultManagementProjectInstaller {}
Note that using the [WmiConfiguration]
assembly-level attribute and the DefaultManagementInstaller
-derived installer (which we used in the previous sections) is not enough to publish events. To properly publish events to the required namespace, the [Instrumented]
assembly-level attribute and the DefaultManagementProjectInstaller
-derived installer are required. It is possible to mix both approaches in the same assembly.
An event that can be published from a WMI provider is represented as a simple managed class. The class contains properties which constitute the event data. The following code demonstrates an event class that can be published whenever the CPU temperature rises above a certain threshold:
public class CpuTemperatureAboveThresholdEvent : BaseEvent
{
public float ActualTemperature { get; private set; }
private CpuTemperatureAboveThresholdEvent(
float actualTemperature)
{
ActualTemperature = actualTemperature;
}
public static void Publish(float actualTemperature)
{
new CpuTemperatureAboveThresholdEvent(
actualTemperature).Fire();
}
}
To publish this event, we can use the static Publish
method we have just written:
CpuTemperatureAboveThresholdEvent.Publish(85.0f);
An alternative to deriving the event class from the BaseEvent
type is using the [InstrumentationClass]
attribute and specifying that the instrumentation type for the class is an event. The following code demonstrates how the event can be rewritten using this paradigm:
[InstrumentationClass(InstrumentationType.Event)]
public class CpuTemperatureAboveThresholdEvent
{
public float ActualTemperature { get; private set; }
private CpuTemperatureAboveThresholdEvent(
float actualTemperature)
{
ActualTemperature = actualTemperature;
}
public static void Publish(float actualTemperature)
{
Instrumentation.Fire(
new CpuTemperatureAboveThresholdEvent(
actualTemperature));
}
}
Note that the only changes are that the class doesn’t derive from BaseEvent
, and that to publish the class, the Instrumentation.Fire
method must be called instead of the Fire
method inherited from BaseEvent
.
Visual Studio’s Server Explorer features the support necessary for subscribing to events published by a WMI provider. Right click the Management Events node, and select Add Event Query. In the dialog displayed, search for your class name (in our case, CpuTemperatureAboveThresholdEvent
). A sub-node appears under the Management Events node – this node is the container for the events consumed by Server Explorer. These events should also appear in the Output window as soon as they are published. In a WinForms application or any other designer surface, it’s possible to drag and drop the event query node to create and configure an instance of the ManagementEventWatcher
class, enabling you to consume the event at runtime.
The ManagementEventWatcher
class can be used directly without Server Explorer’s mediation. The following code demonstrates registering to the CpuTemperatureAboveThresholdEvent
described in the previous section and outputting the CPU temperature when the event is published:
ManagementEventWatcher watcher =
new ManagementEventWatcher(@"root\MyApplication",
"SELECT * FROM CpuTemperatureAboveThresholdEvent");
watcher.EventArrived += (o, e) =>
Console.WriteLine(e.NewEvent["ActualTemperature"]);
watcher.Start();
It is time to outline some of the advanced topics that are outside the scope of this series.
References Between WMI Objects
A reference between WMI objects has to be established using a property decorated with a [ManagementReference]
attribute. The attribute’s parameter specifies the type of the referenced object. A standard object reference will not work with WMI without this attribute.
Enumerators
WMI classes can implement an enumeration static method to dynamically discover or create WMI object instances. This can be accomplished by decorating a method with the [ManagementEnumerator]
attribute and returning an IEnumerable
. This static method can then be called instead of issuing a standard object query; it enables dynamically creating the WMI instances instead of publishing them in advance.
Create and Remove Methods
Instance methods on a WMI class can create a new instance of the WMI class. These methods must have the same signature as the class binding constructor (decorated with [ManagementBind]
) and be decorated with the [ManagementCreate]
attribute.
Similarly, a method can be denoted as a cleanup method for a MWI class instance. This method must not have any parameters, and must return void
, and be decorated with the [ManagementRemove]
attribute.
Dynamic Registration
Instead of performing registration with the InstallUtil.exe tool, it is possible to dynamically register and unregister assemblies or types containing WMI provider information. The InstrumentationManager.RegisterAssembly
and InstrumentationManager.UnregisterAssembly
methods provide dynamic registration and unregistration for all the types in the specified assembly. The InstrumentationManager.RegisterType
and InstrumentationManager.UnregisterType
methods provide dynamic registration and unregistration for the specified specific type.
History