Sandeep
Sandeep

Reputation: 5771

Get Product Code of installed Msi

I have a C# program where I have to get the product code of an installed msi. I have only the msi name as the input. Can this be done programmatically?

Upvotes: 7

Views: 23132

Answers (5)

user8338424
user8338424

Reputation: 11

private static bool GetUninstallString(string ProductName)
{
    try
    {
        RegistryKey localKey = RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, RegistryView.Registry64);
        var key = localKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") ??
            localKey.OpenSubKey(
                @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall");

        if (key == null)
                return false;

        return key.GetSubKeyNames()
                .Select(keyName => key.OpenSubKey(keyName))
                .Select(subkey => subkey.GetValue("DisplayName") as string)
                .Any(displayName => displayName != null && displayName.Contains(ProductName));
     }
     catch
     {
         // Log message                  
         return false;
     }
 }

This is very usefull for search string by productname

Upvotes: 1

Vladimir Shiyanov
Vladimir Shiyanov

Reputation: 1396

There is most fast and simply way - to use WMI with conditional query string.

    public string GetProductCode(string productName)
    {
        string query = string.Format("select * from Win32_Product where Name='{0}'", productName);
        using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(query))
        {
            foreach (ManagementObject product in searcher.Get())
                return product["IdentifyingNumber"].ToString();
        }
        return null;
    }

Upvotes: 3

Sandeep
Sandeep

Reputation: 5771

This is the code I used to get the UninstallString of any MSI.

private string GetUninstallString(string msiName)
{
    Utility.WriteLog("Entered GetUninstallString(msiName) - Parameters: msiName = " + msiName);
    string uninstallString = string.Empty;
    try
    {
        string path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\S-1-5-18\\Products";

        RegistryKey key = Registry.LocalMachine.OpenSubKey(path);

        foreach (string tempKeyName in key.GetSubKeyNames())
        {
            RegistryKey tempKey = key.OpenSubKey(tempKeyName + "\\InstallProperties");
            if (tempKey != null)
            {
                if (string.Equals(Convert.ToString(tempKey.GetValue("DisplayName")), msiName, StringComparison.CurrentCultureIgnoreCase))
                {
                    uninstallString = Convert.ToString(tempKey.GetValue("UninstallString"));
                    uninstallString = uninstallString.Replace("/I", "/X");
                    uninstallString = uninstallString.Replace("MsiExec.exe", "").Trim();
                    uninstallString += " /quiet /qn";
                    break;
                }
            }
        }

        return uninstallString;
    }
    catch (Exception ex)
    {
        throw new ApplicationException(ex.Message);
    }
}

This will give a result like this:

MsiExec.exe /I{6BB09011-69E1-472F-ACAD-FA0E7DA3E2CE}

From this string, you can take the substring within the braces {}, which will be 6BB09011-69E1-472F-ACAD-FA0E7DA3E2CE. I hope this might be the product code.

Upvotes: 6

Bert Huijben
Bert Huijben

Reputation: 19612

This code obtains the product code directly from an MSI file. So this allows reading the code without installing the file.

class MsiHandle : SafeHandleMinusOneIsInvalid
{
    public MsiHandle()
        : base(true)
    { }

    protected override bool ReleaseHandle()
    {
        return NativeMethods.MsiCloseHandle(handle) == 0;
    }
}

class NativeMethods
{
    const string MsiDll = "Msi.dll";

    [DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
    public extern static uint MsiOpenPackageW(string szPackagePath, out MsiHandle product);

    [DllImport(MsiDll, ExactSpelling=true)]
    public extern static uint MsiCloseHandle(IntPtr hAny);

    [DllImport(MsiDll, CharSet = CharSet.Unicode, ExactSpelling = true)]
    static extern uint MsiGetProductPropertyW(MsiHandle hProduct, string szProperty, StringBuilder value, ref int length);


    [DllImport(MsiDll, ExactSpelling = true)]
    public static extern int MsiSetInternalUI(int value, IntPtr hwnd);

    public static uint MsiGetProductProperty(MsiHandle hProduct, string szProperty, out string value)
    {
        StringBuilder sb = new StringBuilder(1024);
        int length = sb.Capacity;
        uint err;
        value = null;
        if(0 == (err = MsiGetProductPropertyW(hProduct, szProperty, sb, ref length)))
        {
            sb.Length = length;
            value = sb.ToString();
            return 0;
        }

        return err;
    }
}

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static int Main(string[] args)
    {
        string msiFile = args[0];

        NativeMethods.MsiSetInternalUI(2, IntPtr.Zero); // Hide all UI. Without this you get a MSI dialog

        MsiHandle msi;
        uint err;
        if (0 != (err = NativeMethods.MsiOpenPackageW(args[0], out msi)))
        {
            Console.Error.WriteLine("Can't open MSI, error {0}", err);
            return 1;
        }

        // Strings available in all MSIs
        string productCode;
        using (msi)
        {
           if (0 != NativeMethods.MsiGetProductProperty(msi, "ProductCode", out productCode))
                throw new InvalidOperationException("Can't obtain product code");

           Console.WriteLine(productCode);
           return 0;
        }
    }
}

Full example in Subversion on http://ankhsvn.open.collab.net/svn/ankhsvn/trunk/src/tools/Ankh.Chocolatey/ Use username 'guest' and no password.

Upvotes: 3

Thorsten Dittmar
Thorsten Dittmar

Reputation: 56697

Do the answers to this question help? They want to get the product name, but maybe it works for the product code, too?

EDIT
If you do not have the MSI file itself to access the database (as suggested by the above link to the other question), you may try to search the following registry path for the name of your MSI file:

HKEY_CLASSES_ROOT\Installer\Products\*\SourceList 

There are many entries under the Products branch. Each of them is a product key. Every branch should contain the SourceList node, which in turn should contain the value PackageName. That value holds the name of the MSI file.

So what I'd do is:

for each key in Products
{
    open SourceList subkey
    read PackageName value
    if name equals my msi file name
    {
        return key-name formatted as GUID
    }
}

Upvotes: 6

Related Questions