How to access an MSI database table using PowerShell

Written by Horatiu Vladasel · June 30th, 2023

If you’ve worked with MSI Windows Installer, you most likely know that the Windows Installer database includes various interrelated tables as a standard format for component management.

Having a standard format makes MSI Windows Installer a good candidate for any automation tasks you may need to perform.

Let’s suppose you want to search for MSI packages within your infrastructure that install a specific version of a file. We can use PowerShell for that. In this article, we'll guide you through the creation of a PowerShell script that allows you to access a specific MSI table.

Why PowerShell?

First things first, PowerShell is installed by default on all Windows systems (both clients and servers). It comes with various modules that, if needed, you can use for a wide variety of tasks.

PowerShell also comes with PowerShell ISE (Integrated Scripting Environment), which you can use to write, test, and debug your scripts in GUI editor mode. It also adds helpful features like syntax coloring, tab completion, context-sensitive help, etc.

How to automate with PowerShell

Firstly, we need to:

1. Create the WindowsInstaller.Installer COM object and load the MSI:

$MSIFilePath = "C:\Users\HoratiuVladasel\MSI_Repository\vlc-3.0.17.4-win64.msi"
$WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
$WindowsInstallerDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @(($MSIFilePath), 0))

2. Once the MSI is loaded, we need to open the table we are interested in:

$WindowsInstallerDatabaseView = $WindowsInstallerDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $WindowsInstallerDatabase, "SELECT * FROM File")
$WindowsInstallerDatabaseView.GetType().InvokeMember("Execute", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)

3. Then, we need to loop through the table and get the details we need - in our scenario, the file name and file version.

4. After making sure that the values retrieved for file name and file version match the ones we were looking for, it's time to display them.

$WindowsInstallerDatabaseRow = $WindowsInstallerDatabaseView.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
    while (($null -ne $WindowsInstallerDatabaseRow) -AND (-Not $SearchFor)) {
        # Add property and value to hash table
        If (($WindowsInstallerDatabaseView.GetType().InvokeMember("StringData", "GetProperty", $null, $WindowsInstallerDatabaseRow, 3) -like "*$SearchForFileName") -AND ($WindowsInstallerDatabaseView.GetType().InvokeMember("StringData", "GetProperty", $null, $WindowsInstallerDatabaseRow, 5) -eq $SearchForFileVersion))
        {
            $SearchFor = $true
        }
        # Fetch the next rowtch", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
        $WindowsInstallerDatabaseRow = $WindowsInstallerDatabaseView.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
    }
    $WindowsInstallerDatabaseView.GetType().InvokeMember("Close", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
    If ($SearchFor)
    {
        Write-Host "File" $SearchForFileName "version" $SearchForFileVersion "found in" $MSIFilePath
    }

5. Following up, create a loop to go through all the MSI files we’ve got within our MSI repository. Display only the ones that install a specific version of a file.

$SearchFolder = "C:\Users\HoratiuVladasel\Downloads\xxx"
$SearchForFileName = "libflac_plugin.dll"
$SearchForFileVersion = "3.0.17.4"
$MSIFilePathArray = Get-ChildItem -Path $SearchFolder -File -Recurse | Where-Object { $_.Name -Match '.*\.msi$' } | % { $_.FullName }
ForEach ($MSIFilePath in $MSIFilePathArray)
{
    # access MSI table code
}

And that’s it.

Find the full script here.

NoteNote: The script takes the $SearchFolder, $SearchForFileName and $SearchForFileVersion as inputs - you can modify these 3 variables as per your needs.

$SearchFolder = "C:\Users\HoratiuVladasel\Downloads\xxx"
$SearchForFileName = "libflac_plugin.dll"
$SearchForFileVersion = "3.0.17.4"
$MSIFilePathArray = Get-ChildItem -Path $SearchFolder -File -Recurse | Where-Object { $_.Name -Match '.*\.msi$' } | % { $_.FullName }
ForEach ($MSIFilePath in $MSIFilePathArray)
{
    [bool] $SearchFor = $false
    # Creating WI object and load MSI database
    $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer
    $WindowsInstallerDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $null, $WindowsInstaller, @(($MSIFilePath), 0))
    # Open the Property-view
    $WindowsInstallerDatabaseView = $WindowsInstallerDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $WindowsInstallerDatabase, "SELECT * FROM File")
    $WindowsInstallerDatabaseView.GetType().InvokeMember("Execute", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
    # Loop through the table
    $WindowsInstallerDatabaseRow = $WindowsInstallerDatabaseView.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
    while (($null -ne $WindowsInstallerDatabaseRow) -AND (-Not $SearchFor)) {
        # Add property and value to hash table
        If (($WindowsInstallerDatabaseView.GetType().InvokeMember("StringData", "GetProperty", $null, $WindowsInstallerDatabaseRow, 3) -like "*$SearchForFileName") -AND ($WindowsInstallerDatabaseView.GetType().InvokeMember("StringData", "GetProperty", $null, $WindowsInstallerDatabaseRow, 5) -eq $SearchForFileVersion))
        {
            $SearchFor = $true
        }
        # Fetch the next rowtch", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
        $WindowsInstallerDatabaseRow = $WindowsInstallerDatabaseView.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
    }
    $WindowsInstallerDatabaseView.GetType().InvokeMember("Close", "InvokeMethod", $null, $WindowsInstallerDatabaseView, $null)
    If ($SearchFor)
    {
        Write-Host "File" $SearchForFileName "version" $SearchForFileVersion "found in" $MSIFilePath
    }
}

Conclusion

To sum up, PowerShell's capabilities extend beyond being a simple scripting language. Its versatility and robustness make it a valuable tool for IT professionals, especially when it comes to tasks like accessing tables within an MSI.

Since it is the default on all Windows systems, it offers a convenient and powerful solution to drive automation and streamline operations.

The script outlined in this article provides an excellent starting point, and with a little customization, it can be a significant asset in your IT toolkit.

Written by
See author's page
Horatiu Vladasel

Horatiu is a Software Packager/Sequencer with over 10 years experience, who has worked as a Software Packager at IBM and is currently offering software packaging services to companies such as BT or Nationwide.

Comments: