美文网首页
Windows端监听注册表事件

Windows端监听注册表事件

作者: MrDecoder | 来源:发表于2019-03-07 20:13 被阅读5次
    • 前言
    • 解决方案
      1. C# API 层面
      2. Win32 API 层面

    前言

    最近项目迎来了文档编辑需求,文档编辑完成上传Server,由于是跨模块交互,了解到SDK与Office插件之间的交互是通过注册表,简要来说就是编辑动作的发生场景是在我们的应用Application.process,一旦触发编辑操作,SDK会往注册表的某个位置动态创建一个Key,然后往创建的Key下写入文件的LocalPath,Offlice插件读取目标项中的路径加载文件,进行编辑。编辑完成删除目标项。

    所以编辑完成的动作改由以目标注册表项被删除的动作表征。任务改由监听注册表【由于其他原因,不能采用直接监听Office进程或者与Office进程建立强连接来通信的方式】。

    • 简单说明
      1. Office的文档格式有三种:PPT,WORD,EXCEL,经研究发现Office插件加载不同文档的方式不同,部分是多文档结构,部分是单文档结构,给直接监听进程增加了难度。
      2. 建立强连接,耦合性会增加,也不是好的解决方案。

    解决方案

    如果能从C#层面入手,那会一定程度减低问题的复杂度

    1. C# API solution

    研究的第一步就是从网上扒拉看是否有类似的Demo,或者去官网查看相应的文档。一般来说,前面部分是前奏,后面的部分辅助之,相辅相成问题解决事半功倍。

    #1.1 Demo Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.ComponentModel;
    using System.Management;
    using Microsoft.Win32;
    using System.Collections.ObjectModel;
    
    namespace ConsoleApp1
    {
        public class RegistryWatcher : ManagementEventWatcher, IDisposable
        {
            static ReadOnlyCollection<RegistryKey> supportedHives = null;
    
            /// <summary> 
            /// Changes to the HKEY_CLASSES_ROOT and HKEY_CURRENT_USER hives are not supported 
            /// by RegistryEvent or classes derived from it, such as RegistryKeyChangeEvent.  
            /// </summary> 
            public static ReadOnlyCollection<RegistryKey> SupportedHives
            {
                get
                {
                    if (supportedHives == null)
                    {
                        RegistryKey[] hives = new RegistryKey[]
                        {
                            Registry.LocalMachine,
                            Registry.CurrentUser,
                            Registry.CurrentConfig
                        };
                        supportedHives = Array.AsReadOnly<RegistryKey>(hives);
                    }
                    return supportedHives;
                }
            }
    
            public RegistryKey Hive { get; private set; }
            public string KeyPath { get; private set; }
            public RegistryKey KeyToMonitor { get; private set; }
    
            public event EventHandler<RegistryKeyChangeEventArgs> RegistryKeyChangeEvent;
    
            /// <exception cref="System.Security.SecurityException"> 
            /// Thrown when current user does not have the permission to access the key  
            /// to monitor. 
            /// </exception>  
            /// <exception cref="System.ArgumentException"> 
            /// Thrown when the key to monitor does not exist. 
            /// </exception>  
            public RegistryWatcher(RegistryKey hive, string keyPath)
            {
                this.Hive = hive;
                this.KeyPath = keyPath;
    
                // If you set the platform of this project to x86 and run it on a 64bit  
                // machine, you will get the Registry Key under  
                // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node when the key path is 
                // HKEY_LOCAL_MACHINE\SOFTWARE 
                this.KeyToMonitor = hive.OpenSubKey(keyPath);
    
                if (KeyToMonitor != null)
                {
                    //           WqlEventQuery query = new WqlEventQuery(
                    //"SELECT * FROM RegistryTreeChangeEvent WHERE " +
                    //"Hive = 'HKEY_LOCAL_MACHINE'" +
                    //@"AND RootPath = 'SOFTWARE\\Classes\\NextLabs.Handler.1\\Shellex' ");
                    // Construct the query string. 
                    string queryString = string.Format(@"SELECT * FROM RegistryTreeChangeEvent  
                       WHERE Hive = '{0}' AND RootPath = '{1}' ", this.Hive.Name, this.KeyPath);
    
                    WqlEventQuery query = new WqlEventQuery
                    {
                        QueryString = queryString,
                        EventClassName = "RegistryTreeChangeEvent",
                        WithinInterval = new TimeSpan(0, 0, 0, 1)
                    };
                    this.Query = query;
    
                    this.EventArrived += new EventArrivedEventHandler(RegistryWatcher_EventArrived);
                }
                else
                {
                    string message = string.Format(
                        @"The registry key {0}\{1} does not exist",
                        hive.Name,
                        keyPath);
                    throw new ArgumentException(message);
                }
            }
    
            void RegistryWatcher_EventArrived(object sender, EventArrivedEventArgs e)
            {
                if (RegistryKeyChangeEvent != null)
                {
                    // Get RegistryKeyChangeEventArgs from EventArrivedEventArgs.NewEvent.Properties. 
                    RegistryKeyChangeEventArgs args = new RegistryKeyChangeEventArgs(e.NewEvent);
    
                    // Raise the event handler.  
                    RegistryKeyChangeEvent(sender, args);
                }
            }
    
            /// <summary> 
            /// Dispose the RegistryKey. 
            /// </summary> 
            public new void Dispose()
            {
                base.Dispose();
                if (this.KeyToMonitor != null)
                {
                    this.KeyToMonitor.Dispose();
                }
            }
        }
    
        public class RegistryKeyChangeEventArgs : EventArgs
        {
            public string Hive { get; set; }
            public string KeyPath { get; set; }
            public uint[] SECURITY_DESCRIPTOR { get; set; }
            public DateTime TIME_CREATED { get; set; }
    
            public RegistryKeyChangeEventArgs(ManagementBaseObject arrivedEvent)
            {
    
                // Class RegistryKeyChangeEvent has following properties: Hive, KeyPath,  
                // SECURITY_DESCRIPTOR and TIME_CREATED. These properties could get from 
                // arrivedEvent.Properties. 
                this.Hive = arrivedEvent.Properties["Hive"].Value as string;
                this.KeyPath = arrivedEvent.Properties["RootPath"].Value as string;
    
                // The property TIME_CREATED is a unique value that indicates the time  
                // when an event is generated.  
                // This is a 64-bit FILETIME value that represents the number of  
                // 100-nanosecond intervals after January 1, 1601. The information is in 
                // the Coordinated Universal Time (UTC) format.  
                this.TIME_CREATED = new DateTime(
                    (long)(ulong)arrivedEvent.Properties["TIME_CREATED"].Value,
                    DateTimeKind.Utc).AddYears(1600);
            }
        }
    }
    
    #1.2 Demo Test
     class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    //HKEY_LOCAL_MACHINE\SOFTWARE\TestForRegistryEvent
                    //Hive_LocalMachine
                    RegistryKey hive1 = RegistryWatcher.SupportedHives[0];
                    var keyPath1 = @"SOFTWARE\\TestForRegistryEvent_HKLM";
    
                    //HKEY_CURRENT_USER\TestForRegistryEvent_HKCU
                    //Hive_CurrentUser
                    RegistryKey hive2 = RegistryWatcher.SupportedHives[1];
                    var keyPath2 = @"TestForRegistryEvent_HKCU";
                    
                    RegistryWatcher watcher = new RegistryWatcher(hive1, keyPath1);
                    watcher.RegistryKeyChangeEvent += OnRegistryKeyEventChanged;
                    watcher.Start();
    
                    // Do something while waiting for events. In your application,
                    // this would just be continuing business as usual.
                    System.Threading.Thread.Sleep(50000);
                    watcher.Stop();
                }
                catch(ManagementException e)
                {
                    Console.WriteLine("An error occured: "+e.Message);
                    Console.ReadKey();
                }
            }
    
            static void OnRegistryKeyEventChanged(object sender, RegistryKeyChangeEventArgs e)
            {
                string newEventMessage = string.Format(@"{0} The key {1}\{2} changed",
                   e.TIME_CREATED.ToLocalTime(), e.Hive, e.KeyPath);
    
                Console.WriteLine(newEventMessage);
                Console.ReadKey();
            }
        }
    
    #1.3 Test result

    我准备了两组测试数据:

    1. HKLM,针对测试目标HKEY_LOCAL_MACHINE\SOFTWARE\TestForRegistryEvent,在监测的Key下增加和删除value。
    Registry_HKLM 监测结果_HKLM.png
    1. HKCU,针对测试目标HKEY_CURRENT_USER\TestForRegistryEvent_HKCU,在监测的Key下增加和删除value。


      Registry_HKCU
    监听结果_HKCU
    #1.4 测试结果分析

    C#层面如果通过往System Registry Provider注册监听来接受Registry Event,只支持Hive type为HKEY_LOCAL_MACHINE。其他的几种Hive type:HKU,HKCU,HKCR,HCC并不支持。

    Registry vent support 说明

    2. Win32 API solution

    为了寻求更通用的Solution,以便能监听任意的注册表项,所以考虑从win32层面来寻求support。通过查询文档发现API中有关注项:

    Notifies the caller about changes to the attributes or contents of a specified registry key.

    LSTATUS RegNotifyChangeKeyValue(
      HKEY   hKey,
      BOOL   bWatchSubtree,
      DWORD  dwNotifyFilter,
      HANDLE hEvent,
      BOOL   fAsynchronous
    );
    
    2.1 Demo Code
    void SampleCppClass::RegisterForEntryPoint(int type, wchar_t *subKey) {
        DWORD dwFilter = REG_NOTIFY_CHANGE_NAME |
            REG_NOTIFY_CHANGE_ATTRIBUTES |
            REG_NOTIFY_CHANGE_LAST_SET |
            REG_NOTIFY_CHANGE_SECURITY;
    
        HANDLE hEvent;
        HKEY hMainKey;
        HKEY hKey;
    
    
        if (type == 0) {
            hMainKey = HKEY_CLASSES_ROOT;
        }
        else if (type == 1) {
            hMainKey = HKEY_CURRENT_USER;
        }
        else if (type == 2) {
            hMainKey = HKEY_LOCAL_MACHINE;
        }
        else if (type == 3) {
            hMainKey = HKEY_USERS;
        }
        else if (type == 4) {
            hMainKey = HKEY_CURRENT_CONFIG;
        }
        else {
            _tprintf(TEXT("Usage: notify [HKLM|HKU|HKCU|HKCR|HCC] [<subkey>]\n"));
            return;
        }
    
        LONG lErrorCode;
        // Open a key.
        lErrorCode = RegOpenKeyEx(hMainKey, subKey, 0, KEY_NOTIFY | KEY_QUERY_VALUE, &hKey);
    
        hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (hEvent == NULL)
        {
            _tprintf(TEXT("Error in CreateEvent (%d).\n"), GetLastError());
            return;
        }
    
        // Watch the registry key for a change of value.
        lErrorCode = RegNotifyChangeKeyValue(hKey,
            TRUE,
            dwFilter,
            hEvent,
            TRUE);
        if (lErrorCode != ERROR_SUCCESS)
        {
            if (lErrorCode == ERROR_INVALID_HANDLE) {
                _tprintf(TEXT("Error in RegNotifyChangeKeyValue The handle is invalid. (%d).\n"), lErrorCode);
                return;
            }
            _tprintf(TEXT("Error in RegNotifyChangeKeyValue (%d).\n"), lErrorCode);
            return;
        }
    
        // Wait for an event to occur.
        _tprintf(TEXT("Waiting for a change in the specified key...\n"));
        if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED)
        {
            _tprintf(TEXT("Error in WaitForSingleObject (%d).\n"), GetLastError());
            return;
        }
        else _tprintf(TEXT("\nChange has occurred.\n"));
        if (m_callback != NULL)
        {
            m_callback(m_pContext);
        }
    
        // Close the key.
        lErrorCode = RegCloseKey(hKey);
        if (lErrorCode != ERROR_SUCCESS)
        {
            _tprintf(TEXT("Error in RegCloseKey (%d).\n"), GetLastError());
            return;
        }
    
        // Close the handle.
        if (!CloseHandle(hEvent))
        {
            _tprintf(TEXT("Error in CloseHandle.\n"));
            return;
        }
    }
    
    #2.2 Demo Test

    在前面的C# code中,HKCU下的监听是不支持的,所以这次针对性进行测试,针对测试目标为:HKEY_CURRENT_USER\TestForRegistryEvent_HKCU,在其中添加和删除value以对相应事件进行测试。

    win32_Test_HKCU

    参考链接
    C#
    Registering for system registry events
    Monitor registry changes (CSMonitorRegistryChange)

    W32
    nf winreg regnotifychangekeyvalue

    相关文章

      网友评论

          本文标题:Windows端监听注册表事件

          本文链接:https://www.haomeiwen.com/subject/sjthpqtx.html