Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Asynchronous Registry Notification Using Strongly-typed WMI Classes in .NET

4.83/5 (17 votes)
14 Nov 2008CPOL6 min read 134.1K   4.6K  
How to receive asynchronous events about Registry changes, using WMI.

Sample Image - maximum width is 600 pixels

Contents

Introduction

Do you want your application to be notified when there is a change in the Windows Registry? Do you need to watch a specific value in the Registry and get notified when it is changed/deleted/renamed? If yes, you can use the component presented in this article.

Background

The component in this article uses WMI events in order to get notified when the system Registry is changed, so a basic understanding of WMI and events in WMI is desired.

How the Component Works

WMI classes

The Registry provider supplies four event classes for events in the system Registry. All of them reside in the root\DEFAULT namespace. The Registry event classes in WMI are: RegistryEvent, RegistryTreeChangeEvent, RegistryKeyChangeEvent, and RegistryValueChangeEvent. Here is a short description of these classes: RegistryEvent is an abstract base class for changes in the Registry. RegistryTreeChangeEvent is a class that monitors changes to a hierarchy of keys. RegistryKeyChangeEvent monitors changes to a single key. RegistryValueChangeEvent monitors changes to a single value. All these classes have a property called Hive that identifies the hierarchy of keys to be monitored for change. HKEY_CLASSES_ROOT and HKEY_CURRENT_USER are not supported by these classes and changes can not be detected.

Handling WMI Events in .NET

In order to handle WMI events in .NET, we can use classes from the System.Management namespace. For example, this is a piece of code produced by the WMI Code Creator:

C#
using System;
using System.Management;
using System.Windows.Forms;

namespace WMISample
{
  public class WMIReceiveEvent
  {
    public static void Main()
    {
      try
      {
        //Construct the query. Keypath specifies the key in the registry to watch.
        //Note the KeyPath should be must have backslashes escaped. Otherwise 
        //you will get ManagementException.
        WqlEventQuery query = new WqlEventQuery(
                  "SELECT * FROM RegistryKeyChangeEvent WHERE " +
                  "Hive = 'HKEY_LOCAL_MACHINE'" +
                 @"AND KeyPath = 'SOFTWARE\\Microsoft\\.NETFramework'");

         ManagementEventWatcher watcher = new ManagementEventWatcher(query);
         Console.WriteLine("Waiting for an event...");

         ManagementBaseObject eventObj = watcher.WaitForNextEvent();

         Console.WriteLine("{0} event occurred.", eventObj["__CLASS"]);

         // Cancel the event subscription
         watcher.Stop();
         return;
      }
      catch(ManagementException err)
      {
          MessageBox.Show("An error occurred while trying to receive an event: " + 
        err.Message);
      }
    }
  }
}

The above code works, but has two major drawbacks:

  • Events are received synchronously
  • Weakly typed WMI classes are used

Becoming Asynchronous

In order to receive events asynchronously, you should subscribe to the EventArrived event of the ManagementEventWatcher class instead of calling the WaitForNextEvent method which blocks the thread. After subscribing to the event, you should call the Start method of the ManagementEventWatcher class. Calling the Stop method will stop listening to events. The event is fired when a new WMI event arrives. Data about the event can be retrieved through the NewEvent property of the EventArrivedEventArgs class. Here is how it works:

C#
using System;
using System.Management;
using System.Windows.Forms;

namespace WMISample
{
  public class WMIReceiveEvent
  {
    public WMIReceiveEvent()
    {
      try
      {
        //Construct the query. Keypath specifies the key in the registry to watch.
        //Note the KeyPath should be must have backslashes escaped. Otherwise you 
        //will get ManagementException.
        WqlEventQuery query = new WqlEventQuery(
                "SELECT * FROM RegistryKeyChangeEvent WHERE " +
               "Hive = 'HKEY_LOCAL_MACHINE'" +
              @"AND KeyPath = 'SOFTWARE\\Microsoft\\.NETFramework'");

        ManagementEventWatcher watcher = new ManagementEventWatcher(query);
        Console.WriteLine("Waiting for an event...");

        watcher.EventArrived += new EventArrivedEventHandler(HandleEvent);

        // Start listening for events
        watcher.Start();

        // Do something while waiting for events
        System.Threading.Thread.Sleep(10000);

        // Stop listening for events
        watcher.Stop();
        return;
      }
      catch(ManagementException err)
      {
        MessageBox.Show("An error occurred while trying to receive an event: " + 
        err.Message);
      }
    }
        
