Windows Installer packages (MSI files) contain various public properties that control installation behavior. These properties can be set during installation via command line (using PROPERTY=value syntax) or through transforms. Common examples include INSTALLDIR for installation path and ALLUSERS for per-machine vs per-user installation.
Several methods exist to retrieve public properties from MSI files:
1. Using Orca (Windows SDK Tool):
Orca shows properties in the Property table. While powerful, it requires manual inspection.
2. PowerShell with WindowsInstaller COM Object:
$installer = New-Object -ComObject WindowsInstaller.Installer
$database = $installer.OpenDatabase("example.msi", 0)
$view = $database.OpenView("SELECT * FROM Property")
$view.Execute()
while ($record = $view.Fetch()) {
Write-Output "$($record.StringData(1)) = $($record.StringData(2))"
}
$view.Close()
For more robust solutions, consider using DTF (Deployment Tools Foundation):
using Microsoft.Deployment.WindowsInstaller;
public void ListMsiProperties(string msiPath) {
using (Database db = new Database(msiPath, DatabaseOpenMode.ReadOnly)) {
foreach (var prop in db.Properties) {
Console.WriteLine($"{prop.Name} = {prop.Value}");
}
}
}
To understand where properties are actually used in the installation logic:
SELECT * FROM CustomAction WHERE Type = 51 OR Type = 6 OR Type = 35
UNION
SELECT * FROM Control WHERE Control.Condition LIKE '%PROPERTY%'
Some properties marked as secure in the Property table won't appear in logs. Check for properties with:
SELECT * FROM MsiSecureCustomProperties
When working with Windows Installer packages (MSI files), public properties serve as crucial configuration points that can be modified during installation. These properties typically begin with uppercase letters and can be set via command line parameters or transforms.
The most reliable way to extract properties is through the Windows Installer API. Here's a PowerShell script that enumerates all public properties:
function Get-MsiProperties {
param(
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$msiPath
)
try {
$windowsInstaller = New-Object -ComObject WindowsInstaller.Installer
$database = $windowsInstaller.OpenDatabase($msiPath, 0)
$query = "SELECT Property FROM Property"
$view = $database.OpenView($query)
$view.Execute()
$properties = @()
while ($record = $view.Fetch()) {
$propertyName = $record.StringData(1)
if ($propertyName -cmatch '^[A-Z]') {
$properties += $propertyName
}
}
$view.Close()
return $properties | Sort-Object
}
finally {
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($database) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($windowsInstaller) | Out-Null
}
}
Get-MsiProperties -msiPath "C:\path\to\package.msi"
Microsoft's Orca tool (part of the Windows SDK) provides a GUI for inspecting MSI files:
- Install Windows SDK and locate Orca.exe
- Open your MSI file
- Navigate to the Property table
- Filter for properties where the first character is uppercase
The WiX toolkit includes dark.exe which can decompile MSI files. Run:
dark.exe -x output_dir package.msi
This extracts the MSI into several XML files where you can find properties in the *.wxs files under <Property> elements.
Many applications use custom property conventions. For example, SQL Server MSIs typically include:
- INSTANCENAME - The named instance identifier
- AGTSVCACCOUNT - SQL Agent service account
- SQLSYSADMINACCOUNTS - Administrator accounts
When working with undocumented MSIs:
- Always test properties in a non-production environment first
- Check the vendor's documentation for common property patterns
- Compare multiple versions of the same product's MSI to identify stable properties
- Use verbose logging during installation to see which properties are being evaluated
The Windows Installer Deployment Tools Foundation (DTF) provides managed API access:
using Microsoft.Deployment.WindowsInstaller;
public static List<string> GetPublicProperties(string msiPath)
{
var properties = new List<string>();
using (var database = new Database(msiPath, DatabaseOpenMode.ReadOnly))
{
using (var view = database.OpenView("SELECT Property FROM Property"))
{
view.Execute();
foreach (var record in view)
{
string property = record.GetString(1);
if (char.IsUpper(property[0]))
{
properties.Add(property);
}
}
}
}
return properties;
}