|
![]() |
| MSIDIFF.VBS |
This is a tool I have written to dump the contents of an MSI or to compare two databases and dump the differences (an "MSI to TEXT" utility). Either way the output is a set of MAKEMSI commands which can be executed to apply the modifications. I recommend that you also have a look at the "Using ORCA and MSIDIFF to Automate MSI Updates" section.
This program can actually process any Windows installer database types (for example ".msm" merge modules) and not just MSI files. Patch files can also be processed. Even transforms (undocumented binary format) are supported via the "/transform" switch.
MSIDIFF installs the following Explorer based options (see image below) as an alternative way of invoking this command line based tool:
This could be very useful if you have a GUI based MSI tool which does "something" you wish to automate. Simply create a "before" snapshot and make a single change and save the result as the "after" version. You can then dump the changes and see what changes were made to the MSI. The "Control Removal" tip in the MAKEMSI manual demonstrates this.
The code this program generates is simplistic and it is typically best to touch it up, for example where a columns value changed from 3 to 2 what may actually have happened is that a particular bit was reset. It might be best to recode it that way yourself (using any constants that may apply)...
| BEFORE IMAGE / TEMPLATE |
While you can dump a complete MSI this is typically not what you really wish to do.
Typically you want to know what makes the MSI you are interested in different from any other, for this reason it is a good idea to create a "blank" MSI which basically does nothing.
With a GUI tool such as Wise for Windows PRO you might ask it to create an empty MSI and save this, with MAKEMSI you could either use the template MSI (probably "uisample.msi") or ask MAKEMSI to create an MSI with whatever minimal information you require.
Now when you dump your MSI (comparing with the template) only the changes will be dumped so constant "Dialog" or "_Validation" table information etc will not get dumped.
| SYNTAX |
MsiDiff[.VBS] [MsiBefore] MsiNow [+MsiTable] [-MsiTable]
/File FileName
/Transform FileName
/NoConsole /Column
By default this command dumps all tables, the "+" and "-" commands can be used to alter this. MSI table names are of course case sensitive.
To dump all tables you should not pass a value for "MsiBefore", it will then create an "empty" database and compare against that.
One or more "+" commands are used to specify the table(s) to be included (for example "+File +Shortcut"), in this case only the listed tables are dumped or compared.
The "-" command is used to specify tables that should not be processed, it is not valid (as it does not make sense) to use a "-" command if any "+" commands have been used.
The "/Transform" option allows you to dump a ".MST" (transform) file. The transform is applied to "MsiNow" and compared to the original "MsiNow" (without the transform). A transform can not be dumped directly as it is in not a windows installer database (it is an undocumented binary format), you can only dump the "effect" of applying the transform.
The "/File" option allows you to create a file containing the results (without having to use operating system redirection).
The "/NoConsole" option turns off output to the screen to speed up the generation (when large amounts of output are generated). You would have used the "/File" option otherwise it makes no sense to use this switch!
The "/Column" shows you the columns being created when a table is created. Normally these are not shown as they generally don't provide a lot of information for standard tables, however if custom tables have been created (or you suspect the schema may have changed) it may be useful. You will need to "translate" the column information before you could define the tables yourself (using the MAKEMSI "TableDefinition" command).
| MSIDIFF.VBS /? |
; []--------------------------------------------------------------[]
; | MSIDIFF.VBS v08.233: Dumps MSI differences in "MakeMsi" format |
; | (C)opyright Dennis Bareis 2003-2008. All rights reserved. |
; | http://dennisbareis.com/index.htm |
; []--------------------------------------------------------------[]
Invalid arguments
~~~~~~~~~~~~~~~~~
The switch "/?" is unknown!
CORRECT SYNTAX
~~~~~~~~~~~~~~
cscript.exe [path\]MsiDiff[.VBS] [MsiBefore] Msi [Options]
OPTIONS
~~~~~~~
+Table : A table to include in output (or "Summary")
-Table : A table to exclude from output (or "Summary")
/File : Output to the specified file (next parameter).
/Transform: Apply transform (next parameter) to MSI then dump differences.
/NoConsole: Don't output (much) to console (you used /file).
/Column : Show Column Inserts. Only useful if custom tables are included.
/NoSort : Don't sort table rows (row order significant?).
/JustTableData : Only output TABLE and ROW macros.
/NoComments : Remove all lines starting with semicolon.
/MacroPrefix : Next parameter prefixes all macro names.
You should also handle the special "MsiDiffError" macro!
If "MsiBefore" is supplied then a compare between the 2 databases is performed,
otherwise the complete database is dumped.
Note that any Windows Installer file (such as merge modules) can also be dumped
and compared. The output can also be redirected.
| LIMITATIONS (current) |
Known limitations are:
If you still want to compare them you could remove the offending table or update its schema but you could also dump both MSIs separately and use "WinMerge.EXE" to compare.
This restriction now only applies if the program "MsiSummRead.exe" is not located in the same directory as MSIDIFF.
If we could not find any differences and the databases have the same file size then they are compared by MD5 (contents), if these match then there is absolutely no doubt that the databases are identical. The MD5 can only be calculated on machines which have Windows Installer version 2.0+ installed.
| EXAMPLE 1 - comcat.msm (merge module) |
The following command was used to dump "comcat.msm" (the "_Validation" table was ignored):
cscript msidiff.vbs "Merge Modules\comcat.msm" -_Validation > comcat.txt
The dumped merge module follows:
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation. All rights reserved.
; []--------------------------------------------------------------[]
; | MSIDIFF.VBS v08.233: Dumps MSI differences in "MakeMsi" format |
; | (C)opyright Dennis Bareis 2003-2008. All rights reserved. |
; | http://dennisbareis.com/index.htm |
; []--------------------------------------------------------------[]
;======================================================================
;===========================[ DUMP ]===================================
;======================================================================
; The following paths may need to be modified depending on where the
; script is located (generated).
; You may also wish to make some of the hard coded values generic so
; that the script will work for any MSI or uses different values, in
; this case you may wish to use PPWIZARD's "/define" switch to
; pass values as a PPWIZARD macro (via the command line).
; The environment is another easy option.
;
; READING THIS OUTPUT
; ~~~~~~~~~~~~~~~~~~~
; A Windows Installer file is a form of SQL database and so
; contains SQL table and rows.
; The SQL is displayed in the form required by MAKEMSI to create
; the file's SQL tables and rows.
;
; DUMPING
; ~~~~~~~
; FILE: Merge Modules\comcat.msm
; SIZE: 19,456
; MD5: 58A0732F-492E2CE3-71AB6B58-8E4E2C64
;
; TABLES
; ~~~~~~
; INCLUDE :
; EXCLUDE : _Validation
;======================================================================
#include "OpenMsi.MMH"
<$Msi 'Merge Modules\comcat.msm' CREATE='Y'>
<$Table "ModuleSignature">
<$TableCreate>
#(
<$Row
ModuleID="COMCAT.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Language="0"
Version="4.71.1460.1"
>
#)
<$/Table>
<$Table "FeatureComponents">
<$TableCreate>
<$/Table>
<$Table "Directory">
<$TableCreate>
#(
<$Row
Directory="MS.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Directory_Parent="Redist.3207D1B0_80E5_11D2_B95D_006097C4DE24"
DefaultDir=".:MS"
>
#)
#(
<$Row
Directory="Redist.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Directory_Parent="SystemFolder"
DefaultDir=".:Redist"
>
#)
#(
<$Row
Directory="System.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Directory_Parent="MS.3207D1B0_80E5_11D2_B95D_006097C4DE24"
DefaultDir=".:System"
>
#)
#(
<$Row
Directory="SystemFolder"
Directory_Parent="TARGETDIR"
DefaultDir="."
>
#)
#(
<$Row
Directory="TARGETDIR"
Directory_Parent=""
DefaultDir="SourceDir"
>
#)
<$/Table>
<$Table "File">
<$TableCreate>
#(
<$Row
File="Global_Controls_COMCATDLL_f0.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
FileName="comcat.dll"
FileSize="22288"
Version="4.71.1460.1"
Language="1033"
Attributes="0"
Sequence="1"
>
#)
<$/Table>
<$Table "Registry">
<$TableCreate>
#(
<$Row
Registry="Global_Controls_COMCATDLL_r0.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Root="0"
Key="CLSID\{0002E005-0000-0000-C000-000000000046}\InprocServer32"
Name="ThreadingModel"
Value="Both"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
#(
<$Row
Registry="Global_Controls_COMCATDLL_r1.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Root="0"
Key="Component Categories"
Name="+"
Value=""
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
#(
<$Row
Registry="Global_Controls_COMCATDLL_r2.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Root="0"
Key="Component Categories\{40FC6ED3-2438-11CF-A3DB-080036F12502}"
Name="409"
Value="Embeddable Objects"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
#(
<$Row
Registry="Global_Controls_COMCATDLL_r3.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Root="0"
Key="Component Categories\{40FC6ED4-2438-11CF-A3DB-080036F12502}"
Name="409"
Value="Controls"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
#(
<$Row
Registry="Global_Controls_COMCATDLL_r4.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Root="0"
Key="Component Categories\{40FC6ED5-2438-11CF-A3DB-080036F12502}"
Name="409"
Value="Automation Objects"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
#(
<$Row
Registry="Global_Controls_COMCATDLL_r5.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Root="0"
Key="Component Categories\{40FC6ED8-2438-11CF-A3DB-080036F12502}"
Name="409"
Value="Document Objects"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
#(
<$Row
Registry="Global_Controls_COMCATDLL_r6.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Root="0"
Key="Component Categories\{40FC6ED9-2438-11CF-A3DB-080036F12502}"
Name="409"
Value="_Printable Objects"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
<$/Table>
<$Table "Extension">
<$TableCreate>
<$/Table>
<$Table "MIME">
<$TableCreate>
<$/Table>
<$Table "Class">
<$TableCreate>
#(
<$Row
CLSID="{0002E005-0000-0000-C000-000000000046}"
Context="InprocServer32"
Component_="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
ProgId_Default=""
Description="Component Categories Manager"
AppId_=""
FileTypeMask=""
Icon_=""
IconIndex=""
DefInprocHandler=""
Argument=""
Feature_="{00000000-0000-0000-0000-000000000000}"
Attributes=""
>
#)
<$/Table>
<$Table "ProgId">
<$TableCreate>
<$/Table>
<$Table "Verb">
<$TableCreate>
<$/Table>
<$Table "Component">
<$TableCreate>
#(
<$Row
Component="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
ComponentId="{3207D1B1-80E5-11D2-B95D-006097C4DE24}"
Directory_="System.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Attributes="24"
Condition=""
KeyPath="Global_Controls_COMCATDLL_f0.3207D1B0_80E5_11D2_B95D_006097C4DE24"
>
#)
<$/Table>
<$Table "ModuleComponents">
<$TableCreate>
#(
<$Row
Component="Global_Controls_COMCATDLL.3207D1B0_80E5_11D2_B95D_006097C4DE24"
ModuleID="COMCAT.3207D1B0_80E5_11D2_B95D_006097C4DE24"
Language="0"
>
#)
<$/Table>
<$Summary "CodePage" Value="1252">
<$Summary "TITLE" Value="Merge Module">
<$Summary "SUBJECT" Value="Microsoft Component Category Manager Library">
<$Summary "KEYWORDS" Value="MergeModule, MSI, Database">
<$Summary "COMMENTS" Value="This redist pack contains the logic, files, and registration information to install the Microsoft Component Category Manager Library.">
<$Summary "TEMPLATE" Value="Intel;0">
<$Summary "PackageCode" Value="{3207D1B0-80E5-11D2-B95D-006097C4DE24}">
<$Summary "CREATE_DTM" Value='cdate("31/05/2000 5:27:07 AM")'>
<$Summary "LastSave_DTM" Value='cdate("31/05/2000 5:27:07 AM")'>
<$Summary "MsiSchema" Value="110">
<$Summary "SourceType" Value="2">
<$Summary "AppName" Value="Orca">
<$Summary "SECURITY" Value="1">
;--- Tables Excluded : _Validation
<$/Msi>
| EXAMPLE 2 - Post Processing of OUTPUT! |
The generation of the MAKEMSI documentation uses "SeqTable.DH" and creates some ".html" files (from MSIDIFF.VBS output) containing the sequencing information of "uisample.msi". This output looks like:
| Action | Sequence Number |
|---|---|
| FatalError | -3 |
| UserExit | -2 |
| ExitDialog | -1 |
| LaunchConditions | 100 |
| PrepareDlg | 140 |
| FindRelatedProducts | 200 |
| AppSearch | 400 |
| CCPSearch | 500 |
| RMCCPSearch | 600 |
| CostInitialize | 800 |
| FileCost | 900 |
| CostFinalize | 1000 |
| MigrateFeatureStates | 1200 |
| WelcomeDlg | 1230 |
| ResumeDlg | 1240 |
| MaintenanceWelcomeDlg | 1250 |
| ProgressDlg | 1280 |
| ExecuteAction | 1300 |
The "MSIDIFF.VBS" output contains "table", "row" and other macros to describe the table data. It is a simple matter to define what each of these macros does when generating HTML and the "#include" this output.
The contents of "SeqTable.DH" code is:
;----------------------------------------------------------------------------
; MODULE NAME: SEQTABLE.DH
;
; $Author: USER "Dennis" $
; $Revision: 1.4 $
; $Date: 31 Jul 2007 17:49:22 $
; $Logfile: C:/DBAREIS/Projects.PVCS/Win32/MakeMsi/SeqTable.DH.pvcs $
;
; DESCRIPTION
; ~~~~~~~~~~~
; The "MsiSeqTable" macro below calls "MSIDIFF.VBS" to dump a
; "UISAMPLE.MSI" <$sequence "sequence table"> into a html file.
;
; I generate an intermediate html file for performance, this way I only
; have to generate the output once and include in as many places as I wish
; to use it (the #include is much quicker).
;
; This is used in the generation of the MAKEMSI documentation, it uses
; the table like:
;
; <$Title Text="InstallUISequence">
; <p>The default sequence of custom actions:
; #include "out\InstallUISequence.html" ;;Previously generated
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
;--- MAIN MACRO (call to generate html files for <$sequence "sequence tables">) -----------
;----------------------------------------------------------------------------
#( ''
#define MsiSeqTable
;--- Call MSIDIFF to generate "raw" output ------------------------------
#evaluate ^^ ^call AddressCmd 'cscript.exe //NoLogo out\MsiDiff.vbs uisample.msi +{$Table} /NoSort /JustTableData /MacroPrefix @@ > out\{$Table}.TXT 2>&1'^
;--- Generate the start of the TABLE/file -------------------------------
#output "out\{$Table}.HTML" ASIS
<!-- Created by "SeqTable.DH" from MSIDIFF.VBS output! --><?NewLine>
<?NewLine>
<table class="SeqTable" border=1 cellSpacing=0 cellPadding=3><?NewLine>
<tr align='left'>
<th>Action</th>
<th>Sequence<br>Number</th>
</tr><?NewLine>
;--- Generate HTML (by including the data we captured above) ------------
#include "out\{$Table}.TXT" ;;Macros (defined below) execute to produce required HTML output
;--- End the table/file -------------------------------------------------
<?NewLine>
</table>
#output
#)
;----------------------------------------------------------------------------
;--- SUPPORT MACROS (see "/MacroPrefix" on MSIDIFF.VBS command) -------------
;----------------------------------------------------------------------------
#(
#define @@TABLE
#RexxVar @@Row.0 = 0 ;;Reset count
#)
#define @@TABLECREATE
#( ''
#define @@/TABLE
;--- Sort the data --------------------------------------------------------
#evaluate '' ^call ArraySort "@@Row"^
;--- Output the information -----------------------------------------------
#{ for @@X = 1 to @@Row.0
;--- Split up info -----------------------------------------------------
#DefineRexx ''
parse var @@Row.@@X @@Ignore '00'x @@Sequence '00'x @@Action;
@@ActionLink = '<' || '$SdkStdCa "' || @@Action || '">';
#DefineRexx
;--- Output table row --------------------------------------------------
<tr>
<td><??@@ActionLink></td>
<td><??@@Sequence></td>
</tr><?NewLine>
#}
#)
#(
#define @@Row
#evaluate ^^ ^<$@@Rexx2LoadInformationInSortableFormat {$?}>^
#)
#DefineRexx '@@Rexx2LoadInformationInSortableFormat'
;--- Get parameters we care about ---------------------------------------
@@Action = '{$Action}';
@@Sequence = '{$Sequence}';
;--- Work out sort key (sort is textual - not numeric) ------------------
@@SortKey = @@Sequence + 900000;
;--- Save information ---------------------------------------------------
@@Sortable = @@SortKey || '00'x || @@Sequence || '00'x || @@Action;
@@RowCnt = @@Row.0 + 1;
@@Row.@@RowCnt = @@Sortable;
@@Row.0 = @@RowCnt;
#DefineRexx
;----------------------------------------------------------------------------
;--- Now create the ".HTML" files for the tables (will #include as required) ---
;----------------------------------------------------------------------------
<$MsiSeqTable Table="InstallExecuteSequence">
<$MsiSeqTable Table="InstallUISequence">
![]() | ![]() |