How to get MSI file information with PowerShell

If you’re an ConfigMgr administrator like me, you will often find yourself in the situation where you may want to get the Product Code from a MSI file. Or perhaps you’re interested in the Product Version or simply just the Product Name. A while back I wrote a simple PowerShell script that can assist with just that, for some reason I forgot to post about it so here goes. As this is going to be a short post, I’ll not go much into detail on how it works, instead I’ll just give you the script.

Update – 2016-04-26
I’ve updated the function so that it doesn’t lock the specified MSI file.

Script

param(
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [System.IO.FileInfo]$Path,

    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [ValidateSet("ProductCode", "ProductVersion", "ProductName", "Manufacturer", "ProductLanguage", "FullVersion")]
    [string]$Property
)
Process {
    try {
        # Read property from MSI database
        $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
        $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @($Path.FullName, 0))
        $Query = "SELECT Value FROM Property WHERE Property = '$($Property)'"
        $View = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, ($Query))
        $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
        $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)
        $Value = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)

        # Commit database and close view
        $MSIDatabase.GetType().InvokeMember("Commit", "InvokeMethod", $null, $MSIDatabase, $null)
        $View.GetType().InvokeMember("Close", "InvokeMethod", $null, $View, $null)           
        $MSIDatabase = $null
        $View = $null

        # Return the value
        return $Value
    } 
    catch {
        Write-Warning -Message $_.Exception.Message ; break
    }
}
End {
    # Run garbage collection and release ComObject
    [System.Runtime.Interopservices.Marshal]::ReleaseComObject($WindowsInstaller) | Out-Null
    [System.GC]::Collect()
}

Save the above script as Get-MSIFileInformation.ps1 to C:\Scripts.

1. Open a PowerShell console and browse to C:\Scripts.
2. Run the following command:

.\Get-MSIFileInformation.ps1 -Path "D:\Source$\Apps\7-zip\7z920-x64.msi" -Property ProductCode

Note! You should specify a valid path to a MSI file in your environment.

105_1

I hope this helps!

