How can I find the upgrade code for an installed application in C#?

This is the opposite method to get the ProductCode from an UpgradeCode. Could be useful for somebody.

using Microsoft.Win32;
using System;
using System.IO;
using System.Linq;
using System.Text;

internal static class RegistryHelper
{
    private const string UpgradeCodeRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes";

    private static readonly int[] GuidRegistryFormatPattern = new[] { 8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2 };


    public static Guid? GetProductCode(Guid upgradeCode)
    {
        // Convert the product code to the format found in the registry
        var productCodeSearchString = ConvertToRegistryFormat(upgradeCode);

        // Open the upgrade code registry key
        var upgradeCodeRegistryRoot = GetRegistryKey(Path.Combine(UpgradeCodeRegistryKey, productCodeSearchString));

        if (upgradeCodeRegistryRoot == null)
            return null;

        var uninstallCode = upgradeCodeRegistryRoot.GetValueNames().FirstOrDefault();
        if (string.IsNullOrEmpty(uninstallCode))
        {
            return null;
        }

        // Convert it back to a Guid
        return ConvertFromRegistryFormat(uninstallCode);
    }





    private static string ConvertToRegistryFormat(Guid code)
    {
        return Reverse(code, GuidRegistryFormatPattern);
    }

    private static Guid ConvertFromRegistryFormat(string code)
    {
        if (code == null || code.Length != 32)
            throw new FormatException("Product code was in an invalid format");

        code = Reverse(code, GuidRegistryFormatPattern);

        return Guid.Parse(code);
    }

    private static string Reverse(object value, params int[] pattern)
    {
        // Strip the hyphens
        var inputString = value.ToString().Replace("-", "");

        var returnString = new StringBuilder();

        var index = 0;

        // Iterate over the reversal pattern
        foreach (var length in pattern)
        {
            // Reverse the sub-string and append it
            returnString.Append(inputString.Substring(index, length).Reverse().ToArray());

            // Increment our posistion in the string
            index += length;
        }

        return returnString.ToString();
    }

    static RegistryKey GetRegistryKey(string registryPath)
    {
        var hklm64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
        var registryKey64 = hklm64.OpenSubKey(registryPath);
        if (((bool?)registryKey64?.GetValueNames()?.Any()).GetValueOrDefault())
        {
            return registryKey64;
        }

        var hklm32 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
        return hklm32.OpenSubKey(registryPath);
    }
}

I have discovered the upgrade codes are stored in the following registry location.

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes

The registry key name is the upgrade code and the registry key value name is the product code. I can easily extract these values however the codes are stored in a different format. The red circle shows the formatted upgrade code, the blue circle shows the formatted product code when viewing it in regedit.exe.

Red circle is the formatted upgrade code, the blue circle the formatted product code

The hyphens are stripped out of the Guid and then a series of string reversals are done. The first 8 characters are reversed, then the next 4, then the following 4 and then the rest of the string is reversed in sets of 2 characters. Normally when reversing a string we need to take care in making sure control and special characters are handled correctly (see Jon Skeet's aricle here) but as we are, in this case, dealing with a Guid string we can be confident the string will be reversed correctly.

Below is the complete code I used to extract the upgrade code for a known product code from the registry.

internal static class RegistryHelper
{
    private const string UpgradeCodeRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes";

    private static readonly int[] GuidRegistryFormatPattern = new[] { 8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2 };

    public static Guid? GetUpgradeCode(Guid productCode)
    {
        // Convert the product code to the format found in the registry
        var productCodeSearchString = ConvertToRegistryFormat(productCode);

        // Open the upgrade code registry key
        var localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
        var upgradeCodeRegistryRoot = localMachine.OpenSubKey(UpgradeCodeRegistryKey);

        if (upgradeCodeRegistryRoot == null)
            return null;

        // Iterate over each sub-key
        foreach (var subKeyName in upgradeCodeRegistryRoot.GetSubKeyNames())
        {
            var subkey = upgradeCodeRegistryRoot.OpenSubKey(subKeyName);

            if (subkey == null)
                continue;

            // Check for a value containing the product code
            if (subkey.GetValueNames().Any(s => s.IndexOf(productCodeSearchString, StringComparison.OrdinalIgnoreCase) >= 0))
            {
                // Extract the name of the subkey from the qualified name
                var formattedUpgradeCode = subkey.Name.Split('\\').LastOrDefault();

                // Convert it back to a Guid
                return ConvertFromRegistryFormat(formattedUpgradeCode);
            }
        }

        return null;
    }

    private static string ConvertToRegistryFormat(Guid productCode)
    {
        return Reverse(productCode, GuidRegistryFormatPattern);
    }

    private static Guid ConvertFromRegistryFormat(string upgradeCode)
    {
        if (upgradeCode == null || upgradeCode.Length != 32)
            throw new FormatException("Product code was in an invalid format");

        upgradeCode = Reverse(upgradeCode, GuidRegistryFormatPattern);

        return Guid.Parse(upgradeCode);
    }

    private static string Reverse(object value, params int[] pattern)
    {
        // Strip the hyphens
        var inputString = value.ToString().Replace("-", "");

        var returnString = new StringBuilder();

        var index = 0;

        // Iterate over the reversal pattern
        foreach (var length in pattern)
        {
            // Reverse the sub-string and append it
            returnString.Append(inputString.Substring(index, length).Reverse().ToArray());

            // Increment our posistion in the string
            index += length;
        }

        return returnString.ToString();
    }
}

The InstallPackage class has a property called LocalPackage. You can use this to query the MSI database that's cached in C:\Windows\Installer and get anything you could possibly want to know about it.