Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
12 KiB

  1. ' Windows Installer utility to generate file cabinets from MSI database
  2. ' For use with Windows Scripting Host, CScript.exe or WScript.exe
  3. ' Copyright (c) 1999, Microsoft Corporation
  4. ' Demonstrates the access to install engine and actions
  5. ' Modified on Sept 12, 2000 by Paul Thompson to save the new cab as *.cab rather than *.CAB and also use the
  6. ' SourcePath argument and FileKey for where to find the file to compress into the cab.
  7. '
  8. Option Explicit
  9. ' FileSystemObject.CreateTextFile and FileSystemObject.OpenTextFile
  10. Const OpenAsASCII = 0
  11. Const OpenAsUnicode = -1
  12. ' FileSystemObject.CreateTextFile
  13. Const OverwriteIfExist = -1
  14. Const FailIfExist = 0
  15. ' FileSystemObject.OpenTextFile
  16. Const OpenAsDefault = -2
  17. Const CreateIfNotExist = -1
  18. Const FailIfNotExist = 0
  19. Const ForReading = 1
  20. Const ForWriting = 2
  21. Const ForAppending = 8
  22. Const msiOpenDatabaseModeReadOnly = 0
  23. Const msiOpenDatabaseModeTransact = 1
  24. Const msiViewModifyInsert = 1
  25. Const msiViewModifyUpdate = 2
  26. Const msiViewModifyAssign = 3
  27. Const msiViewModifyReplace = 4
  28. Const msiViewModifyDelete = 6
  29. Const msiUILevelNone = 2
  30. Const msiRunModeSourceShortNames = 9
  31. Const msidbFileAttributesNoncompressed = &h00002000
  32. Dim argCount:argCount = Wscript.Arguments.Count
  33. Dim iArg:iArg = 0
  34. If argCount > 0 Then If InStr(1, Wscript.Arguments(0), "?", vbTextCompare) > 0 Then argCount = 0
  35. If (argCount < 2) Then
  36. Wscript.Echo "Windows Installer utility to generate compressed file cabinets from MSI database" &_
  37. vbNewLine & " The 1st argument is the path to MSI database, at the source file root" &_
  38. vbNewLine & " The 2nd argument is the base name used for the generated files (DDF, INF, RPT)" &_
  39. vbNewLine & " The 3rd argument can optionally specify separate source location from the MSI" &_
  40. vbNewLine & " The following options may be specified at any point on the command line" &_
  41. vbNewLine & " /L to use LZX compression instead of MSZIP" &_
  42. vbNewLine & " /F to limit cabinet size to 1.44 MB floppy size rather than CD" &_
  43. vbNewLine & " /C to run compression, else only generates the .DDF file" &_
  44. vbNewLine & " /U to update the MSI database to reference the generated cabinet" &_
  45. vbNewLine & " /E to embed the cabinet file in the installer package as a stream" &_
  46. vbNewLine & " /S to sequence number file table, ordered by directories" &_
  47. vbNewLine & " /R to revert to non-cabinet install, removes cabinet if /E specified" &_
  48. vbNewLine & " Notes:" &_
  49. vbNewLine & " In order to generate a cabinet, MAKECAB.EXE must be on the PATH" &_
  50. vbNewLine & " base name used for files and cabinet stream is case-sensitive" &_
  51. vbNewLine & " If source type set to compressed, all files will be opened at the root" &_
  52. vbNewLine & " (The /R option removes the compressed bit - SummaryInfo property 15 & 2)" &_
  53. vbNewLine & " To replace an embedded cabinet, include the options: /R /C /U /E" &_
  54. vbNewLine & " Does not handle updating of Media table to handle multiple cabinets"
  55. Wscript.Quit 1
  56. End If
  57. ' Get argument values, processing any option flags
  58. Dim compressType : compressType = "MSZIP"
  59. Dim cabSize : cabSize = "CDROM"
  60. Dim makeCab : makeCab = False
  61. Dim embedCab : embedCab = False
  62. Dim updateMsi : updateMsi = False
  63. Dim sequenceFile : sequenceFile = False
  64. Dim removeCab : removeCab = False
  65. Dim databasePath : databasePath = NextArgument
  66. Dim baseName : baseName = NextArgument
  67. Dim sourceFolder : sourceFolder = NextArgument
  68. If Not IsEmpty(NextArgument) Then Fail "More than 3 arguments supplied" ' process any trailing options
  69. If Len(baseName) < 1 Or Len(baseName) > 8 Then Fail "Base file name must be from 1 to 8 characters"
  70. If Not IsEmpty(sourceFolder) And Right(sourceFolder, 1) <> "\" Then sourceFolder = sourceFolder & "\"
  71. Dim cabFile : cabFile = baseName & ".cab"
  72. Dim cabName : cabName = cabFile : If embedCab Then cabName = "#" & cabName
  73. ' Connect to Windows Installer object
  74. On Error Resume Next
  75. Dim installer : Set installer = Nothing
  76. Set installer = Wscript.CreateObject("WindowsInstaller.Installer") : CheckError
  77. ' Open database
  78. Dim database, openMode, view, record, updateMode, sumInfo, sequence, lastSequence
  79. If updateMsi Or sequenceFile Or removeCab Then openMode = msiOpenDatabaseModeTransact Else openMode = msiOpenDatabaseModeReadOnly
  80. Set database = installer.OpenDatabase(databasePath, openMode) : CheckError
  81. ' Remove existing cabinet(s) and revert to source tree install if options specified
  82. If removeCab Then
  83. Set view = database.OpenView("SELECT DiskId, LastSequence, Cabinet FROM Media ORDER BY DiskId") : CheckError
  84. view.Execute : CheckError
  85. updateMode = msiViewModifyUpdate
  86. Set record = view.Fetch : CheckError
  87. If Not record Is Nothing Then ' Media table not empty
  88. If Not record.IsNull(3) Then
  89. If record.StringData(3) <> cabName Then Wscript.Echo "Warning, cabinet name in media table, " & record.StringData(3) & " does not match " & cabName
  90. record.StringData(3) = Empty
  91. End If
  92. record.IntegerData(2) = 9999 ' in case of multiple cabinets, force all files from 1st media
  93. view.Modify msiViewModifyUpdate, record : CheckError
  94. Do
  95. Set record = view.Fetch : CheckError
  96. If record Is Nothing Then Exit Do
  97. view.Modify msiViewModifyDelete, record : CheckError 'remove other cabinet records
  98. Loop
  99. End If
  100. Set sumInfo = database.SummaryInformation(3) : CheckError
  101. sumInfo.Property(11) = Now
  102. sumInfo.Property(13) = Now
  103. sumInfo.Property(15) = sumInfo.Property(15) And Not 2
  104. sumInfo.Persist
  105. Set view = database.OpenView("SELECT `Name`,`Data` FROM _Streams WHERE `Name`= '" & cabFile & "'") : CheckError
  106. view.Execute : CheckError
  107. Set record = view.Fetch
  108. If record Is Nothing Then
  109. Wscript.Echo "Warning, cabinet stream not found in package: " & cabFile
  110. Else
  111. view.Modify msiViewModifyDelete, record : CheckError
  112. End If
  113. Set sumInfo = Nothing ' must release stream
  114. database.Commit : CheckError
  115. If Not updateMsi Then Wscript.Quit 0
  116. End If
  117. ' Create an install session and execute actions in order to perform directory resolution
  118. installer.UILevel = msiUILevelNone
  119. Dim session : Set session = installer.OpenPackage(database) : If Err <> 0 Then Fail "Database: " & databasePath & ". Invalid installer package format"
  120. Dim shortNames : shortNames = session.Mode(msiRunModeSourceShortNames) : CheckError
  121. If Not IsEmpty(sourceFolder) Then session.Property("OriginalDatabase") = sourceFolder : CheckError
  122. Dim stat : stat = session.DoAction("CostInitialize") : CheckError
  123. If stat <> 1 Then Fail "CostInitialize failed, returned " & stat
  124. ' Check for non-cabinet files to avoid sequence number collisions
  125. lastSequence = 0
  126. If sequenceFile Then
  127. Set view = database.OpenView("SELECT Sequence,Attributes FROM File") : CheckError
  128. view.Execute : CheckError
  129. Do
  130. Set record = view.Fetch : CheckError
  131. If record Is Nothing Then Exit Do
  132. sequence = record.IntegerData(1)
  133. If (record.IntegerData(2) And msidbFileAttributesNoncompressed) <> 0 And sequence > lastSequence Then lastSequence = sequence
  134. Loop
  135. End If
  136. ' Join File table to Component table in order to find directories
  137. Dim orderBy : If sequenceFile Then orderBy = "Directory_" Else orderBy = "Sequence"
  138. Set view = database.OpenView("SELECT File,FileName,Directory_,Sequence,File.Attributes FROM File,Component WHERE Component_=Component ORDER BY " & orderBy) : CheckError
  139. view.Execute : CheckError
  140. ' Create DDF file and write header properties
  141. Dim FileSys : Set FileSys = CreateObject("Scripting.FileSystemObject") : CheckError
  142. Dim outStream : Set outStream = FileSys.CreateTextFile(baseName & ".DDF", OverwriteIfExist, OpenAsASCII) : CheckError
  143. outStream.WriteLine "; Generated from " & databasePath & " on " & Now
  144. outStream.WriteLine ".Set CabinetNameTemplate=" & baseName & "*.cab"
  145. outStream.WriteLine ".Set CabinetName1=" & cabFile
  146. outStream.WriteLine ".Set ReservePerCabinetSize=8"
  147. outStream.WriteLine ".Set MaxDiskSize=" & cabSize
  148. outStream.WriteLine ".Set CompressionType=" & compressType
  149. outStream.WriteLine ".Set InfFileLineFormat=(*disk#*) *file#*: *file* = *Size*"
  150. outStream.WriteLine ".Set InfFileName=" & baseName & ".INF"
  151. outStream.WriteLine ".Set RptFileName=" & baseName & ".RPT"
  152. outStream.WriteLine ".Set InfHeader="
  153. outStream.WriteLine ".Set InfFooter="
  154. outStream.WriteLine ".Set DiskDirectoryTemplate=."
  155. outStream.WriteLine ".Set Compress=ON"
  156. outStream.WriteLine ".Set Cabinet=ON"
  157. ' Fetch each file and request the source path, then verify the source path
  158. Dim fileKey, fileName, folder, sourcePath, delim, message, attributes
  159. Do
  160. Set record = view.Fetch : CheckError
  161. If record Is Nothing Then Exit Do
  162. fileKey = record.StringData(1)
  163. fileName = record.StringData(2)
  164. folder = record.StringData(3)
  165. sequence = record.IntegerData(4)
  166. attributes = record.IntegerData(5)
  167. If (attributes And msidbFileAttributesNoncompressed) = 0 Then
  168. If sequence <= lastSequence Then
  169. If Not sequenceFile Then Fail "Duplicate sequence numbers in File table, use /S option"
  170. sequence = lastSequence + 1
  171. record.IntegerData(4) = sequence
  172. view.Modify msiViewModifyUpdate, record
  173. End If
  174. lastSequence = sequence
  175. delim = InStr(1, fileName, "|", vbTextCompare)
  176. If delim <> 0 Then
  177. If shortNames Then fileName = Left(fileName, delim-1) Else fileName = Right(fileName, Len(fileName) - delim)
  178. End If
  179. REM sourcePath = session.SourcePath(folder) & fileName
  180. sourcePath = sourceFolder & fileKey
  181. outStream.WriteLine sourcePath & " " & fileKey
  182. If installer.FileAttributes(sourcePath) = -1 Then message = message & vbNewLine & sourcePath
  183. End If
  184. Loop
  185. outStream.Close
  186. REM Wscript.Echo "SourceDir = " & session.Property("SourceDir")
  187. If Not IsEmpty(message) Then Fail "The following files were not available:" & message
  188. ' Generate compressed file cabinet
  189. If makeCab Then
  190. Dim WshShell : Set WshShell = Wscript.CreateObject("Wscript.Shell") : CheckError
  191. Dim cabStat : cabStat = WshShell.Run("MakeCab.exe /f " & baseName & ".DDF", 7, True) : CheckError
  192. If cabStat <> 0 Then Fail "MAKECAB.EXE failed, possibly could not find source files, or invalid DDF format"
  193. End If
  194. ' Update Media table and SummaryInformation if requested
  195. If updateMsi Then
  196. Set view = database.OpenView("SELECT DiskId, LastSequence, Cabinet FROM Media ORDER BY DiskId") : CheckError
  197. view.Execute : CheckError
  198. updateMode = msiViewModifyUpdate
  199. Set record = view.Fetch : CheckError
  200. If record Is Nothing Then ' Media table empty
  201. Set record = Installer.CreateRecord(3)
  202. record.IntegerData(1) = 1
  203. updateMode = msiViewModifyInsert
  204. End If
  205. record.IntegerData(2) = lastSequence
  206. record.StringData(3) = cabName
  207. view.Modify updateMode, record
  208. Set sumInfo = database.SummaryInformation(3) : CheckError
  209. sumInfo.Property(11) = Now
  210. sumInfo.Property(13) = Now
  211. sumInfo.Property(15) = (shortNames And 1) + 2
  212. sumInfo.Persist
  213. End If
  214. ' Embed cabinet if requested
  215. If embedCab Then
  216. Set view = database.OpenView("SELECT `Name`,`Data` FROM _Streams") : CheckError
  217. view.Execute : CheckError
  218. Set record = Installer.CreateRecord(2)
  219. record.StringData(1) = cabFile
  220. record.SetStream 2, cabFile : CheckError
  221. view.Modify msiViewModifyAssign, record : CheckError 'replace any existing stream of that name
  222. End If
  223. ' Commit database in case updates performed
  224. database.Commit : CheckError
  225. Wscript.Quit 0
  226. ' Extract argument value from command line, processing any option flags
  227. Function NextArgument
  228. Dim arg
  229. Do ' loop to pull in option flags until an argument value is found
  230. If iArg >= argCount Then Exit Function
  231. arg = Wscript.Arguments(iArg)
  232. iArg = iArg + 1
  233. If (AscW(arg) <> AscW("/")) And (AscW(arg) <> AscW("-")) Then Exit Do
  234. Select Case UCase(Right(arg, Len(arg)-1))
  235. Case "C" : makeCab = True
  236. Case "E" : embedCab = True
  237. Case "F" : cabSize = "1.44M"
  238. Case "L" : compressType = "LZX"
  239. Case "R" : removeCab = True
  240. Case "S" : sequenceFile = True
  241. Case "U" : updateMsi = True
  242. Case Else: Wscript.Echo "Invalid option flag:", arg : Wscript.Quit 1
  243. End Select
  244. Loop
  245. NextArgument = arg
  246. End Function
  247. Sub CheckError
  248. Dim message, errRec
  249. If Err = 0 Then Exit Sub
  250. message = Err.Source & " " & Hex(Err) & ": " & Err.Description
  251. If Not installer Is Nothing Then
  252. Set errRec = installer.LastErrorRecord
  253. If Not errRec Is Nothing Then message = message & vbNewLine & errRec.FormatText
  254. End If
  255. Fail message
  256. End Sub
  257. Sub Fail(message)
  258. Wscript.Echo message
  259. Wscript.Quit 2
  260. End Sub