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.

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!

Nickolaj Andersen
Principal Consultant and Enterprise Mobility MVP. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows deployments and Automation. In 2015 Nickolaj was awarded as PowerShell Hero by the community for his script and tools contributions. Author of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService and a frequent speaker at user groups.

(2649)

comments
  • Tord Bergset
    Posted at 14:54 June 1, 2015
    Tord Bergset
    Reply
    Author

    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)

    • Nickolaj
      Posted at 19:18 June 2, 2015
      Nickolaj
      Reply
      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

      • Tony Eklund
        Posted at 08:40 October 5, 2016
        Tony Eklund
        Reply
        Author

        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?

        • Tony Eklund
          Posted at 10:14 October 5, 2016
          Tony Eklund
          Reply
          Author

          Nevermind 😛

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

          • Peter
            Posted at 15:35 December 22, 2016
            Peter
            Author

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

          • Tony Eklund
            Posted at 12:21 December 23, 2016
            Tony Eklund
            Author

            as stated in my comment..

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

    • Mike
      Posted at 15:54 June 9, 2016
      Mike
      Reply
      Author

      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,

  • Stephen
    Posted at 22:41 January 14, 2016
    Stephen
    Reply
    Author

    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.

    • James Ward
      Posted at 10:04 February 23, 2016
      James Ward
      Reply
      Author

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

  • James Ward
    Posted at 06:16 February 23, 2016
    James Ward
    Reply
    Author

    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 🙂

    • Nickolaj
      Posted at 13:16 April 25, 2016
      Nickolaj
      Reply
      Author

      Hi James,

      See the comment from Stephen above 🙂

      Regards,
      Nickolaj

  • Baard
    Posted at 14:42 April 27, 2016
    Baard
    Reply
    Author

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

    • Nickolaj
      Posted at 15:45 April 27, 2016
      Nickolaj
      Reply
      Author

      Ooops, editing raw HTML code can sometimes be tricky 😉

      Thanks for pointing out!

      Regards,
      Nickolaj

  • Jay Michaud
    Posted at 18:20 August 7, 2016
    Jay Michaud
    Reply
    Author

    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!

  • Thomas Ehler
    Posted at 13:39 February 1, 2017
    Thomas Ehler
    Reply
    Author

    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

  • Greg Wood
    Posted at 18:09 March 1, 2017
    Greg Wood
    Reply
    Author

    With the code above, your return value will be an array with the first 3 members being empty, because of the following lines:

    $View.GetType().InvokeMember(“Execute”, “InvokeMethod”, $null, $View, $null)
    $MSIDatabase.GetType().InvokeMember(“Commit”, “InvokeMethod”, $null, $MSIDatabase, $null)
    $View.GetType().InvokeMember(“Close”, “InvokeMethod”, $null, $View, $null)

    If you pipe each of those to Out-Null, then $Value will be a string. This is a stupid powershell limitation of how it deals with returns.

    In addition, if you aren’t making any changes to the MSI, you shouldn’t commit them; which will update the modified date, which I find undesirable, so I comment out the commit line.

    • Nickolaj
      Posted at 13:06 March 2, 2017
      Nickolaj
      Reply
      Author

      Hi Greg,

      You’re making some good points.

      Regards,
      Nickolaj

  • Gomer Pyle
    Posted at 02:09 March 3, 2017
    Gomer Pyle
    Reply
    Author

    For Working with MSIs check on this example on Microsoft Technet Gallery

    https://gallery.technet.microsoft.com/Reading-and-Writing-to-MSI-c54e15c5

  • Leave a Reply