LoginSignup
2
3

More than 5 years have passed since last update.

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

Posted at

簡単な方法

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");
            }
        }
    }
}

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3