65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Windows 事务递归删除注册表树

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2022 年 8 月 16 日

MIT

4分钟阅读

viewsIcon

7846

downloadIcon

223

本文介绍了如何使用 Windows 事务在注册表子键下递归删除注册表树,然后删除子键本身。

引言

注册表事务是一项强大的技术。当您要对注册表进行多次更改时,您通常希望它们全部成功或全部失败。您不希望留下一个部分修改的注册表,这可能导致应用程序不再正常工作,甚至无法成功卸载它们。在我的之前的文章中,我解释了注册表和文件访问的事务。在本文中,我提供了一个真实世界的用例,在这种情况下,这种技术确实起到了作用。

背景

最近,我正在编写代码,让一个 COM 服务器从注册的类列表中取消注册自己。由于一个拼写错误,出了一些问题。而且这也不是什么小问题。我的过程包括两个部分。第一部分是删除一个注册表子树,第二部分是一个单独的注册表操作,它依赖于第一部分。

不幸的是,由于拼写错误,我删除了 HKEY_CURRENT_USER,这令人惊讶地起作用了。第二个更改失败,因为拼写错误。如果我使用了事务,那么除了我需要花费 5 分钟寻找拼写错误之外,什么都不会发生。现在,我破坏了我的用户配置文件,这花费了我相当长的时间来恢复。

所以我认为这是一个很好的地方来提供一个健壮的 Win32 帮助函数,将几个操作联系在一起,并同时演示事务的强大功能。

Using the Code

该代码的用例是,我们希望删除子键下的所有注册表键和值,然后删除子键本身。为此,我创建了辅助函数w32_RegDeleteTreeTransacted

//Delete all values and keys underneath a given key and - optionally- the subkey
//itself. This is performed in a transacted way. Either an overall transaction is
//supplied, or a local one is created and transacted locally.
LSTATUS w32_RegDeleteTreeTransacted(
    HKEY hKeyRoot,                              //root of the tree to be deleted
    LPCTSTR subKey,                             //subkey that is to be deleted
    bool deleteSubKey,                          //do we delete the subkey itself 
                                                //or only what's underneath?
    HANDLE transaction = INVALID_HANDLE_VALUE); //transaction 

参数列表很直观。我们希望删除注册表键(通常是像HKEY_CURRENT_USER这样的命名键之一)的子键下的所有内容。这是前两个参数涵盖的内容。deleteSubkey参数指定是否也需要删除subkey本身。

事务参数是可选的。有几种用例,其中操作是可能由一个总体事务涵盖的更大的一组更改的一部分。使用事务参数,此更改可以集成在该总体事务中。

输入验证

    //The root cannot be NULL
    if (hKeyRoot == NULL)
        return ERROR_INVALID_PARAMETER;

    //Do not accidentally delete a root
    if ((hKeyRoot == HKEY_CLASSES_ROOT ||
        hKeyRoot == HKEY_CURRENT_CONFIG ||
        hKeyRoot == HKEY_CURRENT_USER ||
        hKeyRoot == HKEY_LOCAL_MACHINE ||
        hKeyRoot == HKEY_USERS) && subKey == NULL)
        return ERROR_INVALID_PARAMETER;

    //If the subkey itself needs to be deleted, it cannot be null
    if(deleteSubKey && subKey == NULL)
        return ERROR_INVALID_PARAMETER;

显然,子键的根不能是NULL。如果根是已知的注册表键,则子键不能是NULL。如果为基本 API RegDeleteTree 提供了NULL 作为子键,那么它会忽略子键,而是删除根下的所有内容。虽然这在技术上是可能的,但我无法想到一个问题,删除根键下的所有内容是一个有效的解决方案。

最后,正如我提到的,虽然如果根已经是先前打开的注册表键,则不指定子键是合法的,但如果没有提供子键,则无法删除子键。

事务管理

    //If an overall transaction was supplied, we use that.
    //If not, we use a local one.
    HANDLE localTransaction = INVALID_HANDLE_VALUE;
    if (transaction == INVALID_HANDLE_VALUE) {
        localTransaction = w32_CreateTransaction();
        if (localTransaction == INVALID_HANDLE_VALUE)
            return GetLastError();
    }
    else
        localTransaction = transaction;

    // ....

    if (transaction == INVALID_HANDLE_VALUE) {
        //there was a local transaction
        if (status != ERROR_SUCCESS) {
            RollbackTransaction(localTransaction);
            CloseHandle(localTransaction);
            return GetLastError();
        }
        else {
            CommitTransaction(localTransaction);
            CloseHandle(localTransaction);
            return NO_ERROR;
        }
    }

正如我提到的,事务参数是可选的。如果没有提供,这些更改将由一个本地于此函数的函数涵盖,并在函数调用开始时创建。最后,我们根据输入值确定要做什么。如果有总体事务,那么我们什么也不做,并将决定权留给控制总体事务的各方。否则,我们根据错误状态提交或回滚。

