RegEnumKeyEx and RegDeleteKeyEx, Or: Why reading all the documentation for a function is a good idea

RegEnumKeyEx seems simple enough. Open the parent key, call RegEnumKeyEx specifying dwIndex=0, then increment dwIndex and call RegEnumKeyEx until you get ERROR_NO_MORE_ITEMS.

There are, however, two caveats in the documentation that are easy to overlook.

Did you notice that the documentation says “Because subkeys are not ordered, any new subkey will have an arbitrary index. This means that the function may return subkeys in any order”? That’s not so bad; we don’t generally need subkeys in any particular order.

Did also you scroll down far enough to catch the caveat “While an application is using the RegEnumKeyEx function, it should not make calls to any registration functions that might change the key being enumerated”? Ohh… now that is annoying. If we’re reading only, things aren’t too bad; the odds of another process or thread getting in our way are pretty slim. It could be unpleasant if another thread or process mucks with one of “our” key’s subkeys. (I haven’t confirmed this… there may be magic that stops a subkey from being deleted while the parent is being enumerated, but I’m only aware of “open” keys being protected.) If we want to delete subkeys, this becomes important.

Say we have this…

HKLM\SOFTWARE\Test
HKLM\SOFTWARE\Test\Sub1\\Data=”Good”
HKLM\SOFTWARE\Test\Sub2\\Data=”Bad”
HKLM\SOFTWARE\Test\Sub3\\Data=”Good”
HKLM\SOFTWARE\Test\Sub4\\Data=”Bad”

And we want to remove all of the Subkeys of HKLM\SOFTWARE\Test that have the string “Bad” in the REG_SZ value “Data”.

We RegOpenKeyEx HKLM\SOFTWARE\Test with KEY_ALL_ACCESS|DELETE rights and use the handle to the open key to start enumerating happily along. We get to Sub2, and realize that it needs to be deleted. Easy enough, call RegDeleteKeyEx(hKey, _T(“Sub2”), KEY_WOW64_32KEY, 0), and continue iterating. Uh oh…

Sometimes we get lucky, but this is where the combination of “the function may return subkeys in any order” and we “should not make calls to any registration functions that might change the key being enumerated” becomes important. The key returned by the next call to RegEnumKeyEx may be the next key in the list, but it is just as likely that we will skip a key or even get the name of the key that we just deleted.

So, to enumerate and delete some subkeys, here are two options.
1. As soon as you find a key that needs to be deleted, set dwIndex back to 0 and re-enumerate all of the subkeys. This is lame and inefficient, but it may also be the best solution in some cases.
2. Build a list of the subkeys that need to be deleted while enumerating, then iterate through that list and delete each subkey by name.

Not rocket science, clearly documented, but still annoying and easy to overlook.

What do you think?