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

Hive = HKEY_LOCAL_MACHINE
RootPath = Software\RavnaAndTines
SECURITY_DESCRIPTOR =
TIME_CREATED = 128351151930418266

For Key change

Hive = HKEY_LOCAL_MACHINE
KeyPath = Software\RavnaAndTines
SECURITY_DESCRIPTOR =
TIME_CREATED = 128351153191840662

For Value change

Hive = HKEY_LOCAL_MACHINE
KeyPath = Software\RavnaAndTines
SECURITY_DESCRIPTOR =
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

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).

3 comments :

Anonymous said...

Great article. Did convert it into .Net (C#) without a problem. I've searced alot on this topic on the web but couldn't find anything working but your code. I am also pasting my code. Again Thank you.


private static void MonitorRegistryEvents()
{
string QueryStr = "Select * From RegistryKeyChangeEvent Where Hive='HKEY_LOCAL_MACHINE' And KeyPath='SOFTWARE\\\\Yahoo\\\\Companion'";
EventQuery Eq = new EventQuery(QueryStr);

ManagementScope Ms = new ManagementScope("ROOT\\DEFAULT");
ManagementEventWatcher Ew = new ManagementEventWatcher(Ms, Eq);

Ew.EventArrived += new EventArrivedEventHandler(Ew_EventArrived);
Ew.Start();
Console.ReadLine();
Ew.Stop();
}


static void Ew_EventArrived(object sender, EventArrivedEventArgs e)
{
foreach (PropertyData p in e.NewEvent.Properties)
{
Console.WriteLine("{0}, {1}", p.Name, p.Value);
}
}

Steve Gilham said...

Actually, the IronPython code was adapted from a sample somewhere in the MSDN documentation for System.Management, but the original might have been in VB.

I've said it before, but it needs saying often -- there is a lot of good stuff hidden in System.Management that up to now only admins with VB (and recently PowerShell) have known about, because they know of it as WMI (whereas most C# programmers know it as "LOL, WUT!?).

John Williams said...

Superb