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





5.00/5 (5投票s)
本文介绍了如何使用 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: 第一个版本