Pete Hinchley: Update CAB File and MSI Transform via Command Line

Here is the scenario: You've been provided with an MSI. The installer includes a transform (an MST) that references a cabinet (CAB) file. The cabinet includes a single configuration file which you want to modify. This involves updating the cabinet with a modified configuration file, and then updating the transform, as ths MST includes a reference to the size of each file within the cabinet.

Here is the setup:

Let's start by extracting the content of the cabinet into a subfolder of c:\scripts. From a command prompt, run the following:

mkdir c:\scripts\cabinet
expand c:\scripts\app.cab -F:*.* c:\scripts\cabinet

Let's assume the cabinet contained a single configuration file named app.ini. After you have modified the extracted file, you need to add it back into a new cabinet. To do this, create a "cabinet directive" file named c:\scripts\cabinet\cabinet.ddf with the following content:

.OPTION EXPLICIT
.Set CabinetNameTemplate="app.cab"
.set DiskDirectoryTemplate=CDROM
.Set CompressionType=MSZIP
.Set UniqueFiles="OFF"
.Set Cabinet=on
.Set DiskDirectory1=.
app.ini

Note: If the cabinet included more than one file, it is essential those files are listed at the bottom of the ddf in the exact same order as they are listed within the Files table of the MSI/MST.

Now we can run the following commands to create the new cabinet (replacing the original):

cd c:\scripts\cabinet
makecab.exe /f cabinet.ddf
move /Y cabinet.ddf ..

Now that you have updated the cabinet, you need to update the MST to reflect the size of the modified configuration file (app.ini). We will do this via the following PowerShell script:

$installer = "C:\Scripts\app.msi"
$transform = "C:\Scripts\app.mst"

$tempmsi = "$installer.tmp"
$tempmst = "$transform.tmp"

copy-item $installer $tempmsi -force
copy-item $transform $tempmst -force

# id of file to be modified within file table of msi.
$id = "app.ini"

# path to file that has been updated in the cabinet.
$file = "c:\scripts\cabinet\app.ini"

# size of updated file.
$size = (get-item $file).length

$msi = New-Object -ComObject WindowsInstaller.Installer

$db1 = $msi.OpenDatabase($tempmsi, 2)
$db1.ApplyTransform($tempmst, 0)

$view = $db1.OpenView("UPDATE File SET FileSize = $size WHERE File = '$id'")
$view.Execute()
$view.Close()
$view = $null

$db2 = $msi.OpenDatabase($installer, 2)
$db1.GenerateTransform($db2, $transform)

$transform = $null
$db1 = $null
$db2 = $null
$msi = $null

[GC]::Collect()

start-sleep -seconds 5

remove-item $tempmsi -force
remove-item $tempmst -force

The following PowerShell script can be used to confirm that the MST was updated successfully.

$installer = "C:\Scripts\app.msi"
$transform = "C:\Scripts\app.mst"

$id = "app.ini"

$file = "c:\scripts\cabinet\app.ini"

# actual size of file updated in the cabinet.
$actualsize = (get-item $file).length

$msi = New-Object -ComObject WindowsInstaller.Installer
$db  = $msi.OpenDatabase($installer, 0)
$db.ApplyTransform($transform, 0)
$view = $db.OpenView("SELECT FileSize FROM File WHERE File = '$id'")

$view.Execute()
$item = $view.Fetch()
$size = $item.StringData(1)
$view.Close()
$view = $null

if ($size -eq $actualsize) {
  write-host "The transform was updated successfully."
} else {
  write-host "The size of the file in the transform does not match the actual size of the file."
}

$transform = $null
$db = $null
$msi = $null

[GC]::Collect()

Note: The $id variable is used to identify the relevant row in the File table of the MSI/MST. This is a unique identifier, and is not always the same as the unqualified file name.