The first stage was to get the LogParser COM interop built. This was pleasantly easy. As simple as running tlbimp "C:Program Files (x86)Log Parser 2.2LogParser.dll" /out:Interop.MSUtil.dll, adding the DLL as a reference to my project, adding using statements to Program.cs, and then writing some code. I started by getting the search going.

You’ll see I explicitly reference the five registry keys in the FROM statement of the query I give LogParser, even though I’m searching the whole registry. This is because when I tried FROM /, I got two results per root key of the registry, one using the abbreviated root key name, one using it’s full name (e.g. I’d get HKEY_CLASSES_ROOTChromeHTMLshellopencommand and HKCRChromeHTMLshellopencommand).

So once I had the code above working, the next step was to actually access and update the keys using Microsoft.Win32.Registry. This proved to be more complex than I had expected as (a) you have to access the root keys as static properties of Registry, and (b) from a particular key, you can only access its immediate subkeys. I’m sure there are libraries that make matters simpler, but working around was easy enough.

To deal with the root keys, I created a dictionary to use to look up root key abbreviations from LogParser, and return the root key objects. I created a recursive function to move through subkeys to finally access the subkey referenced by a path.

So for HKCRChromeHTMLshellopencommand and HKCRChromeHTMLshellopencommand, it’ll split off HKCR and get the root key, call GetSubKey(Registry.ClassesRoot, { "ChromeHTML", "shell", "open", "command" }) which will get the ChromeHTML subkey within HKCR, and call GetSubKey(ChromeHTML, { "shell", "open", "command" }), and so on, until it calls with GetSubKey(open, { "command" }), and which point recursion ends, and the “command” key is opened writable and returned.

From this point things were easy. The only other complication was that LogParser represents the default key as "(Default)", whereas Microsoft.Win32.Registry represents it as string.Empty.

The final code looks like this. Parameterisation, tidying, etc is left as an exercise for the reader.