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.
            Asked
            
        
        
            Active
            
        
            Viewed 9,721 times
        
    4
            
            
        - 
                    2@user529570 Welcome to Stack Overflow - it doesn't matter that you have already solved this, it's still useful. You might want edit it (use the 'edit' link below the question) to phrase this as a question and put your solution(s) in the form of an answer. This allows people to offer alternative answers and vote on your answer. – Colin Pickard Dec 03 '10 at 16:08
 - 
                    Usefull. Codeproject seems to be the place to post things like this. A site where people could post solutions would be very usefull. – Nick Dec 03 '10 at 16:09
 - 
                    As indicated in one of the answers, you should use **DTF** for this when coding in C# - it does all the pinvoke stuff for you once and for all. [**Here is a sample of DTF used to deal with MSI**](https://stackoverflow.com/a/1061606/129130). DTF is installed with the [WiX toolset](https://wixtoolset.org/). Here are some [quick start tips for WiX](https://stackoverflow.com/a/25005864/129130). Most important dll: **`Microsoft.Deployment.WindowsInstaller.dll`**. Find it. Add a reference. Go time. Done. – Stein Åsmul Nov 16 '19 at 11:13
 
3 Answers
9
            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.
        user529570
        
- 139
 - 1
 - 7
 
- 
                    2This 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. – Michael Urman Dec 06 '10 at 14:02
 - 
                    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. – user529570 Dec 07 '10 at 20:58
 - 
                    Error 19 Cannot convert to static type 'Microsoft.Deployment.WindowsInstaller.Installer' – Jader Dias Jul 05 '13 at 19:45
 
6
            
            
        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.
        Christopher Painter
        
- 54,556
 - 6
 - 63
 - 100
 
- 
                    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. – user529570 Dec 07 '10 at 20:55
 - 
                    1BTW, 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. – Christopher Painter Dec 08 '10 at 18:11
 - 
                    1+1. WiX SDK generally results in about two thirds less code for me compared to the other options in the accepted answer. – Tharwen May 29 '12 at 07:48
 - 
                    Except that nowadays Wix is MS-RL so it is not suitable for some commercial software – zaitsman Jan 05 '16 at 05:01
 - 
                    @zaitsman I'm not a lawyer, and neither is Rob Mensching, but according to what he says here http://windows-installer-xml-wix-toolset.687559.n2.nabble.com/Outcurve-License-question-td7581298.html you don't have to do anything special if you use DTF unmodified. – RenniePet Aug 12 '17 at 22:24
 
0
            
            
        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);
    }
        aolszowka
        
- 1,300
 - 12
 - 36
 
- 
                    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. – Mekroebo Mar 15 '23 at 15:20