Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Using Windows Transactions to Recursively Delete a Registry Tree

5.00/5 (5 votes)
16 Aug 2022MIT4 min read 7.5K   168  
This article describes how to use Windows transactions to recursively delete a registry tree under a registry subkey and then the subkey itself.
Using registry transactions, it is possible to chain multiple independent operations together and have them all succeed at once, or have them rolled back if somewhere in the chain there is an error. In a previous article, I explained transactions in general. In this article, I provide a real-world scenario, and describe how transactions can be used to mitigate problems.

Image 1

Introduction

Registry transactions are a powerful technology. Whenever you are making multiple changes to the registry, you typically want them all to succeed or fail together. You don't want to be left with a half modified registry that may result in applications no longer working correctly, or even being unable to uninstall them successfully. In my previous article, I explained transactions for registry and file access. In this article, I am providing a real-world usecase where such technology makes a real difference.

Background

Recently, I was programming the code for a COM server to unregister itself from the registered classes list. Because of a typo, something went wrong. And it was nothing trivial either. My process consisted of two parts. The first part was supposed to delete a registry subtree, the second part was a separate registry action that depended on the first.

Unfortunately, due to the typo, I deleted HKEY_CURRENT_USER which surprisingly worked. The second change failed because of the typo. Had I used transactions, then nothing would have happened except I would have had to spend 5 minutes looking for the typo. Now, I destroyed my user profile, which cost me quite some time to restore.

So I figured this would be a good place to provide a robust Win32 helper function to tie a couple of actions together and demonstrate the power of transactions at the same time.

Using the Code

The use case for the code is that we want to delete all registry keys and values underneath a subkey, and then the subkey itself. For this, I have created the helper function, w32_RegDeleteTreeTransacted.

C++
//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 

The parameter list is fairly self-evident. We want to delete everything under the subkey of a registry key (typically one of the named ones like HKEY_CURRENT_USER). That is the first two parameters covered. The deleteSubkey parameter specifies whether the subkey itself needs to be deleted as well.

The transaction parameter is optional. There are several use cases where the operation is part of a larger set of changes which may be covered by an overall transaction. Using the transaction parameter, this change can be integrated in that overall transaction.

Input Validation

C++
//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;

Obviously, the root of the subkey cannot be NULL. And if the root is a well known registry key, then the subkey cannot be NULL. If a NULL is supplied to the base API RegDeleteTree for the subkey, then it ignores the subkey and instead deletes everything underneath the root. And while that it technically possible, I cannot think of a single problem for which deleting everything under a root key is a valid solution.

And finally, as I mentioned, while it is legal to not specify a subkey if the root is already a previously opened registry key, we cannot delete a subkey if none was supplied.

Transaction Management

C++
//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;
    }
}

As I mentioned, the transaction parameter is optional. If none is supplied, these changes are going to be covered by one that is local to this function and create in the beginning of the function call. At the end, we determine what to do based on the input value. If there was an overall transaction, then we do nothing and leave the decision to the party controlling the overall transaction. Otherwise, we commit or roll back based on the error status.

Doing the Work

C++
//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);

The main functionality is first to open a registry key and delete everything underneath that key. The RegDeleteTree API itself is not transaction aware, but that's the cool thing: it doesn't need to be. If we opened the handle as transacted, then the operations on that handle will be transacted.

The second part is we delete the subkey itself. For this, there is a transacted API.

And that's it! We now have a helper function that performs multiple operations (deleting a tree under a subkey, and deleting the subkey itself) in a safe and predictable manner. Putting it all together, this is the final implementation:

C++
//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;
    }
}

Points of Interest

By now, it should be clear that the possibilities of transactions are endless. They can really make your code more robust and prevent half executed changes from ruining your configuration without requiring you to write a whole lot of additional rollback code yourself.

The reason I wrote this second article is that the first article was more generic, and this article solved a problem I ran into.

The article is licensed under the MIT license. Feel free to use this helper function as well as the others in the Win32Helpers.h/cpp file to make your life easier.

History

  • V1. 17AUG2022: First version

License

This article, along with any associated source code and files, is licensed under The MIT License