Is your "new" menu too long?

Something that I find annoying is how many programs add items to the Windows Explorer "New" submenu ("New item" on the Windows 8 Explorer ribbon, or right click in the Explorer window and select "New") that I really never, or very seldom, want to create directly. All this noise gets in the way of the few items I do want to quickly access from there. Now, it's possible to remove these extra items via RegEdit but recently I've been doing a lot of OS installs and it's a chore to repeat the edits manually. If you've got an itch, scratch it... So I decided to write a little tool to do the editing for me, and here it is.

First, a little bit about the relevant registry items: under HKEY_CLASSES_ROOT there are subkeys for each recognised file suffix, below that there are several more, one of which may have a ShellNew key. If that key is non-empty, then Windows Explorer populates an entry in the new items menu for the appropriate file type. If you delete (or empty out) the ShellNew key, the menu entry no longer appears. Deleting is a bit drastic, and renaming ShellNew to something else lets you get the entry back if you've changed your mind. (Actually, there are other means of getting items on to that menu, but I'm only interested in these ones since they correspond to the entries I'm most interested in manipulating - to be honest, I've not gone looking for the others. Please note that this has all been gleaned from exploring the registry - I don't think this stuff is documented formally. As most articles involving registry editing say, erroneous changes could break things and the risk is yours!

The utility I'm going to outline here iterates over all of the HKEY_CLASSES_ROOT subkeys looking for non-empty ShellNew, or for XShellNew (which is the not very imaginative name I've chosen to use to indicate "deleted" ShellNew keys), and presents them as a list of checkboxes to be disabled or re-enabled, respectively. Here's the routine for populating my list (ListItems is a CheckedListBox):

private void PopulateList()
{
    using (var classRoot = Registry.ClassesRoot)
    {
        foreach (var typeName in classRoot.GetSubKeyNames())
        {
            using (var type = classRoot.OpenSubKey(typeName))
            {
                foreach (var subkeyName in type.GetSubKeyNames())
                {
                    using (var subkey = type.OpenSubKey(subkeyName))
                    using (var shellNew = subkey.OpenSubKey("ShellNew"))
                    using (var xshellNew = subkey.OpenSubKey("XShellNew"))
                    {
                        bool enabled = shellNew != null && shellNew.GetValueNames().Length > 0;
                        if (enabled || xshellNew != null)
                        {
                            this.ListItems.Items.Add(new SuffixDetails { Suffix = typeName, Subkey = subkeyName }, enabled);
                            break;
                        }
                    }
                }
            }
        }
    }
}

SuffixDetails is a class containing two string fields (Suffix and Subkey) and a ToString override which returns just the suffix, to be used for display in the ListBox.

The list box has an Itemcheck event handler which either renames ShellNew to XShellNew or vice versa depending on whether the item has been unchecked or checked.

private void ListItems_ItemCheck(object sender, ItemCheckEventArgs e)
{
    var item = this.ListItems.Items[e.Index] as SuffixDetails;
    bool enable = e.NewValue == CheckState.Checked;

    using (var key = Registry.ClassesRoot.OpenSubKey(item.Suffix))
    using (var subkey = key.OpenSubKey(item.Subkey, true))
    {
        string fromKeyName = enable ? "XShellNew" : "ShellNew";
        string toKeyName = enable ? "ShellNew" : "XShellNew";
        try
        {
            subkey.RenameSubKey(fromKeyName, toKeyName);
        }
        catch (Exception ex)
        {
            MessageBox.Show(string.Format("Error {0}abling '{1}/{2}'", enable ? "en" : "dis", item.Suffix, item.Subkey),
                "New Item Squasher", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }
}

Now, RegistryKey doesn't actually have a RenameSubKey method - and the registry doesn't support renaming anyway. This is an extension method which recursively copies one subkey to another, and then (assuming no errors) deletes the first. Note that if the destination subkey already exists, this adds to or overwrites that - that seems reasonable in this case, but perhaps an error should be thrown in other applications of this "renaming" facility. (Overwriting is helpful here since Windows Update patches frequently re-introduce ShellNew keys for document types I'd previously disabled, and this lets me re-disable them by copying the new ShellNew over the old XShellNew without any errors.

The class below provides the registry manipulations. The copying is simply done recursively:

public static class RegistryExtensions
{
    public static void RenameSubKey(this RegistryKey key, string fromSubKeyName, string toSubKeyName)
    {
        using (var toKey = key.OpenSubKey(fromSubKeyName))
        using (var fromKey = key.CreateSubKey(toSubKeyName))
        {
            RecursiveCopy(toKey, fromKey);
        }
        // If there was an error, it'll throw an exception before here so we won't lose the source key...
        key.DeleteSubKeyTree(fromSubKeyName);
    }

    private static void RecursiveCopy(RegistryKey fromKey, RegistryKey toKey)
    {
        foreach (string fromSubKeyName in fromKey.GetSubKeyNames())
        {
            using (var fromSubKey = fromKey.OpenSubKey(fromSubKeyName))
            using (var toSubKey = toKey.CreateSubKey(fromSubKeyName))
            {
                RecursiveCopy(fromSubKey, toSubKey);
            }
        }

        foreach (string valueName in fromKey.GetValueNames())
        {
            var value = fromKey.GetValue(valueName);
            var kind = fromKey.GetValueKind(valueName);
            toKey.SetValue(valueName, value, kind);
        }
    }
}

The only other thing of interest in the application is that it must run elevated (standard users can't write into HKEY_CLASSES_ROOT) and the easiest way to guarantee that is to add a manifest to the project (available directly in the add new item Visual Studio wizard), and set the requestedExecutionLevel to requireAdministrator. This will cause the application to elevate (or prompt for elevation) when it starts.