Tuesday, September 25, 2007

Watching Files & Registry keys from .Net (incl IronPython)

Registry watching

As I've noted before, the System.Management namespace has a lot of WMI-related goodies in it. In particular it includes analogues for the RegistryTreeChangeEvent, RegistryKeyChangeEvent and RegistryValueChangeEvent behaviours.

You can get at them as follows

  1. Create a suitable  System.Management.ManagementScope object -- e.g. scope = new ManagementScope("root\\default");
  2. Create a new WqlEventQuery, call it q, and set the EventClass like q.EventClassName = "RegistryValueChangeEvent";
  3. Set the appropriate condition depending on the event class -- 
    • For Tree change q.Condition = "Hive='HKEY_LOCAL_MACHINE' and RootPath='Software\\\\RavnaAndTines' ";
    • For Key change q.Condition = "Hive='HKEY_LOCAL_MACHINE' and KeyPath='Software\\\\RavnaAndTines' ";
    • For Value change q.Condition = "Hive='HKEY_LOCAL_MACHINE' and KeyPath='Software\\\\RavnaAndTines' and ValueName='my key'";
    where the path '\' separators must be double escaped (or escaped even if you use an @-qualified string in C#)
  4. Create a ManagementEventWatcher(scope, q) and add an appropriate EventArrivedEventHandler to the EventArrived property
  5. Start() the watcher (probably in its own thread, unless you can create it before the application main loop).
  6. As part of application termination, Stop() the watcher and Dispose() it.

Alternatively, the WqlEventQuery query string could be set manually, in which case it would be of form like

select * from RegistryTreeChangeEvent where Hive='HKEY_LOCAL_MACHINE' and RootPath='Software\\RavnaAndTines' 

Presumably with more SQL-fu one could build a portmanteau query to look at multiple events, rather than creating one watcher per key or value.

The event handler looks like EventArrived(object sender, EventArrivedEventArgs e); the latter argument's NewEvent property is a name/value collection representing the WMI event, like

For Tree change

RootPath = Software\RavnaAndTines
TIME_CREATED = 128351151930418266

For Key change

KeyPath = Software\RavnaAndTines
TIME_CREATED = 128351153191840662

For Value change

KeyPath = Software\RavnaAndTines
TIME_CREATED = 128351161584291834
ValueName = my key

and can obviously be common code for all watcher objects

Sample code

IronPython script used for rapid prototyping/validation

import clr
from System.Management import *
from System import Console

def EvLogEventArrived(sender, e):
##Get the Event object and display it
for pd in e.NewEvent.Properties:
Console.WriteLine("{0} = {1}", pd.Name, pd.Value)

scope = ManagementScope("root\\default");
q = WqlEventQuery();
q.EventClassName = "RegistryValueChangeEvent"
q.Condition = "Hive='HKEY_LOCAL_MACHINE' and KeyPath='Software\\\\RavnaAndTines'"+
" and ValueName='my key'";
w = ManagementEventWatcher(scope, q)
w.EventArrived += EventArrivedEventHandler(EvLogEventArrived)
Console.ReadLine() ## Block this thread, keeping the app active
##and letting the event be handled on a system thread

File System watching

This is much simpler, in that the behaviour is kept into one object (System.IO.FileSystemWatcher).  there are complexities about multi-threading (if the object is being invoked in conjunction with UI objects); and for handling situations where there is a high frequency of changes expected (by configuring how much space to devote to buffering events).

The FileSystemWatcher is supplied with a directory path, a flag to say whether it should recurse through sub-directories, and a simple filter string to determine which files to watch.  Event handlers are then added to the appropriate property (Changed, Created, Deleted, Renamed). The object is then enabled by setting its EnableRaisingEvents property.

The object is, like the ManagementEventWatcher, an IDisposable, but is stopped by clearing the EnableRaisingEvents property.

The event handler looks like EventArrived(object sender, FileSystemEventArgs e); this gives the type of event and the path and name of the file (the new name in the case of a rename, the object being sub-typed to also have an old name and old path property).

Post a Comment