Search code examples
windowswinapiregistrymonitor

Monitoring registry value to never be changed or deleted


My application uses RegistryMonitor which is a wrapper for RegNotifyChangeKeyValue.

In my case I want a certain key in every HKEY_USERS profile to never be deleted and have a certain string value. I have one monitor for every profile and if anything happens to any of these keys my event handler checks them all and recreates the whole tree if needed. It works fine when I'm changing or deleting the value itself but when I'm deleting the key or any key above it - it works the first time as expected, but after that the monitor for the given profile dies and never works again.

The problem is somewhere in this piece of code:

private void ThreadLoop()
{
    IntPtr registryKey;
    int result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY,
                                out registryKey);
    if (result != 0)
        throw new Win32Exception(result);

    try
    {
        AutoResetEvent _eventNotify = new AutoResetEvent(false);
        WaitHandle[] waitHandles = new WaitHandle[] { _eventNotify, _eventTerminate };
        while (!_eventTerminate.WaitOne(0, true))
        {
            result = RegNotifyChangeKeyValue(registryKey, true, _regFilter, _eventNotify.Handle, true);
            if (result != 0)
                throw new Win32Exception(result);

            if (WaitHandle.WaitAny(waitHandles) == 0)
            {
                OnRegChanged();
            }
        }
    }
    finally
    {
        if (registryKey != IntPtr.Zero)
        {
            RegCloseKey(registryKey);
        }
    }
}

After deletion my handler recreates the whole deleted tree, but after that the result of RegNotifyChangeKeyValue is 1018 (ERROR_KEY_DELETED) and the monitor is dead from this point, and not gonna react to changes in this profile.


Solution

  • When the monitored key is deleted, you have to close your handle to the key and reopen it before you can monitor it again.

    In other words, once RegNotifyChangeKeyValue(registryKey, ...) fails because registryKey is no longer valid, you need to call RegCloseKey(registryKey); and then call either RegOpenKeyEx(..., out registryKey) or RegCreateKeyEx(..., out registryKey) to reopen registryKey before you can then call RegNotifyChangeKeyValue(registryKey, ...) again. For example:

    private void ThreadLoop()
    {
        IntPtr registryKey = IntPtr.Zero;
    
        int result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY, out registryKey);
        if (result != 0)
            throw new Win32Exception(result);
    
        try
        {
            AutoResetEvent _eventNotify = new AutoResetEvent(false);
            WaitHandle[] waitHandles = new WaitHandle[] { _eventNotify, _eventTerminate };
    
            while (!_eventTerminate.WaitOne(0, true))
            {
                result = RegNotifyChangeKeyValue(registryKey, true, _regFilter, _eventNotify.Handle, true);
                if (result != 0)
                {
                    if ((result != ERROR_KEY_DELETED) && (result != ERROR_INVALID_PARAMETER))
                        throw new Win32Exception(result);
    
                    RegCloseKey(registryKey);
                    registryKey = IntPtr.Zero;
    
                    // recreate Registry key as needed ...
    
                    result = RegOpenKeyEx(_registryHive, _registrySubName, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_NOTIFY, out registryKey);
                    if (result != 0)
                        throw new Win32Exception(result);
    
                    _eventNotify.Set();
                }
    
                if (WaitHandle.WaitAny(waitHandles) == 0)
                {
                    OnRegChanged();
                }
            }
        }
        finally
        {
            if (registryKey != IntPtr.Zero)
            {
                RegCloseKey(registryKey);
            }
        }
    }