How do I quickly obtain the ProductVersion of an msi database through pinvoke using the msi.dll? Mostly what I found involved utilizing the WindowsInstaller COM wrapper, while this got the job done, I want to achieve the same results through pinvoke using the msi.dll.
Checking ProductVersion of an MSI programmatically
Asked Answered
Here's what I've come up with.
C# Windows Installer COM library:
// Get the type of the Windows Installer object
Type installerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
// Create the Windows Installer object
Installer installer = (Installer)Activator.CreateInstance(installerType);
// Open the MSI database in the input file
Database database = installer.OpenDatabase(od.FileName, MsiOpenDatabaseMode.msiOpenDatabaseModeReadOnly);
// Open a view on the Property table for the version property
WindowsInstaller.View view = database.OpenView("SELECT * FROM Property WHERE Property = 'ProductVersion'");
// Execute the view query
view.Execute(null);
// Get the record from the view
Record record = view.Fetch();
// Get the version from the data
string version = record.get_StringData(2);
C# Pinvoke:
[DllImport("msi.dll", SetLastError = true)]
static extern uint MsiOpenDatabase(string szDatabasePath, IntPtr phPersist, out IntPtr phDatabase);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
static extern int MsiDatabaseOpenViewW(IntPtr hDatabase, [MarshalAs(UnmanagedType.LPWStr)] string szQuery, out IntPtr phView);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
static extern int MsiViewExecute(IntPtr hView, IntPtr hRecord);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
static extern uint MsiViewFetch(IntPtr hView, out IntPtr hRecord);
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
static extern int MsiRecordGetString(IntPtr hRecord, int iField,
[Out] StringBuilder szValueBuf, ref int pcchValueBuf);
[DllImport("msi.dll", ExactSpelling = true)]
static extern IntPtr MsiCreateRecord(uint cParams);
[DllImport("msi.dll", ExactSpelling = true)]
static extern uint MsiCloseHandle(IntPtr hAny);
public string GetVersionInfo(string fileName)
{
string sqlStatement = "SELECT * FROM Property WHERE Property = 'ProductVersion'";
IntPtr phDatabase = IntPtr.Zero;
IntPtr phView = IntPtr.Zero;
IntPtr hRecord = IntPtr.Zero;
StringBuilder szValueBuf = new StringBuilder();
int pcchValueBuf = 255;
// Open the MSI database in the input file
uint val = MsiOpenDatabase(fileName, IntPtr.Zero, out phDatabase);
hRecord = MsiCreateRecord(1);
// Open a view on the Property table for the version property
int viewVal = MsiDatabaseOpenViewW(phDatabase, sqlStatement, out phView);
// Execute the view query
int exeVal = MsiViewExecute(phView, hRecord);
// Get the record from the view
uint fetchVal = MsiViewFetch(phView, out hRecord);
// Get the version from the data
int retVal = MsiRecordGetString(hRecord, 2, szValueBuf, ref pcchValueBuf);
uRetCode = MsiCloseHandle(phDatabase);
uRetCode = MsiCloseHandle(phView);
uRetCode = MsiCloseHandle(hRecord);
return szValueBuf.ToString();
}
This could easily be extrapolated into obtaining any property or field from the msi database by changing the SQL statement. I hope this helps someone out.
This P/Invoke code has two main flaws (which are unlikely to bite you until you forgot writing this code): MSIHANDLEs are Int32 not IntPtr (yes, this is unlike most HANDLE types, and relevant only on x64 environments), and your property fetch code can't handle property values longer than 255. –
Irruptive
I didn't realize that about the MSIHANDLEs, thanks! That would have been a huge rabbit hole to go down to find that bug. The property values length can be easily changed. I had just set it to 255 arbitrarily. –
Analiese
Error 19 Cannot convert to static type 'Microsoft.Deployment.WindowsInstaller.Installer' –
Dover
Anyone needing to do .NET interop with MSI should be using Microsoft.Deployment.WindowsInstaller found in WiX's DTF SDK. It's a very clean library and way better then trying to write your own.
Is there any way to embed the interop library into the main executeable? I was looking for a way to interop without having to tag along an additional external DLL to my app. –
Analiese
BTW, if you really want to consume 1 or 2 functions in the API and have a lightweight .CS file to do it, I suggest you google for MsiInterop.cs and then strip it down to what you really need. Better then using COM IMO. For general purpose MSI Interop I still stand by that WiX DTF is the best solution. –
Hubby
+1. WiX SDK generally results in about two thirds less code for me compared to the other options in the accepted answer. –
Nick
Except that nowadays Wix is MS-RL so it is not suitable for some commercial software –
Clicker
@Clicker I'm not a lawyer, and neither is Rob Mensching, but according to what he says here windows-installer-xml-wix-toolset.687559.n2.nabble.com/… you don't have to do anything special if you use DTF unmodified. –
Wisniewski
I was unable to get @user529570's answer working for me, this however worked in C#
using System;
using System.Runtime.InteropServices;
using System.Text;
public static class Msi
{
public static string GetProductVersion(string fileName)
{
IntPtr hInstall = IntPtr.Zero;
try
{
uint num = MsiOpenPackage(fileName, ref hInstall);
if ((ulong)num != 0)
{
throw new Exception("Cannot open database: " + num);
}
int pcchValueBuf = 255;
StringBuilder szValueBuf = new StringBuilder(pcchValueBuf);
num = MsiGetProperty(hInstall, "ProductVersion", szValueBuf, ref pcchValueBuf);
if ((ulong)num != 0)
{
throw new Exception("Failed to Get Property ProductVersion: " + num);
}
return szValueBuf.ToString();
}
finally
{
if(hInstall != IntPtr.Zero)
{
MsiCloseHandle(hInstall);
}
}
}
[DllImport("msi.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern int MsiCloseHandle(IntPtr hAny);
[DllImport("msi.dll", CharSet = CharSet.Unicode, EntryPoint = "MsiOpenPackageW", ExactSpelling = true, SetLastError = true)]
private static extern uint MsiOpenPackage(string szDatabasePath, ref IntPtr hProduct);
[DllImport("msi.dll", CharSet = CharSet.Unicode, EntryPoint = "MsiGetPropertyW", ExactSpelling = true, SetLastError = true)]
private static extern uint MsiGetProperty(IntPtr hInstall, string szName, [Out] StringBuilder szValueBuf, ref int pchValueBuf);
}
This works, but please note that opening a large file will take a long time as you are opening the complete package and not just the database. –
Spevek
© 2022 - 2024 — McMap. All rights reserved.
Microsoft.Deployment.WindowsInstaller.dll
. Find it. Add a reference. Go time. Done. – Menswear