Installing a patch will often mean that files are to be updated, though patching isn’t limited or even required to just patch files. Starting with Windows Installer 3.0 you can find the final sequence of patches to be installed or reinstalled in a verbose log file similar to the following:
MSI (c) (8C:C4) [14:27:53:319]: Final Patch Application Order:
MSI (c) (8C:C4) [14:27:53:319]: {3DB7C93B-AA16-49E8-9D9E-D427F2C5DDEB}
MSI (c) (8C:C4) [14:27:53:319]: {685E109C-738F-4575-B1FA-CF645F3611F0} – Example.msp
Transforms from all applicable patches will be resolved in that order. Read How Patching Works for more details.
But how do you know which patch is installing which files? Before I go into a solution lets cover a few basic principles of repairing and patching a product.
When you repair a product you can specify REINSTALL=ALL
, or rather a comma-delimited subset of your features. This tells Windows Installer to evaluate components under all features or the specified features, respectively, to determine if any components require reinstallation. Of course, if REINSTALLMODE
includes “a” (force all files to be reinstalled) all components under specified features are reinstalled. When you install or uninstall a patch the same thing happens except that transforms contained in the patch that are applicable to the current product being reinstalled modify the view so that those modifications are processed as necessary when the product is repaired.
Windows Installer uses the Sequence column in the File table to determine which entry in the Media table to use as the source of the file. When you install a patch one of the pair of transforms starting with a # symbol adds an entry to the Media table if files are to be patched and changes the Sequence column for that file or files in the File table so that the source of the patched file is now a cabinet embedded in the .msp patch file. Prior to Windows Installer 2.0 there could be no overlap in the starting sequence for different patches, but Windows Installer 2.0 introduced a feature that would rebase file sequences automatically because keeping track of file sequences could be difficult.
With that knowledge in mind, you can write a custom action that will use the related information from the File and Media tables along with the PatchPackage table that the # transform adds to determine which files are contained in which patch. Recall from What’s in a Patch that this transform is not applied – thus the PatchPackage table will not exist – when installing a patched administrative installation.
To begin you should query for related records in the PatchPackage and Media tables. Be sure to check that at least the PatchPackage table exists otherwise the MsiDatabaseOpenView
function or Database.OpenView will fail. The SQL for this query could look like this:
SELECT DISTINCT `PatchPackage`.`PatchId`, `Media`.`LastSequence` FROM `PatchPackage`, `Media` WHERE `PatchPackage`.`Media_` = `Media`.`DiskId` ORDER BY `LastSequence`
We order by LastSequence in the Media table because we need to know what the previous sequence was when querying for files between those sequences. Next we need to determine what the last sequence of files that are not being patched is. Since we sorted by LastSequence above we can use the first sequence number and query the Media table for entries less than that number and get the last result. See the GetPreviousSequence
function in my example prototype implementation.
Once you have the start and end sequences you can select all files from the File table that are greater than the starting sequence (the LastSequence of the previous Media table record) and less than or equal to the LastSequence for the current patch-related Media table record:
SELECT `File` FROM `File` WHERE `Sequence` > ” & lastSequence & ” AND `Sequence` <= ” & currentSequence
The example prototype implementation of such a custom action uses this information to build a table called PatchFile with the given archive text file format:
PatchId_ File_
s38 s72
PatchFile PatchId_ File_
This information shouldn’t be used to determine if a file is actually being reinstalled, however. To determine if a file is actually being reinstalled you should use its component action by prefixing the component name that contains the file with a $ symbol and check for INSTALLSTATE_LOCAL
(1). This could be used in a conditional statement or even evaluated with the MsiEvaluateCondition
function.
The example prototype implementation is provided only for information purposes and is not supported by Microsoft. It is written in VBScript to provide a clear understanding of how to determine which files are being patched and to dump tables’ structure and, optionally, contents in another custom action provided for diagnostics purposes. It is highly recommended that you do not use script custom actions because they’re very difficult to debug. Also included in the .zip file is a file named Debug.wsf for the very reason that script custom actions are difficult to debug, and I’m limited in what I could do. If you want to include such diagnostic information in your product or even use that information to make decisions about what actions to take on patched files you should implement your custom action in native code as, for example, an immediate custom action in an embedded DLL (a type 1 custom action).
0 comments