Updating the registry using .NET and LogParser

I have discovered a need to be able to search and replace registry values. I originally thought about using Powershell but after reading this blog post about Powershell performance with the registry, I decided to use .NET. I quickly encountered the idea of using LogParser to read the registry at high speed, and decided this was a fruitful avenue.

The background to this need is that when you use a custom profile location, you can only use Chrome as your default browser by editing the registry. I did this manually once. Then when I found the keys had reset themselves, I decided coding something to update the registry for me would be interesting.

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.

using System;
using System.Linq;
using System.Runtime.InteropServices;
using LogQuery = Interop.MSUtil.LogQueryClass;
using RegistryInputFormat = Interop.MSUtil.COMRegistryInputContextClass;
using RegRecordSet = Interop.MSUtil.ILogRecordset;
using System.Diagnostics;
using Microsoft.Win32;
using System.Collections.Generic;

namespace FreeSpiritSoftware.ChromeRegistryCustomProfile
{
	public class Program
	{
		private const string DefaultChromeCall = @"""C:UserssamAppDataLocalGoogleChromeApplicationchrome.exe"" -- ""%1""";

		public static void Main(string[] args)
		{
			RegRecordSet rs = null;

			Stopwatch stopWatch = new Stopwatch();
			stopWatch.Start(); 
			try
			{
				LogQuery qry = new LogQuery();
				RegistryInputFormat registryFormat = new RegistryInputFormat();
				string query = string.Format(@"SELECT Path, ValueName from HKCR, HKCU, HKLM, HKCC, HKU where Value = '{0}'", DefaultChromeCall);
				rs = qry.Execute(query, registryFormat);
				for (; !rs.atEnd(); rs.moveNext())
				{
					string path = rs.getRecord().toNativeString(0);
					string valueName = rs.getRecord().toNativeString(1);
					Console.WriteLine(path);
					Console.WriteLine(valueName);
					Console.WriteLine("--");
				}
			}
			finally
			{
				rs.close();
			}
			stopWatch.Stop();
			Console.WriteLine(stopWatch.Elapsed.TotalSeconds + " seconds");
			Console.ReadKey(false);
		}
	}
}

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.

private static readonly IDictionary<string, RegistryKey> RegistryLookup = new Dictionary<string, RegistryKey>
{
	{ "HKCR", Registry.ClassesRoot },
	{ "HKCU", Registry.CurrentUser },
	{ "HKLM", Registry.LocalMachine },
	{ "HKCC", Registry.CurrentConfig },
	{ "HKU", Registry.Users },
};

private static RegistryKey GetSubKey(IEnumerable<string> splitPath)
{
	RegistryKey rootKey = RegistryLookup[splitPath.First()];
	return GetSubKey(rootKey, splitPath.Skip(1));
}

private static RegistryKey GetSubKey(RegistryKey key, IEnumerable<string> splitPath)
{
	var theRest = splitPath.Skip(1);
	return theRest.Any()
		? GetSubKey(key.OpenSubKey(splitPath.First()), splitPath.Skip(1))
		: key.OpenSubKey(splitPath.First(), writable: true);
}

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.

using System;
using System.Linq;
using System.Runtime.InteropServices;
using LogQuery = Interop.MSUtil.LogQueryClass;
using RegistryInputFormat = Interop.MSUtil.COMRegistryInputContextClass;
using RegRecordSet = Interop.MSUtil.ILogRecordset;
using System.Diagnostics;
using Microsoft.Win32;
using System.Collections.Generic;

namespace FreeSpiritSoftware.ChromeRegistryCustomProfile
{
	public class Program
	{
		private const string DefaultChromeCall = @"""C:UserssamAppDataLocalGoogleChromeApplicationchrome.exe"" -- ""%1""";
		private const string ReplacementChromeCall = @"""C:UserssamAppDataLocalGoogleChromeApplicationchrome.exe"" --user-data-dir=""E:settingschrome-profiles""  -- ""%1""";
		private static readonly char[] PathSeparator = new[] { '\' };
		private static readonly IDictionary<string, RegistryKey> RegistryLookup = new Dictionary<string, RegistryKey>
        {
            { "HKCR", Registry.ClassesRoot },
            { "HKCU", Registry.CurrentUser },
            { "HKLM", Registry.LocalMachine },
            { "HKCC", Registry.CurrentConfig },
            { "HKU", Registry.Users },
        };

		public static void Main(string[] args)
		{
			RegRecordSet rs = null;

			Stopwatch stopWatch = new Stopwatch();
			stopWatch.Start();
			try
			{
				LogQuery qry = new LogQuery();
				RegistryInputFormat registryFormat = new RegistryInputFormat();
				string query = string.Format(@"SELECT Path, ValueName from HKCR, HKCU, HKLM, HKCC, HKU where Value = '{0}'", DefaultChromeCall);
				rs = qry.Execute(query, registryFormat);
				for (; !rs.atEnd(); rs.moveNext())
				{
					string path = rs.getRecord().toNativeString(0);
					string valueName = rs.getRecord().toNativeString(1);
					if (valueName == "(Default)")
						valueName = string.Empty;
					Console.WriteLine(path);
					Console.WriteLine(valueName);
					String[] splitPath = path.Split(PathSeparator);
					RegistryKey key = GetSubKey(splitPath.Take(splitPath.Length));
					Console.WriteLine(key.GetValue(valueName));
					key.SetValue(valueName, ReplacementChromeCall);
					Console.WriteLine(key.GetValue(valueName));
					Console.WriteLine("--");
				}
			}
			finally
			{
				rs.close();
			}
			stopWatch.Stop();
			Console.WriteLine(stopWatch.Elapsed.TotalSeconds + " seconds");
			Console.ReadKey(false);
		}

		private static RegistryKey GetSubKey(IEnumerable<string> splitPath)
		{
			RegistryKey rootKey = RegistryLookup[splitPath.First()];
			return GetSubKey(rootKey, splitPath.Skip(1));
		}

		private static RegistryKey GetSubKey(RegistryKey key, IEnumerable<string> splitPath)
		{
			var theRest = splitPath.Skip(1);
			return theRest.Any()
				? GetSubKey(key.OpenSubKey(splitPath.First()), splitPath.Skip(1))
				: key.OpenSubKey(splitPath.First(), writable: true);
		}
	}
}

(Note: I’m aware that there are aliases within the registry so I’m performing duplicate searches, I was happy to just use a brute-force search).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s