#define TRACE using System; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Win32; namespace ArchestrAServices.Common; public class RegistryWatcher : IDisposable { private IntPtr intPtrRegistryHive; private string strRegistrySubName; private object objThreadLock = new object(); private Thread workerThread; private bool bDisposed; private ManualResetEvent eventToTerminate = new ManualResetEvent(initialState: false); private bool bEnableRaisingEvents; private RegistryChangeFilter registryChangeFilter = RegistryChangeFilter.Value; private static readonly string wow64OptionalLayer = (Environment.Is64BitProcess ? "Wow6432Node\\" : string.Empty); private const int KEY_QUERY_VALUE = 1; private const int KEY_NOTIFY = 16; private const int STANDARD_RIGHTS_READ = 131072; private static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(int.MinValue); private static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(-2147483647); private static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(-2147483646); private static readonly IntPtr HKEY_USERS = new IntPtr(-2147483645); private static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(-2147483644); private static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(-2147483643); private static readonly IntPtr HKEY_DYN_DATA = new IntPtr(-2147483642); public RegistryChangeFilter RegistryChangeNotifyFilter { get { return registryChangeFilter; } set { lock (objThreadLock) { if (IsMonitoring) { SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 2, "Monitoring thread is already running"); } else { registryChangeFilter = value; } } } } public bool EnableRaisingEvents { get { return bEnableRaisingEvents; } set { if (bEnableRaisingEvents != value) { if (value) { bEnableRaisingEvents = Start(); return; } Stop(); bEnableRaisingEvents = value; } } } private bool IsMonitoring => workerThread != null; private event EventHandler changed; public event EventHandler Changed { add { lock (objThreadLock) { changed += value.Invoke; } } remove { lock (objThreadLock) { changed -= value.Invoke; } } } [DllImport("advapi32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)] private static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int samDesired, out IntPtr phkResult); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegNotifyChangeKeyValue(IntPtr hKey, bool bWatchSubtree, RegistryChangeFilter dwNotifyFilter, IntPtr hEvent, bool fAsynchronous); [DllImport("advapi32.dll", SetLastError = true)] private static extern int RegCloseKey(IntPtr hKey); protected virtual void OnChanged() { EventHandler eventHandler = this.changed; if (eventHandler != null) { lock (objThreadLock) { eventHandler(this, null); } } } public bool SetRegistryPath(RegistryHive registryHive, string subKey) { bool flag = false; try { flag = InitAndValidateRegistryKey(registryHive, subKey); } catch (Exception ex) { flag = false; SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 2, $"SetRegistryPath exception: '{ex.Message}'"); } return flag; } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } private bool InitAndValidateRegistryKey(RegistryHive hive, string name) { bool flag = true; switch (hive) { case RegistryHive.ClassesRoot: intPtrRegistryHive = HKEY_CLASSES_ROOT; break; case RegistryHive.CurrentConfig: intPtrRegistryHive = HKEY_CURRENT_CONFIG; break; case RegistryHive.CurrentUser: intPtrRegistryHive = HKEY_CURRENT_USER; break; case RegistryHive.LocalMachine: intPtrRegistryHive = HKEY_LOCAL_MACHINE; break; case RegistryHive.PerformanceData: intPtrRegistryHive = HKEY_PERFORMANCE_DATA; break; case RegistryHive.Users: intPtrRegistryHive = HKEY_USERS; break; default: flag = false; SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 2, "Invalid rootkey for registry path"); break; } if (flag) { RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(name); if (registryKey != null) { strRegistrySubName = name; registryKey.Close(); } else { flag = false; SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 2, "Invalid subkey for registry path"); } } return flag; } private bool Start() { bool result = false; if (bDisposed) { SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 2, "This instance is already disposed"); } if (!string.IsNullOrEmpty(strRegistrySubName)) { IntPtr phkResult; int num = RegOpenKeyEx(intPtrRegistryHive, strRegistrySubName, 0u, 131089, out phkResult); if (phkResult != IntPtr.Zero) { RegCloseKey(phkResult); } if (num == 0) { lock (objThreadLock) { if (!IsMonitoring) { eventToTerminate.Reset(); workerThread = new Thread(RegWatcherThread); workerThread.IsBackground = true; workerThread.Start(); result = true; } } } } return result; } private void RegWatcherThread() { IntPtr phkResult = IntPtr.Zero; try { if (RegOpenKeyEx(intPtrRegistryHive, strRegistrySubName, 0u, 131089, out phkResult) != 0) { SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 2, "RegOpenKeyEx is failed to open the given registry key path"); } AutoResetEvent autoResetEvent = new AutoResetEvent(initialState: false); WaitHandle[] waitHandles = new WaitHandle[2] { autoResetEvent, eventToTerminate }; int num = ReadWaitTimeout(); while (!eventToTerminate.WaitOne(0, exitContext: true)) { int num2 = RegNotifyChangeKeyValue(phkResult, bWatchSubtree: true, registryChangeFilter, autoResetEvent.SafeWaitHandle.DangerousGetHandle(), fAsynchronous: true); if (num2 != 0) { throw new Win32Exception(num2); } if (WaitHandle.WaitAny(waitHandles) == 0) { while (RegNotifyChangeKeyValue(phkResult, bWatchSubtree: true, RegistryChangeFilter.Value, autoResetEvent.SafeWaitHandle.DangerousGetHandle(), fAsynchronous: true) == 0 && WaitHandle.WaitAny(waitHandles, TimeSpan.FromSeconds(num)) == 0) { } OnChanged(); } } } catch (Exception ex) { SvcTrace.DiagException.TraceEvent(TraceEventType.Error, 2, "MonitorThread exception {0}", ex.Message); } finally { if (phkResult != IntPtr.Zero) { RegCloseKey(phkResult); } workerThread = null; } } private static int ReadWaitTimeout() { int result = 2; try { using RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\" + wow64OptionalLayer + "ArchestrA\\ArchestrAServices", writable: false); object obj = registryKey?.GetValue("DiscoveryChangeNotifyDelay"); if (obj != null && !int.TryParse(obj.ToString(), out result)) { result = 2; } } catch (Exception) { } return result; } private void Stop() { if (bDisposed) { SvcTrace.DiagException.TraceEvent(TraceEventType.Warning, 2, "This instance is already disposed"); return; } lock (objThreadLock) { Thread thread = workerThread; if (thread != null) { eventToTerminate.Set(); thread.Join(); } } } protected virtual void Dispose(bool disposing) { if (!bDisposed) { Stop(); if (disposing && eventToTerminate != null) { eventToTerminate.Dispose(); eventToTerminate = null; } bDisposed = true; } } }