Reputation: 5771
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
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
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
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
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
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