MSEndpointMgr

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.

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

Chief Technical Architect and Enterprise Mobility MVP since 2016. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows devices and deployments including automation. Awarded as PowerShell Hero in 2015 by the community for his script and tools contributions. Creator of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService to name a few. Frequent speaker at conferences such as Microsoft Ignite, NIC Conference and IT/Dev Connections including nordic user groups.

25 comments

  • This is so good. Now I can compare MSI versions in my scripts and only execute the most recent ones.
    Talk about time saver.

    Thanks a lot.

  • Works great, just execute the code and within seconds the MSI GUID is there. Nice to use for SCCM detections.

  • Hello All,
    I am new to powershell and MSI packages. I would like to be able to extract the property table and summary table of an MSI file in an excel / csv file. How can I achieve this with powershell? It would be great to have a prompt for selecting the MSI file.

  • You star! Just want I needed. So many scripts online need tweaking to get them to work, but yours was just copy, paste, run. Thank you!

  • 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.

  • 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

  • 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!

  • 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 🙂

  • 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.

  • 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)

    • 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

      • 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?

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

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

      • as stated in my comment..
        I used a false path-variable.. i.e i was checking a file that did not exist =)

      • Was getting the same error. I was using a relative file path. Replaced it with the full path and it worked.

    • 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,

Sponsors