#簡単な方法
ManagementEventWatcher Class
クエリに基づいて、Windows上のイベントを拾えるみたいです。
RegistryKey Hive = Registry.LocalMachine;
string KeyPath = @"SYSTEM\\CurrentControlSet\\Services\\Test";
string ValueName = "Trial";
ManagementEventWatcher watcher = new ManagementEventWatcher();
// キーを監視する場合
watcher.Query = new EventQuery($"SELECT * FROM RegistryKeyChangeEvent WHERE Hive='{Hive.Name}' AND KeyPath='{KeyPath}'");
// 値を監視する場合
watcher.Query = new EventQuery($"SELECT * FROM RegistryValueChangeEvent WHERE Hive='{Hive.Name}' AND KeyPath='{KeyPath}' AND ValueName='{ValueName}'");
watcher.EventArrived += (sender, args) =>
{
Console.WriteLine("Changed");
};
watcher.Start();
この方法には問題点があって、64bitのOS上で実行するアプリケーションがx86だった場合でかつ、WoW6432Nodeが存在しうるキーパスを監視する場合、必ずWoW6432Nodeの中を監視してしまいます。その場合は次のようにWin32APIをP/Invokeして使う必要があります。
#面倒な方法
簡単な方法と違って数行で済まないため、出来合いのクラスを貼っておきます。
レジストリの変更とキャンセルの通知どちらかの変更を、新たなスレッドでループさせて待ちます。変更が加えられるとWaitしていた部分を通過してイベントを起こします。
RegistryKeyWatcher.cs
using System;
using System.Threading.Tasks;
using Microsoft.Win32;
namespace RegistryWatchers.Win32
{
internal class RegistryKeyWatcher : IDisposable
{
internal event EventHandler<RegistryKeyChangedEventArgs> RegistryKeyChanged;
private RegistryHive Hive;
private RegistryView View;
private string KeyPath;
private IntPtr _cancelEvent;
internal RegistryKeyWatcher(RegistryHive hive, RegistryView view, string keyPath)
{
this.Hive = hive;
this.View = view;
this.KeyPath = keyPath;
}
internal void Cancel()
{
if (_cancelEvent != IntPtr.Zero)
{
NativeMethods.SetEvent(_cancelEvent);
IsRunning = false;
}
}
internal bool IsRunning { get; private set; } = false;
internal void Start()
{
if (!IsRunning)
{
_cancelEvent = NativeMethods.CreateEvent(IntPtr.Zero, false, false, Guid.NewGuid().ToString());
Task.Run(() =>
{
Console.WriteLine($"Registry Key Watcher task is started");
IsRunning = true;
IntPtr regEvent = NativeMethods.CreateEvent(IntPtr.Zero, false, false, Guid.NewGuid().ToString());
IntPtr reg = IntPtr.Zero;
IntPtr[] waitEvents = new IntPtr[] { regEvent, _cancelEvent };
UIntPtr hive = UIntPtr.Zero;
if (Hive == RegistryHive.LocalMachine)
{
hive = NativeMethods.Registry.HKEY_LOCAL_MACHINE;
}
else if (Hive == RegistryHive.CurrentUser)
{
hive = NativeMethods.Registry.HKEY_CURRENT_USER;
}
else if (Hive == RegistryHive.Users)
{
hive = NativeMethods.Registry.HKEY_USERS;
}
if (hive == UIntPtr.Zero)
{
Console.WriteLine($"Failed to get hive pointer: Hive={Hive.ToString()}");
return;
}
if (0 != NativeMethods.Registry.RegOpenKeyEx(
hive,
KeyPath,
0,
(int)NativeMethods.Registry.RegistryRights.ReadKey | (View == RegistryView.Registry64 ? (int)NativeMethods.Registry.RegWow64Options.KEY_WOW64_64KEY : (int)NativeMethods.Registry.RegWow64Options.KEY_WOW64_32KEY),
out reg))
{
Console.WriteLine($"Failed to open registry: Hive={Hive.ToString()}, KeyPath={KeyPath}");
return;
}
while (true)
{
if (0 != NativeMethods.Registry.RegNotifyChangeKeyValue(reg, false, NativeMethods.Registry.REG_NOTIFY_CHANGE.LAST_SET, regEvent, true))
{
Console.WriteLine($"Failed to register RegNotify: Hive={Hive.ToString()}, KeyPath={KeyPath}");
break;
}
if (NativeMethods.WAIT_OBJECT_0 != NativeMethods.WaitForMultipleObjects((uint)waitEvents.Length, waitEvents, false, NativeMethods.INFINITE))
{
Console.WriteLine($"Registry Monitor Exit.");
break;
}
RegistryKeyChanged?.Invoke(this, new RegistryKeyChangedEventArgs(Hive.ToString(), KeyPath));
}
if (reg != IntPtr.Zero)
{
NativeMethods.CloseHandle(reg);
reg = IntPtr.Zero;
}
if (regEvent != IntPtr.Zero)
{
NativeMethods.CloseHandle(regEvent);
regEvent = IntPtr.Zero;
}
if (_cancelEvent != IntPtr.Zero)
{
NativeMethods.CloseHandle(_cancelEvent);
_cancelEvent = IntPtr.Zero;
}
Console.WriteLine($"Registry Key Watcher task is finished");
});
}
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (_cancelEvent != IntPtr.Zero)
{
NativeMethods.CloseHandle(_cancelEvent);
_cancelEvent = IntPtr.Zero;
}
disposedValue = true;
}
}
~RegistryKeyWatcher()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
NativeMethods.cs
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace RegistryWatchers.Win32
{
internal static class NativeMethods
{
internal static class Registry
{
internal enum RegWow64Options
{
None = 0,
KEY_WOW64_64KEY = 0x0100,
KEY_WOW64_32KEY = 0x0200
}
internal enum RegistryRights
{
ReadKey = 0x20019,
WriteKey = 0x20006
}
internal static UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u);
internal static UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);
internal static UIntPtr HKEY_USERS = new UIntPtr(0x80000003u);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
internal static extern int RegOpenKeyEx(
UIntPtr hKey,
string subKey,
int ulOptions,
int samDesired,
out IntPtr hkResult);
[DllImport("Advapi32.dll")]
internal static extern int RegNotifyChangeKeyValue(
IntPtr hKey,
bool watchSubtree,
REG_NOTIFY_CHANGE notifyFilter,
IntPtr hEvent,
bool asynchronous
);
[Flags]
internal enum REG_NOTIFY_CHANGE : uint
{
/// <summary>
/// Notify the caller if a subkey is added or deleted
/// </summary>
NAME = 0x1,
/// <summary>
/// Notify the caller of changes to the attributes of the key,
/// such as the security descriptor information
/// </summary>
ATTRIBUTES = 0x2,
/// <summary>
/// Notify the caller of changes to a value of the key. This can
/// include adding or deleting a value, or changing an existing value
/// </summary>
LAST_SET = 0x4,
/// <summary>
/// Notify the caller of changes to the security descriptor of the key
/// </summary>
SECURITY = 0x8
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);
internal const UInt32 INFINITE = 0xFFFFFFFF;
internal const UInt32 WAIT_ABANDONED = 0x00000080;
internal const UInt32 WAIT_OBJECT_0 = 0x00000000;
internal const UInt32 WAIT_TIMEOUT = 0x00000102;
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName);
[DllImport("kernel32.dll")]
internal static extern uint WaitForMultipleObjects(uint nCount, IntPtr[] lpHandles, bool bWaitAll, uint dwMilliseconds);
[DllImport("KERNEL32.DLL", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern bool CloseHandle(IntPtr hHandle);
[DllImport("kernel32.dll")]
internal static extern bool SetEvent(IntPtr hEvent);
}
}
RegistryKeyChangedEventArgs.cs
using System;
namespace RegistryWatchers.Win32
{
internal class RegistryKeyChangedEventArgs : EventArgs
{
internal string Hive { get; set; }
internal string KeyPath { get; set; }
internal RegistryKeyChangedEventArgs(string hive, string keyPath)
{
Hive = hive;
KeyPath = keyPath;
}
}
}
#64bit上の32bitアプリで64bitのレジストリを取得する時
上記クラスの使用例も兼ねてます
Program.cs
using System;
using Microsoft.Win32;
using RegistryWatchers.Win32;
namespace RegistryWatchers
{
class Program
{
static void Main(string[] args)
{
// Registry64を使用
RegistryKeyWatcher watcher = new RegistryKeyWatcher(RegistryHive.LocalMachine, RegistryView.Registry64, @"SOFTWARE\\Test\\Watch");
watcher.RegistryKeyChanged += (sender, eventArgs) => { Console.WriteLine("Changed"); };
watcher.Start();
// Registry64をBaseKeyとして開いておく
RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
RegistryKey regKey = baseKey.OpenSubKey(@"SOFTWARE\\Test\\Watch");
if (regKey != null)
{
int value = (int) regKey.GetValue("Trial");
}
}
}
}