MSI files are Windows Installer data packages that actually contain application files stored in a COM structure and a database containing applications information for installing, updating, uninstalling.
Windows provides a set of APIs for manipulating msi programs, contained in msi.dll
, three of which are listed below.
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
private static extern uint MsiEnumClients(string szComponent, uint iProductIndex, string lpProductBuf);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
private static extern int MsiGetComponentPath(string szProduct, string szComponent, string lpPathBuf, out uint pcchBuf);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
private static extern Int32 MsiGetProductInfo(string product, string property, [Out] StringBuilder valueBuf, ref Int32 len);
Product Code
In the MSI installer, ProductCode
is used as a unique identification code for the application. For installed applications, two ways to obtain ProductCode
are provided below.
- Powershell
This script will list the ProductName
, ProductCode
and Version
of all MSI applications installed on the local machine.
$x86Path = "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
$installedItemsX86 = Get-ItemProperty -Path $x86Path | Select-Object -Property PSChildName, DisplayName, DisplayVersion
$x64Path = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*"
$installedItemsX64 = Get-ItemProperty -Path $x64Path | Select-Object -Property PSChildName, DisplayName, DisplayVersion
$installedItems = $installedItemsX86 + $installedItemsX64
$installedItems | Where-Object -FilterScript { $null -ne $_.DisplayName } | Sort-Object -Property DisplayName | Out-GridView
- Advanced Uninstaller
Advanced Uninstaller contains information about most of the applications on the market, and we can use special search techniques to get the information we need.
Take Navisworks Manage 2019
as an example, enter Navisworks Manage2018 site://https://www.advanceduninstaller.com/
in the search engine and the first search result contains the information we want.
Translated with www.DeepL.com/Translator (free version)
SQUID
If we use the application's ProductCode
, i.e. GUID, to search directly in the registry, we often can't search anything. This is due to the fact that msi instanller converts the GUID into a SQUID for storage during the installation and writing of the registry.
SQUID that is, "squished GUID", was compressed GUID. its compressed form for the GUID five paragraphs of numbers, the first three paragraphs of numbers completely reverse order, the last two paragraphs of numbers two reverse order, the following example (the results do not contain hyphens).
//GUID
12345678-abcd-1234-abcd-123456789012
//SQUID
87654321dcba4321badc214365870921
As searched above, the Navisworks Manage 2018
GUID BE4A780F-E1DC-0000-B189-9CBF8D228C06
can be converted to F087A4EBCD1E00001B98C9FBD822C860
, and in the registry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\F087A4EBCD1E00001B98C9FBD822C860
to get the relevant information.
Component
When different versions of the application use the same component, you can use the MsiEnumClients
method to get all the products that use the component ProductCode
and then get more details.
In my open source project RevitLauncher (welcome ⭐ and PR), knowing that different Revit versions (even in Navisworks or some other applications that support reading Revit files) all contains the RevitDB
component, so you can get all the installed Revit product information through this component.
/// <summary>
/// Must use the {GUID} format
/// </summary>
private const string REVITDB_COMPONENT_ID = "{DF7D485F-B8BA-448E-A444-E6FB1C258912}";
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
private static extern uint MsiEnumClients(string szComponent, uint iProductIndex, string lpProductBuf);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
private static extern int MsiGetComponentPath(string szProduct, string szComponent, string lpPathBuf, out uint pcchBuf);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
private static extern Int32 MsiGetProductInfo(string product, string property, [Out] StringBuilder valueBuf, ref Int32 len);
void Main()
{
string productCode = new string('0', 38);
uint num = 0u;
while (MsiEnumClients(REVITDB_COMPONENT_ID, num++, productCode) == 0u)
{
Int32 length = 512;
Int32 length1 = 512;
StringBuilder name = new StringBuilder(length);
StringBuilder location = new StringBuilder(length1);
MsiGetProductInfo(productCode, "ProductName", name, ref length);
MsiGetProductInfo(productCode, "InstallLocation", location, ref length1);
Console.WriteLine($"ProductName:{name.ToString()}");
Console.WriteLine($"ProductCode:{productCode}");
Console.WriteLine($"InstallLocation:{location.ToString()}");
}
}