    private void HandleEvent(object sender,
            EventArrivedEventArgs e)
    {
      Console.WriteLine("RegistryKeyChangeEvent event occurred.");
    }

    public static void Main()
    {
      WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
      return;
    }
  }
}

From Weakly Typed Classes to Strongly Typed WMI Classes

Strongly-typed WMI classes can be generated by using the Mgmtclassgen.exe tool which is included in the .NET Framework SDK. Every property and every method in the WMI class has an equivalent property and method in the generated class. These are the classes generated by this tool: RegistryEvent, RegistryKeyChangeEvent, RegistryTreeChangeEvent, and RegistryValueChangeEvent. They can be found in the WMI classes folder.

The Component

In order to get notified when the Registry changes, you do not need to create the query yourself and subscribe to the event. You just pass the Hive and KeyPath you want to watch, to the component presented in this article. The component consists of an abstract class RegistryChangeBase which inherits from ManagementEventWatcher. There are three classes inheriting from RegistryChangeBase: RegistryKeyChange, RegistryTreeChange, and RegistryValueChange. All of these classes provide an event which is raised when the corresponding event is detected by WMI. This is how it all works:

C#
public class RegistryKeyChange : RegistryChangeBase
{
  public event EventHandler<RegistryKeyChangedEventArgs> RegistryKeyChanged;

  //Template for the query
  private const string queryString = 
    "SELECT * FROM RegistryKeyChangeEvent WHERE Hive = '{0}' AND ({1})";

  public RegistryKeyChange(string Hive, string KeyPath)
      : this(Hive, new List<string>(new string[] { KeyPath }))
  { }

  //Base class constructor does basic validation of the parameters passed.
  public RegistryKeyChange(string Hive, List<string> KeyPathCollection)
      : base(Hive, KeyPathCollection)
  {
    this.Query.QueryString = BuildQueryString(Hive, KeyPathCollection);

    this.EventArrived += new EventArrivedEventHandler(RegistryKeyChange_EventArrived);
  }

  private void RegistryKeyChange_EventArrived(object sender, EventArrivedEventArgs e)
  {
    //Retrieve data about the event and create instance of strongly typed WMI class.
    //Then raise the event.
    RegistryKeyChangeEvent RegValueChange = new RegistryKeyChangeEvent(e.NewEvent);

    OnRegistryKeyChanged(RegValueChange);
  }

  protected virtual void OnRegistryKeyChanged(RegistryKeyChangeEvent RegValueChange)
  {
    if (RegistryKeyChanged != null)
    {
      RegistryKeyChanged(this, new RegistryKeyChangedEventArgs(RegValueChange));
    }
  }
}

As you can see from the above code, the constructor of the class calls a method that builds the query string and sets up the event handler for the EventArrived event. In the event handler, it uses the data of the event to create a new instance of the RegistryKeyChangeEvent class which is a strongly typed wrapper around the WMI class. After that, it just raises the event if there are any subscribers. Other classes inheriting from RegistryChangeBase are designed in the same way.

Creating a Proper WHERE Clause

When building queries for receiving WMI events, the where clause must contain the values for every property in the specified event class. Moreover, the system Registry provider must be able to build a list of possible values for each property. Here are some examples:

SQL
SELECT * FROM RegistryTreeChangeEvent 
         WHERE Hive = 'HKEY_LOCAL_MACHINE' AND Rootpath = 'Software'

SELECT * FROM RegistryTreeChangeEvent WHERE (hive = 'hkey_local_machine' 
         AND rootpath = 'software') OR 
         (hive = 'hkey_current_user' AND rootpath = 'console')

SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_LOCAL_MACHINE' AND 
         KeyPath = 'SOFTWARE\\MICROSOFT\\WBEM\\CIMOM' AND 
         (ValueName = 'Backup Interval Threshold' OR ValueName = 'Logging')

This shows a query that will not work:

SQL
SELECT * FROM RegistryTreeChangeEvent 
         WHERE hive = hkey_local_machine' OR rootpath ='software'

You can find detailed information here: Creating a Proper WHERE Clause for the Registry Provider

The constructor of the RegistryChangeBase class ensures that these conditions are met when constructing queries.

Getting the Component to Work on XP

When I initially published the article, the component worked on Vista but not on XP. This was really strange as usually things break on Vista and work on XP but not the other way around. In his comment Uros Calakovic posted how to make the component work in XP. The only thing that I changed is adding the following line to constructor of RegistryChangeBase class:

C#
this.Scope.Path.NamespacePath = @"root\default";

Using the Code

Using the classes should be quite straightforward. Just choose the class you need according to what kind of notification you need to receive, create an instance, subscribe to the RegistryXXXChanged event, and call the Start method to start receiving notification. Here is an example:

C#
Subscribe()
{
  RegistryKeyChange keychange = 
    new RegistryKeyChange("HKEY_LOCAL_MACHINE", @"SOFTWARE\\Microsoft");
  keychange.RegistryKeyChanged += 
    new EventHandler<RegistryKeyChangedEventArgs>(keychange_RegistryKeyChanged);
  keychange.Start();
}

void keychange_RegistryKeyChanged(object sender, RegistryKeyChangedEventArgs e)
{
  lsbDisplay.Invoke((MethodInvoker)(() =>

  {
    lsbDisplay.Items.Add(string.Format("RegistryKeyChangeEvent detected at {0} 
                                        Event data: Hive: {1} KeyPath: {2}",
             DateTime.FromFileTime((long)e.RegistryKeyChangeData.TIME_CREATED).ToString(),
             e.RegistryKeyChangeData.Hive,
             e.RegistryKeyChangeData.KeyPath));
  }));
}

As the events are fired asynchronously, you cannot access controls created by other threads directly. Note that the parameter of the event handler is an instance of the strongly typed WMI class. Also note the use of the TIME_CREATED property to retrieve the time when the event was generated. To stop receiving notifications, just call Stop method.

Constructors that accept List<String> as one of the parameters can be used to build queries like this:

SQL
SELECT * FROM RegistryKeyChangeEvent WHERE 
   hive = 'hkey_local_machine' AND (keypath = 'software' OR keypath = 'console')

The RegistryValueChange class allows to build queries like this:

SQL
SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_LOCAL_MACHINE' AND 
       KeyPath = 'SOFTWARE\\MICROSOFT\\WBEM\\CIMOM' AND 
       (ValueName = 'Backup Interval Threshold' OR ValueName = 'Logging')

NSFAQ (Not So Frequently Asked Questions)

Here are some questions that no one has asked yet, but I suspect they will be asked.

  • Is it possible to find out which application triggered a Registry event?
  • Not with this component.

  • Is it possible to cancel the event?
  • Not with this component.

  • Is there any other way to get notified when the Registry changes?
  • Yes, there is. Have a look at this article: RegistryMonitor - a .NET wrapper class for RegNotifyChangeKeyValue.

  • I need to get notified when there is a change in the HKEY_CURRENT_USER hive, but it is not supported by this component. What should I do?
  • Read the answer to a previous question.

  • Which platforms are supported?
  • I have tested this component on 64 bit Vista Ultimate SP1 and XP (see history). It should also work on other platforms where WMI is present.

  • When I rename a key in the Registry, I get two events. Why?
  • When you rename a key, it is deleted and a key with a new name is created.

References

History

  • November 2, 2008 - Initial release.
  • November 14, 2008 - Version 1.1
    • Now works on XP too. Credits go to Uros Calakovic - Urke for posting about it in comments

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)