1

I'm trying to create a script in PowerShell that extracts [ProductCode] from .msi package located somewhere on disk. I found that I need to use next two methods: MsiOpenPackage and MsiGetProperty. Based on that, I wrote next code snippet:

$signature_GetProperty = @'
[DllImport("msi.dll", CharSet=CharSet.Unicode)]
public static extern int MsiGetProperty(
int hInstall,
string szName,
[Out] StringBuilder szValueBuf,
ref int pchValueBuf);
'@

$signature_OpenPackage = @'
[DllImport("msi.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true, ExactSpelling = true)]
public static extern UInt32 MsiOpenPackageEx(
string szPackagePath,
UInt32 dwOptions,
void **hProduct);
'@

$OpenPackageType = Add-Type -MemberDefinition $signature_OpenPackage  -Name "WinMsiOpenPackageEX" -Namespace Win32Functions -PassThru
$OpenPackageType::MsiOpenPackageEx($path, 1, STRUGGLING HERE)

$GetInfoType = Add-Type -MemberDefinition $signature_GetProperty -Name    "WinGetProperty" -Namespace Win32GetProductCodeMSI -Using System.Text     -PassThru
$GetInfoType::MsiGetProperty(AND HERE, "ProductCode", 

I'm struggling with how should I declare and use variables that are defined as parameters MsiGetProperty and MsiOpenPackageEx.

For instance, last parameter in OpenPackage is void **hProduct. How should I declare it in .ps1 script in order to use later within MsiGetProperty function. The same goes with ref int pchValueBuf.

I'm sorry for such a lame question, but I would really appreciate any help or clarification or article to read about this type of issue.

5
  • 3
    This is not a lame question :-) you should be able to use [ref]$ptr and [ref]$int respectively, where $ptr is an already created copy of [intptr]::zero and $int is an already instantiated integer. You may need to add the out keyword to the void **hProduct parameter Commented Sep 18, 2017 at 9:27
  • @MathiasR.Jessen First of all, thanks for the advice, it get me going into right direction. But now I faced something that blows mymind. [link]pastebin.com/Z7ZSUxgu That's the final code with errors, which I don't know how to fix. Sorry for bothering you and me being too insistent. Commented Sep 19, 2017 at 10:04
  • 1
    void pointers in C# needs to be marked unsafe, the only way I can think of is to define another C# method alongside the dllimport's, that then wraps the call to MsiOpenPackageEx - then call the C# method from PowerShell Commented Sep 19, 2017 at 11:38
  • 1
    Yeah, I marked OpenPackage as unsafe and added -CompilerParameters $unsafe. That's obviously not enough. I guess I understood what you propose, it's just I'm not that skilled to do that right now. Anyway, really thanks a lot for help, I'll try to wrap my mind around it and maybe try to implement your proposal. Again, very grateful for your time. Commented Sep 19, 2017 at 12:27
  • I think you should be able to define pchValueBuf as out IntPtr rather than void**. Commented Jan 8, 2020 at 8:09

1 Answer 1

3

Here's a snippet we're using internally:

Add-Type -TypeDefinition @"
    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(255);
                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);
    }
"@

[Msi]::GetProductVersion("R:\PathTo.msi")
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.