执行操作

    //Open a transacted handle and delete everything 
    //underneath the specified key / subkey
    HKEY hKey = NULL;
    DWORD status = ERROR_SUCCESS;
    
    if (status == ERROR_SUCCESS)
        status = RegOpenKeyTransacted(
            hKeyRoot, subKey, 0, KEY_WRITE | KEY_READ, &hKey, localTransaction, NULL);
    if (status == ERROR_SUCCESS)
        status = RegDeleteTree(hKey, NULL);

    //Delete the subkey itself. This function requires subKey to not be NULL.
    if (status == ERROR_SUCCESS) {
        if (deleteSubKey) {
            status = RegDeleteKeyTransacted(
                hKeyRoot, subKey, KEY_WRITE | KEY_READ, 0, localTransaction, NULL);
        }
    }
    
    if(hKey != NULL)
        CloseHandle(hKey);

主要功能是首先打开一个注册表键并删除该键下的所有内容。RegDeleteTree API 本身不支持事务,但这是很酷的事情:它不需要。如果我们将句柄作为事务打开,那么对该句柄的操作将被事务化。

第二部分是删除子键本身。为此,有一个事务 API。

就这样!我们现在有一个辅助函数,它以安全和可预测的方式执行多个操作(删除子键下的树,以及删除子键本身)。把它放在一起,这就是最终的实现

//Win32Helper.h
LSTATUS w32_RegDeleteTreeTransacted(
    HKEY hKeyRoot,                              //root of the tree to be deleted
    LPCTSTR subKey,                             //subkey that is to be deleted
    bool deleteSubKey,                          //do we delete the subkey itself 
                                                //or only what's underneath?
    HANDLE transaction = INVALID_HANDLE_VALUE); //transaction

//win32Helper.cpp
LSTATUS w32_RegDeleteTreeTransacted(
    HKEY hKeyRoot,                              //root of the tree to be deleted
    LPCTSTR subKey,                             //subkey that is to be deleted
    bool deleteSubKey,                          //do we delete the subkey itself 
                                                //or only what's underneath?
    HANDLE transaction)                         //transaction
{
    //The root cannot be NULL
    if (hKeyRoot == NULL)
        return ERROR_INVALID_PARAMETER;

    //Do not accidentally delete a root
    if ((hKeyRoot == HKEY_CLASSES_ROOT ||
        hKeyRoot == HKEY_CURRENT_CONFIG ||
        hKeyRoot == HKEY_CURRENT_USER ||
        hKeyRoot == HKEY_LOCAL_MACHINE ||
        hKeyRoot == HKEY_USERS) && subKey == NULL)
        return ERROR_INVALID_PARAMETER;

    //If the subkey itself needs to be deleted, it cannot be null
    if(deleteSubKey && subKey == NULL)
        return ERROR_INVALID_PARAMETER;

    //If an overall transaction was supplied, we use that.
    //If not, we use a local one.
    HANDLE localTransaction = INVALID_HANDLE_VALUE;
    if (transaction == INVALID_HANDLE_VALUE) {
        localTransaction = w32_CreateTransaction();
        if (localTransaction == INVALID_HANDLE_VALUE)
            return GetLastError();
    }
    else
        localTransaction = transaction;

    //Open a transacted handle and delete everything 
    //underneath the specified key / subkey
    HKEY hKey = NULL;
    DWORD status = ERROR_SUCCESS;
    
    if (status == ERROR_SUCCESS)
        status = RegOpenKeyTransacted(
            hKeyRoot, subKey, 0, KEY_WRITE | KEY_READ, &hKey, localTransaction, NULL);
    if (status == ERROR_SUCCESS)
        status = RegDeleteTree(hKey, NULL);

    //Delete the subkey itself. This function requires subKey to not be NULL.
    if (status == ERROR_SUCCESS) {
        if (deleteSubKey) {
            status = RegDeleteKeyTransacted(
                hKeyRoot, subKey, KEY_WRITE | KEY_READ, 0, localTransaction, NULL);
        }
    }

    if (hKey != NULL)
        CloseHandle(hKey);

    if (transaction == INVALID_HANDLE_VALUE) {
        //there was a local transaction
        if (status != ERROR_SUCCESS) {
            RollbackTransaction(localTransaction);
            CloseHandle(localTransaction);
            return GetLastError();
        }
        else {
            CommitTransaction(localTransaction);
            CloseHandle(localTransaction);
            return NO_ERROR;
        }
    }
    else {
        //The transaction originated outside this function, so do nothing
        return NO_ERROR;
    }
}

关注点

到目前为止,应该很清楚事务的可能性是无穷无尽的。它们确实可以使您的代码更健壮,并防止部分执行的更改破坏您的配置,而无需您自己编写大量额外的回滚代码。

我写这第二篇文章的原因是第一篇文章更通用,而本文解决了我在其中遇到的一个问题。

本文根据 MIT 许可证获得许可。欢迎使用此辅助函数以及Win32Helpers.h/cpp文件中的其他函数,让您的生活更轻松。

历史

  • V1. 17AUG2022: 第一个版本
© . All rights reserved.