MAKEMSI quickly and reliably creates MSI files in a non-programmatic way
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
[Bottom][Contents][Prev]: MmLockFile.VBS[Next]: MSI (Windows Installer) Platform SDK
Have your say! Join the MAKEMSI discussion list or view archive! Suggest improvements. No question too simple or too complex.
\->Tips and Tricks->Tools->MSIDIFF.VBS

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:

  1. Does not show database code page differences.

  2. Does not show any "_Storages" table differences.

  3. Does not show any "_Streams" table differences. If the stream was created via another table (such as the "Binary" table) it will pick it up but the output value is text describing the value rather than the contents of the file (or a link to the file).

  4. If the MSIs has two tables with different schema a compare will not be possible. This is a limitation which is unlikely to be removed as it is a Windows Installer restriction. The compare functionaility is in any case mainly useful for comparing similar databases built using the same tools.

    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.

  5. Summary items longer than 259 characters can't be retrieved (therefore compared). Apparently this Windows Installer bug has been fixed in "Windows Server 2003 and later versions", why the fix can't be addressed for all other operating systems is unknown...

    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:

ActionSequence
Number
FatalError-3
UserExit-2
ExitDialog-1
LaunchConditions100
PrepareDlg140
FindRelatedProducts200
AppSearch400
CCPSearch500
RMCCPSearch600
CostInitialize800
FileCost900
CostFinalize1000
MigrateFeatureStates1200
WelcomeDlg1230
ResumeDlg1240
MaintenanceWelcomeDlg1250
ProgressDlg1280
ExecuteAction1300

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">


Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006 & 2007 for the Windows SDK (Windows Installer) area.This external link was OK when tested at 27 Dec 2008Please email me any feedback, additional information or corrections.
See this page online (look for updates)

[Top][Contents][Prev]: MmLockFile.VBS[Next]: MSI (Windows Installer) Platform SDK


MAKEMSI© is (C)opyright Dennis Bareis 2003-2008 (All rights reserved).
Saturday December 27 2008 at 3:15pm
Visit MAKEMSI's Home PageThis external link was OK when tested at 7 Dec 2008

HTML page dated Mon, 29 Jan 2007 00:11:11 GMT
Microsoft awarded me an MVP (Most Valuable Professional award) in 2004, 2005, 2006 & 2007 for the Windows SDK (Windows Installer) area.This external link was OK when tested at 27 Dec 2008