Merge Multiple Windows Installer Transforms

Description:

This script will merge multiple Windows Installer transforms into one single transform.

Usage:

CScript.exe {Script} {MSI} {Transform 1} {Transform 2} {Transform x..}
(or if transform ordering is not important, drag an MSI and multiple MSTs onto the VBS file)

Script:


'set up log file
Dim fso : Set fso = CreateObject("Scripting.FileSystemObject")
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8

'create 2 constants - one for when we want to just query the MSI (read) and one for when we want to make changes (write)

Const msiOpenDatabaseModeReadOnly = 0
Const msiOpenDatabaseModeTransact = 1

' Adds a row that already exists.
Const msiTransformErrorAddExistingRow = 1 
' Deletes a row that does not exist.
Const msiTransformErrorDeleteNonExistingRow = 2 
' Adds a table that already exists.
Const msiTransformErrorAddExistingTable = 4 
' Deletes a table that does not exist.
Const msiTransformErrorDeleteNonExistingTable = 8 
' Updates a row that does not exist.
Const msiTransformErrorUpdateNonExistingRow = 16 
' Transform and database code pages do not match and neither has a neutral code page.
Const msiTransformErrorChangeCodePage = 32 
' Creates the temporary _TransformView table.
Const msiTransformErrorViewTransform = 256 

Dim errorCondition : errorCondition = msiTransformErrorChangeCodePage + msiTransformErrorUpdateNonExistingRow + msiTransformErrorDeleteNonExistingTable _
+ msiTransformErrorAddExistingTable + msiTransformErrorDeleteNonExistingRow + msiTransformErrorAddExistingRow

'create WindowsInstaller.Installer object
Dim oInstaller : Set oInstaller = CreateObject("WindowsInstaller.Installer")

Dim i
Dim msiname : msiname = ""
'Find MSI name.  This is useful if the order of applying the transform is not important and we want to just drag an MSI and a bunch of MSTs on to the script 
For i = 0 To Wscript.Arguments.Count - 1
	If Right(LCase(WScript.Arguments.Item(i)),3) = "msi" Then
		msiname = WScript.Arguments.Item(i)
	End If
Next

'create a name/path for the transform
Dim originalMSIPath : Set originalMSIPath = fso.GetFile(msiname)  
Dim mergedMSTPath : mergedMSTPath = Left(originalMSIPath.Path, InStrRev(originalMSIPath.Path, ".") - 1) & "_Merged.MST"

'create a name/path for log File
Dim logFile : logFile = Left(originalMSIPath.Path, InStrRev(originalMSIPath.Path, ".") - 1) & ".log"

Dim objLogFile : Set objLogFile = fso.OpenTextFile(logFile, ForAppending, True)

WriteLog "Merging Transforms"
WriteLog "Processing: " & originalMSIPath.Name

'create a temp filename in the same folder as the MSI
Dim TempMSI : TempMSI = originalMSIPath.ParentFolder & "\" & fso.GetTempName
Dim TempMST : TempMST = ""

'create a copy opf the MSI, and call it our temporary name. 
originalMSIPath.Copy (TempMSI)   
'set the file attributes of the temporary MSI
fso.GetFile(TempMSI).Attributes = 0

	'open our temp MSI
	Dim tempDatabase : Set tempDatabase = oInstaller.OpenDatabase(TempMSI, msiOpenDatabaseModeTransact)

	'apply all our transforms
	For i = 0 To Wscript.Arguments.Count - 1
		If Right(LCase(WScript.Arguments.Item(i)),3) = "mst" Then
			TempMST = WScript.Arguments.Item(i)
			tempDatabase.ApplyTransform TempMST, errorCondition
			tempDatabase.Commit
			WriteLog "Applying Transform: " & Right(WScript.Arguments.Item(i), Len(WScript.Arguments.Item(i)) - InStrRev(WScript.Arguments.Item(i), "\"))
		End If	
	Next

'open our original, un-transformed MSI
Dim originalMSI : Set originalMSI = oInstaller.OpenDatabase(originalMSIPath.Path, 0)

'Get the difference between our currently opened database with transforms applied (tempDatabase) and our original database (originalMSI), and generate
'a transform with the path of 'mergedMSTPath'
tempDatabase.GenerateTransform originalMSI, mergedMSTPath

'Create a summary information stream for the transform
tempDatabase.CreateTransformSummaryInfo originalMSI, mergedMSTPath, 0, 0

Const PID_TITLE = 2
Const PID_SUBJECT = 3
Const PID_AUTHOR = 4
Const PID_KEYWORDS = 5
Const PID_COMMENTS = 6
Const PID_TEMPLATE = 7
Const PID_PAGECOUNT = 14
Dim PID_TITLE_value : PID_TITLE_value = ""
Dim PID_SUBJECT_value : PID_SUBJECT_value = ""
Dim PID_AUTHOR_value : PID_AUTHOR_value = ""
Dim PID_KEYWORDS_value : PID_KEYWORDS_value = ""
Dim PID_COMMENTS_value : PID_COMMENTS_value = ""
Dim PID_TEMPLATE_value : PID_TEMPLATE_value = ""
Dim PID_PAGECOUNT_value : PID_PAGECOUNT_value = ""

'read SIS values from last applied transform
Dim sumInfo  : Set sumInfo = oInstaller.SummaryInformation(TempMST, 0)
PID_TITLE_value = sumInfo.Property(PID_TITLE)
PID_SUBJECT_value = sumInfo.Property(PID_SUBJECT)
PID_AUTHOR_value = sumInfo.Property(PID_AUTHOR)
PID_KEYWORDS_value = sumInfo.Property(PID_KEYWORDS)
PID_COMMENTS_value = sumInfo.Property(PID_COMMENTS)
PID_TEMPLATE_value = sumInfo.Property(PID_TEMPLATE)
PID_PAGECOUNT_value = sumInfo.Property(PID_PAGECOUNT)
Set sumInfo = Nothing

'now set the values of the SIS in the new transform
Set sumInfo = oInstaller.SummaryInformation(mergedMSTPath, 7)
sumInfo.Property(PID_TITLE) = PID_TITLE_value
sumInfo.Property(PID_SUBJECT) = PID_SUBJECT_value
sumInfo.Property(PID_AUTHOR) = PID_AUTHOR_value
sumInfo.Property(PID_KEYWORDS) = PID_KEYWORDS_value
sumInfo.Property(PID_COMMENTS) = PID_COMMENTS_value
sumInfo.Property(PID_TEMPLATE) = PID_TEMPLATE_value
sumInfo.Property(PID_PAGECOUNT) = PID_PAGECOUNT_value
'persist changes
sumInfo.Persist
Set sumInfo = Nothing

'close database
Set tempDatabase = Nothing

Dim File : Set File = fso.GetFile(TempMSI)
'we need to ensure all view and record objects are closed, otherwise the deletion will fail!
File.Delete
Set File = Nothing

WriteLog mergedMSTPath & " was created successfully!"

objLogFile.Close
Set fso = Nothing
Set objLogFile = Nothing

Set originalMSI = Nothing
Set oInstaller = Nothing

Sub WriteLog(LogMessage)

	WScript.echo Now() & ": " & LogMessage
    objLogFile.Writeline(Now() & ": " & LogMessage)

End Sub