15 Comments

  1. Tord Bergset

    Would be nice to have this easy accessible in a GUI too…like in my script below 🙂

    # —————————————————————————–
    # Get_MSI_File_info.ps1 – Created by Tord Bergset Jun2015
    #
    # Just right-click the script and “Run with Powershell”…or launch in Powershell
    # It will prompt you for the msi file to display information for.
    # —————————————————————————–

    # Set-ExecutionPolicy Unrestricted -Scope CurrentUser

    [System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) | Out-Null
    $myDialog = New-Object System.Windows.Forms.OpenFileDialog
    $myDialog.Title = “Please select a file”
    $myDialog.InitialDirectory = “C:\”
    $myDialog.Filter = “MSI files (*.msi)|*.msi”
    $result = $myDialog.ShowDialog()
    If($result -eq “OK”) {
    $inputFile = $myDialog.FileName
    # Continue working with file
    }
    else {
    Write-Host “Cancelled by user”
    Exit
    }

    $MSIInfo=$inputFile + “`n” + “`n”

    $array = @(“ProductName”,”ProductVersion”,”ProductCode”,”Manufacturer”)
    foreach ($element in $array) {
    $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
    $MSIDatabase = $WindowsInstaller.GetType().InvokeMember(“OpenDatabase”,”InvokeMethod”,$Null,$WindowsInstaller,@($inputFile,0))
    $Query = “SELECT Value FROM Property WHERE Property = ‘$($element)'”
    $View = $MSIDatabase.GetType().InvokeMember(“OpenView”,”InvokeMethod”,$null,$MSIDatabase,($Query))
    $View.GetType().InvokeMember(“Execute”, “InvokeMethod”, $null, $View, $null)
    $Record = $View.GetType().InvokeMember(“Fetch”,”InvokeMethod”,$null,$View,$null)
    $Value = $Record.GetType().InvokeMember(“StringData”,”GetProperty”,$null,$Record,1)
    $MSIInfo = $MSIInfo + “$element = ” + $Value + “`n”
    # Write-Output “$element = ” $Value
    }

    # The output dialog…
    $wshell = New-Object -ComObject Wscript.Shell
    $wshell.Popup($MSIInfo,0,”MSI Information output”,0x1)

    Reply
    1. NickolajNickolaj (Post author)

      Hi Tord!

      Thanks for sharing your code here, highly appreciated. I might to a console extension for this in the future, but for now I’ve added it as a function in my custom module I use for ConfigMgr.

      Regards,
      Nickolaj

      Reply
      1. Tony Eklund

        Hi Nickolaj!

        Thnx for the script it is exactly what im looking for.. But when i try to make it into a function to run in a bigger script i get this error:
        WARNING: Exception calling “InvokeMember” with “5” argument(s): “OpenDatabase,DatabasePath,OpenMode”

        Any idea why?

        Reply
        1. Tony Eklund

          Nevermind 😛

          I found the error myself.. i was calling a path variable that was false..

          Reply
          1. Peter

            Sorry, I have the same errors, what did you do to correct this ?

          2. Tony Eklund

            as stated in my comment..

            I used a false path-variable.. i.e i was checking a file that did not exist =)

    2. Mike

      Thanks for a great script. I am having some difficulties understanding everything. I have added some properties to the output. UpgradeCode and few others.
      What i would like to know, how would i export the output to the specific fields in word or excel for documentation?
      9065210@gmail
      Thanks,

      Reply
  2. Stephen

    Just came across this article. Very helpful, however if you plan to manipulate that MSI file at all after running this, Do the following two things:

    1. Edit the script to look like this:

    function Get-MSIInfo {
    param(
    [parameter(Mandatory=$true)]
    [IO.FileInfo]$Path,
    [parameter(Mandatory=$true)]
    [ValidateSet(“ProductCode”,”ProductVersion”,”ProductName”)]
    [string]$Property
    )
    try {
    $WindowsInstaller = $(New-Object -ComObject WindowsInstaller.Installer)
    $MSIDatabase = $WindowsInstaller.GetType().InvokeMember(“OpenDatabase”,”InvokeMethod”,$Null,$WindowsInstaller,@($Path.FullName,0))
    $Query = “SELECT Value FROM Property WHERE Property = ‘$($Property)'”
    $View = $MSIDatabase.GetType().InvokeMember(“OpenView”,”InvokeMethod”,$null,$MSIDatabase,($Query))
    $View.GetType().InvokeMember(“Execute”, “InvokeMethod”, $null, $View, $null)
    $Record = $View.GetType().InvokeMember(“Fetch”,”InvokeMethod”,$null,$View,$null)
    $Value = $Record.GetType().InvokeMember(“StringData”,”GetProperty”,$null,$Record,1)
    ######### Additions to the script #########
    $View.GetType().InvokeMember(“Close”,”InvokeMethod”,$null,$View,$null)
    $MSIDatabase.GetType().InvokeMember(“Commit”,”InvokeMethod”,$null,$MSIDatabase,$null)
    $MSIDatabase = $null
    ###################################
    return $Value
    }
    catch {
    Write-Output $_.Exception.Message
    }
    }
    ———————————————————————————
    2. Add the following after your function call:

    [System.GC]::Collect()
    Start-Sleep -Seconds 10

    The garbage collection needs to run to release the holds on the MSI file.

    Reply
    1. James Ward

      I was pulling my hair out over this code locking up the msi’s. Thanks!

      Reply
  3. James Ward

    Hi Nikolaj,

    This code is really great. But I run into problems after I use it and try to move the msi later in my script, the msi database seems to lock the msi file (hold it open) and you can’t move or delete it.

    Do you know anyway to fix this (dispose the database, release the lock etc)

    Please help me 🙂

    Reply
    1. NickolajNickolaj (Post author)

      Hi James,

      See the comment from Stephen above 🙂

      Regards,
      Nickolaj

      Reply
  4. Baard

    Awesome!
    However, there’s one param( too many 😉

    Reply
    1. NickolajNickolaj (Post author)

      Ooops, editing raw HTML code can sometimes be tricky 😉

      Thanks for pointing out!

      Regards,
      Nickolaj

      Reply
  5. Jay Michaud

    Thank you for this script. It is much shorter and more focused than others I have found. I did find what I believe is a bug, though. When specifying the “FullVersion” property, the script throws an exception stating “WARNING: You cannot call a method on a null-valued expression.” This occurs on the line assigning a value to $Value because the previous line returned $null and assigned it to $Record. When I consulted the list of Windows Installer properties on TechNet (https://msdn.microsoft.com/en-us/library/windows/desktop/aa370905(v=vs.85).aspx), I found that “FullVersion” is not a default or required property, and indeed, the MSI I was testing did not have that property. All the other properties you listed are required properties, so they will always work. Suggestion: Remove the “FullVersion” property from the ValidateSet.
    Thanks again!

    Reply
  6. Thomas Ehler

    With this script and the built in Powershell executer in .NET I’ve managed to build a simple Drag/Drop GUI. Drop a bunch of MSIs and you get the GUIDs.

    You can download it here
    usmtgui.com/test/MSI code Viewer.zip

    With kind regards Sam

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *