レジストリの値取得やキーの監視方法まとめ

#簡単な方法

[ManagementEventWatcher Class](https://docs.microsoft.com/ja-jp/dotnet/api/system.management.managementeventwatcher?view=netframework-4.7.2)
クエリに基づいて、Windows上のイベントを拾えるみたいです。


```c#
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していた部分を通過してイベントを起こします。

```c#: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);
        }
    }
}

```

```c#: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);
    }
}
```
```c#: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のレジストリを取得する時
上記クラスの使用例も兼ねてます

```c#: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");
            }
        }
    }
}

```