Checking ProductVersion of an MSI programmatically
Asked Answered
A

3

4

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.

Analiese answered 3/12, 2010 at 16:0 Comment(3)
@Analiese 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.Knave
Usefull. Codeproject seems to be the place to post things like this. A site where people could post solutions would be very usefull.Sierrasiesser
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. DTF is installed with the WiX toolset. Here are some quick start tips for WiX. Most important dll: Microsoft.Deployment.WindowsInstaller.dll. Find it. Add a reference. Go time. Done.Menswear
A
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.

Analiese answered 3/12, 2010 at 16:50 Comment(3)
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
H
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.

Hubby answered 3/12, 2010 at 22:31 Comment(5)
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 softwareClicker
@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
A
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);
    }
Ahead answered 28/4, 2021 at 15:48 Comment(1)
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.