PageRenderTime 62ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 1ms

/src/wix/Binder.cs

http://wix.codeplex.com
C# | 7382 lines | 6326 code | 502 blank | 554 comment | 760 complexity | 94b8d58ea811c85ed07eeab508bb81a8 MD5 | raw file
Possible License(s): CPL-1.0

Large files files are truncated, but you can click here to view the full file

  1. //-------------------------------------------------------------------------------------------------
  2. // <copyright file="Binder.cs" company="Outercurve Foundation">
  3. // Copyright (c) 2004, Outercurve Foundation.
  4. // This software is released under Microsoft Reciprocal License (MS-RL).
  5. // The license and further copyright text can be found in the file
  6. // LICENSE.TXT at the root directory of the distribution.
  7. // </copyright>
  8. //
  9. // <summary>
  10. // Binder core of the WiX toolset.
  11. // </summary>
  12. //-------------------------------------------------------------------------------------------------
  13. namespace WixToolset
  14. {
  15. using System;
  16. using System.Collections;
  17. using System.Collections.Generic;
  18. using System.Collections.ObjectModel;
  19. using System.Collections.Specialized;
  20. using System.ComponentModel;
  21. using System.Diagnostics;
  22. using System.Diagnostics.CodeAnalysis;
  23. using System.Globalization;
  24. using System.IO;
  25. using System.Reflection;
  26. using System.Runtime.InteropServices;
  27. using System.Security.Cryptography;
  28. using System.Security.Cryptography.X509Certificates;
  29. using System.Text;
  30. using System.Xml;
  31. using System.Xml.XPath;
  32. using WixToolset.Cab;
  33. using WixToolset.CLR.Interop;
  34. using WixToolset.MergeMod;
  35. using WixToolset.Msi;
  36. using WixToolset.Msi.Interop;
  37. using Wix = WixToolset.Serialize;
  38. // TODO: (4.0) Refactor so that these don't need to be copied.
  39. // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs
  40. [Flags]
  41. internal enum WixFileSearchAttributes
  42. {
  43. Default = 0x001,
  44. MinVersionInclusive = 0x002,
  45. MaxVersionInclusive = 0x004,
  46. MinSizeInclusive = 0x008,
  47. MaxSizeInclusive = 0x010,
  48. MinDateInclusive = 0x020,
  49. MaxDateInclusive = 0x040,
  50. WantVersion = 0x080,
  51. WantExists = 0x100,
  52. IsDirectory = 0x200,
  53. }
  54. [Flags]
  55. internal enum WixRegistrySearchAttributes
  56. {
  57. Raw = 0x01,
  58. Compatible = 0x02,
  59. ExpandEnvironmentVariables = 0x04,
  60. WantValue = 0x08,
  61. WantExists = 0x10,
  62. Win64 = 0x20,
  63. }
  64. internal enum WixComponentSearchAttributes
  65. {
  66. KeyPath = 0x1,
  67. State = 0x2,
  68. WantDirectory = 0x4,
  69. }
  70. [Flags]
  71. internal enum WixProductSearchAttributes
  72. {
  73. Version = 0x1,
  74. Language = 0x2,
  75. State = 0x4,
  76. Assignment = 0x8,
  77. }
  78. /// <summary>
  79. /// Binder core of the WiX toolset.
  80. /// </summary>
  81. public sealed class Binder : WixBinder, IDisposable
  82. {
  83. // as outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs
  84. private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
  85. // The following constants must stay in sync with src\burn\engine\core.h
  86. private const string BURN_BUNDLE_NAME = "WixBundleName";
  87. private const string BURN_BUNDLE_ORIGINAL_SOURCE = "WixBundleOriginalSource";
  88. private const string BURN_BUNDLE_LAST_USED_SOURCE = "WixBundleLastUsedSource";
  89. private string emptyFile;
  90. private bool backwardsCompatibleGuidGen;
  91. private int cabbingThreadCount;
  92. private string cabCachePath;
  93. private Cab.CompressionLevel defaultCompressionLevel;
  94. private bool exactAssemblyVersions;
  95. private bool setMsiAssemblyNameFileVersion;
  96. private string pdbFile;
  97. private bool reuseCabinets;
  98. private bool suppressAssemblies;
  99. private bool suppressAclReset;
  100. private bool suppressBuildInfo;
  101. private bool suppressFileHashAndInfo;
  102. private StringCollection suppressICEs;
  103. private bool suppressLayout;
  104. private bool suppressWixPdb;
  105. private bool suppressValidation;
  106. private StringCollection ices;
  107. private StringCollection invalidArgs;
  108. private bool suppressAddingValidationRows;
  109. private Validator validator;
  110. private string contentsFile;
  111. private string outputsFile;
  112. private string builtOutputsFile;
  113. private string wixprojectFile;
  114. /// <summary>
  115. /// Creates an MSI binder.
  116. /// </summary>
  117. public Binder()
  118. {
  119. this.defaultCompressionLevel = Cab.CompressionLevel.Mszip;
  120. this.suppressICEs = new StringCollection();
  121. this.ices = new StringCollection();
  122. this.invalidArgs = new StringCollection();
  123. this.validator = new Validator();
  124. }
  125. /// <summary>
  126. /// Gets or sets whether the GUID generation should use a backwards
  127. /// compatible version (i.e. MD5).
  128. /// </summary>
  129. public bool BackwardsCompatibleGuidGen
  130. {
  131. get { return this.backwardsCompatibleGuidGen; }
  132. set { this.backwardsCompatibleGuidGen = value; }
  133. }
  134. /// <summary>
  135. /// Gets or sets the number of threads to use for cabinet creation.
  136. /// </summary>
  137. /// <value>The number of threads to use for cabinet creation.</value>
  138. public int CabbingThreadCount
  139. {
  140. get { return this.cabbingThreadCount; }
  141. set { this.cabbingThreadCount = value; }
  142. }
  143. /// <summary>
  144. /// Gets or sets the default compression level to use for cabinets
  145. /// that don't have their compression level explicitly set.
  146. /// </summary>
  147. public Cab.CompressionLevel DefaultCompressionLevel
  148. {
  149. get { return this.defaultCompressionLevel; }
  150. set { this.defaultCompressionLevel = value; }
  151. }
  152. /// <summary>
  153. /// Gets or sets the exact assembly versions flag (see docs).
  154. /// </summary>
  155. public bool ExactAssemblyVersions
  156. {
  157. get { return this.exactAssemblyVersions; }
  158. set { this.exactAssemblyVersions = value; }
  159. }
  160. /// <summary>
  161. /// Gets and sets the location to save the WixPdb.
  162. /// </summary>
  163. /// <value>The location in which to save the WixPdb. Null if the the WixPdb should not be output.</value>
  164. public string PdbFile
  165. {
  166. get { return this.pdbFile; }
  167. set { this.pdbFile = value; }
  168. }
  169. /// <summary>
  170. /// Gets and sets the option to set the file version in the MsiAssemblyName table.
  171. /// </summary>
  172. /// <value>The option to set the file version in the MsiAssemblyName table.</value>
  173. public bool SetMsiAssemblyNameFileVersion
  174. {
  175. get { return this.setMsiAssemblyNameFileVersion; }
  176. set { this.setMsiAssemblyNameFileVersion = value; }
  177. }
  178. /// <summary>
  179. /// Gets and sets the option to suppress resetting ACLs by the binder.
  180. /// </summary>
  181. /// <value>The option to suppress resetting ACLs by the binder.</value>
  182. public bool SuppressAclReset
  183. {
  184. get { return this.suppressAclReset; }
  185. set { this.suppressAclReset = value; }
  186. }
  187. /// <summary>
  188. /// Gets and sets the option to suppress adding _Validation rows.
  189. /// </summary>
  190. /// <value>The option to suppress adding _Validation rows.</value>
  191. public bool SuppressAddingValidationRows
  192. {
  193. get { return this.suppressAddingValidationRows; }
  194. set { this.suppressAddingValidationRows = value; }
  195. }
  196. /// <summary>
  197. /// Gets and sets the option to suppress grabbing assembly name information from assemblies.
  198. /// </summary>
  199. /// <value>The option to suppress grabbing assembly name information from assemblies.</value>
  200. public bool SuppressAssemblies
  201. {
  202. get { return this.suppressAssemblies; }
  203. set { this.suppressAssemblies = value; }
  204. }
  205. /// <summary>
  206. /// Gets and sets the option to suppress writing build information in the output.
  207. /// </summary>
  208. /// <value>The option to suppress writing build information in the output.</value>
  209. public bool SuppressBuildInfo
  210. {
  211. get { return this.suppressBuildInfo; }
  212. set { this.suppressBuildInfo = value; }
  213. }
  214. /// <summary>
  215. /// Gets and sets the option to suppress grabbing the file hash, version and language at link time.
  216. /// </summary>
  217. /// <value>The option to suppress grabbing the file hash, version and language.</value>
  218. public bool SuppressFileHashAndInfo
  219. {
  220. get { return this.suppressFileHashAndInfo; }
  221. set { this.suppressFileHashAndInfo = value; }
  222. }
  223. /// <summary>
  224. /// Gets and sets the option to suppress creating an image for MSI/MSM.
  225. /// </summary>
  226. /// <value>The option to suppress creating an image for MSI/MSM.</value>
  227. public bool SuppressLayout
  228. {
  229. get { return this.suppressLayout; }
  230. set { this.suppressLayout = value; }
  231. }
  232. /// <summary>
  233. /// Gets and sets the option to suppress MSI/MSM validation.
  234. /// </summary>
  235. /// <value>The option to suppress MSI/MSM validation.</value>
  236. /// <remarks>This must be set before calling Bind.</remarks>
  237. public bool SuppressValidation
  238. {
  239. get
  240. {
  241. return this.suppressValidation;
  242. }
  243. set
  244. {
  245. // make a new validator if validation has been turned off and is now being turned back on
  246. if (!value && null == this.validator)
  247. {
  248. this.validator = new Validator();
  249. }
  250. this.suppressValidation = value;
  251. }
  252. }
  253. /// <summary>
  254. /// Gets help for all the command line arguments for this binder.
  255. /// </summary>
  256. /// <returns>A string to be added to light's help string.</returns>
  257. public override string GetCommandLineArgumentsHelpString()
  258. {
  259. return WixStrings.BinderArguments;
  260. }
  261. /// <summary>
  262. /// Parse the commandline arguments.
  263. /// </summary>
  264. /// <param name="args">Commandline arguments.</param>
  265. /// <param name="consoleMessageHandler">The console message handler.</param>
  266. [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "These strings are not round tripped, and have no security impact")]
  267. public override StringCollection ParseCommandLine(string[] args, ConsoleMessageHandler consoleMessageHandler)
  268. {
  269. for (int i = 0; i < args.Length; ++i)
  270. {
  271. string arg = args[i];
  272. if (null == arg || 0 == arg.Length) // skip blank arguments
  273. {
  274. continue;
  275. }
  276. if ('-' == arg[0] || '/' == arg[0])
  277. {
  278. string parameter = arg.Substring(1);
  279. if (parameter.Equals("bcgg", StringComparison.Ordinal))
  280. {
  281. this.backwardsCompatibleGuidGen = true;
  282. }
  283. else if (parameter.Equals("cc", StringComparison.Ordinal))
  284. {
  285. this.cabCachePath = CommandLine.GetDirectory(parameter, consoleMessageHandler, args, ++i);
  286. if (String.IsNullOrEmpty(this.cabCachePath))
  287. {
  288. return this.invalidArgs;
  289. }
  290. }
  291. else if (parameter.Equals("ct", StringComparison.Ordinal))
  292. {
  293. if (!CommandLine.IsValidArg(args, ++i))
  294. {
  295. consoleMessageHandler.Display(this, WixErrors.IllegalCabbingThreadCount(String.Empty));
  296. return this.invalidArgs;
  297. }
  298. try
  299. {
  300. this.cabbingThreadCount = Convert.ToInt32(args[i], CultureInfo.InvariantCulture.NumberFormat);
  301. if (0 >= this.cabbingThreadCount)
  302. {
  303. consoleMessageHandler.Display(this, WixErrors.IllegalCabbingThreadCount(args[i]));
  304. }
  305. consoleMessageHandler.Display(this, WixVerboses.SetCabbingThreadCount(this.cabbingThreadCount.ToString()));
  306. }
  307. catch (FormatException)
  308. {
  309. consoleMessageHandler.Display(this, WixErrors.IllegalCabbingThreadCount(args[i]));
  310. }
  311. catch (OverflowException)
  312. {
  313. consoleMessageHandler.Display(this, WixErrors.IllegalCabbingThreadCount(args[i]));
  314. }
  315. }
  316. else if (parameter.Equals("cub", StringComparison.Ordinal))
  317. {
  318. string cubeFile = CommandLine.GetFile(parameter, consoleMessageHandler, args, ++i);
  319. if (String.IsNullOrEmpty(cubeFile))
  320. {
  321. return this.invalidArgs;
  322. }
  323. this.validator.AddCubeFile(cubeFile);
  324. }
  325. else if (parameter.StartsWith("dcl:", StringComparison.Ordinal))
  326. {
  327. string defaultCompressionLevel = arg.Substring(5);
  328. if (String.IsNullOrEmpty(defaultCompressionLevel))
  329. {
  330. return this.invalidArgs;
  331. }
  332. this.defaultCompressionLevel = WixCreateCab.CompressionLevelFromString(defaultCompressionLevel);
  333. }
  334. else if (parameter.Equals("eav", StringComparison.Ordinal))
  335. {
  336. this.exactAssemblyVersions = true;
  337. }
  338. else if (parameter.Equals("fv", StringComparison.Ordinal))
  339. {
  340. this.setMsiAssemblyNameFileVersion = true;
  341. }
  342. else if (parameter.StartsWith("ice:", StringComparison.Ordinal))
  343. {
  344. this.ices.Add(parameter.Substring(4));
  345. }
  346. else if (parameter.Equals("contentsfile", StringComparison.Ordinal))
  347. {
  348. this.contentsFile = CommandLine.GetFile(parameter, consoleMessageHandler, args, ++i);
  349. if (String.IsNullOrEmpty(this.contentsFile))
  350. {
  351. return this.invalidArgs;
  352. }
  353. }
  354. else if (parameter.Equals("outputsfile", StringComparison.Ordinal))
  355. {
  356. this.outputsFile = CommandLine.GetFile(parameter, consoleMessageHandler, args, ++i);
  357. if (String.IsNullOrEmpty(this.outputsFile))
  358. {
  359. return this.invalidArgs;
  360. }
  361. }
  362. else if (parameter.Equals("builtoutputsfile", StringComparison.Ordinal))
  363. {
  364. this.builtOutputsFile = CommandLine.GetFile(parameter, consoleMessageHandler, args, ++i);
  365. if (String.IsNullOrEmpty(this.builtOutputsFile))
  366. {
  367. return this.invalidArgs;
  368. }
  369. }
  370. else if (parameter.Equals("wixprojectfile", StringComparison.Ordinal))
  371. {
  372. this.wixprojectFile = CommandLine.GetFile(parameter, consoleMessageHandler, args, ++i);
  373. if (String.IsNullOrEmpty(this.wixprojectFile))
  374. {
  375. return this.invalidArgs;
  376. }
  377. }
  378. else if (parameter.Equals("O1", StringComparison.Ordinal))
  379. {
  380. consoleMessageHandler.Display(this, WixWarnings.DeprecatedCommandLineSwitch("O1"));
  381. }
  382. else if (parameter.Equals("O2", StringComparison.Ordinal))
  383. {
  384. consoleMessageHandler.Display(this, WixWarnings.DeprecatedCommandLineSwitch("O2"));
  385. }
  386. else if (parameter.Equals("pdbout", StringComparison.Ordinal))
  387. {
  388. this.pdbFile = CommandLine.GetFile(parameter, consoleMessageHandler, args, ++i);
  389. if (String.IsNullOrEmpty(this.pdbFile))
  390. {
  391. return this.invalidArgs;
  392. }
  393. }
  394. else if (parameter.Equals("reusecab", StringComparison.Ordinal))
  395. {
  396. this.reuseCabinets = true;
  397. }
  398. else if (parameter.Equals("sa", StringComparison.Ordinal))
  399. {
  400. this.suppressAssemblies = true;
  401. }
  402. else if (parameter.Equals("sacl", StringComparison.Ordinal))
  403. {
  404. this.suppressAclReset = true;
  405. }
  406. else if (parameter.Equals("sbuildinfo", StringComparison.Ordinal))
  407. {
  408. this.suppressBuildInfo = true;
  409. }
  410. else if (parameter.Equals("sf", StringComparison.Ordinal))
  411. {
  412. this.suppressAssemblies = true;
  413. this.suppressFileHashAndInfo = true;
  414. }
  415. else if (parameter.Equals("sh", StringComparison.Ordinal))
  416. {
  417. this.suppressFileHashAndInfo = true;
  418. }
  419. else if (parameter.StartsWith("sice:", StringComparison.Ordinal))
  420. {
  421. this.suppressICEs.Add(parameter.Substring(5));
  422. }
  423. else if (parameter.Equals("sl", StringComparison.Ordinal))
  424. {
  425. this.suppressLayout = true;
  426. }
  427. else if (parameter.Equals("spdb", StringComparison.Ordinal))
  428. {
  429. this.suppressWixPdb = true;
  430. }
  431. else if (parameter.Equals("sval", StringComparison.Ordinal))
  432. {
  433. this.suppressValidation = true;
  434. }
  435. else
  436. {
  437. this.invalidArgs.Add(arg);
  438. }
  439. }
  440. else
  441. {
  442. this.invalidArgs.Add(arg);
  443. }
  444. }
  445. this.pdbFile = this.suppressWixPdb ? null : this.pdbFile;
  446. return this.invalidArgs;
  447. }
  448. /// <summary>
  449. /// Do any setting up needed after all command line parsing.
  450. /// </summary>
  451. public override void PostParseCommandLine()
  452. {
  453. if (!this.suppressWixPdb && null == this.pdbFile && null != this.OutputFile)
  454. {
  455. this.pdbFile = Path.ChangeExtension(this.OutputFile, ".wixpdb");
  456. }
  457. }
  458. /// <summary>
  459. /// Adds an event handler.
  460. /// </summary>
  461. /// <param name="newHandler">The event handler to add.</param>
  462. public override void AddMessageEventHandler(MessageEventHandler newHandler)
  463. {
  464. base.AddMessageEventHandler(newHandler);
  465. validator.Extension.Message += newHandler;
  466. }
  467. /// <summary>
  468. /// Binds an output.
  469. /// </summary>
  470. /// <param name="output">The output to bind.</param>
  471. /// <param name="file">The Windows Installer file to create.</param>
  472. /// <remarks>The Binder.DeleteTempFiles method should be called after calling this method.</remarks>
  473. /// <returns>true if binding completed successfully; false otherwise</returns>
  474. public override bool Bind(Output output, string file)
  475. {
  476. // ensure the cabinet cache path exists if we are going to use it
  477. if (null != this.cabCachePath && !Directory.Exists(this.cabCachePath))
  478. {
  479. Directory.CreateDirectory(this.cabCachePath);
  480. }
  481. // tell the binder about the validator if validation isn't suppressed
  482. if (!this.suppressValidation && (OutputType.Module == output.Type || OutputType.Product == output.Type))
  483. {
  484. if (String.IsNullOrEmpty(this.validator.TempFilesLocation))
  485. {
  486. this.validator.TempFilesLocation = Environment.GetEnvironmentVariable("WIX_TEMP");
  487. }
  488. // set the default cube file
  489. Assembly lightAssembly = Assembly.GetExecutingAssembly();
  490. string lightDirectory = Path.GetDirectoryName(lightAssembly.Location);
  491. if (OutputType.Module == output.Type)
  492. {
  493. this.validator.AddCubeFile(Path.Combine(lightDirectory, "mergemod.cub"));
  494. }
  495. else // product
  496. {
  497. this.validator.AddCubeFile(Path.Combine(lightDirectory, "darice.cub"));
  498. }
  499. // by default, disable ICEs that have equivalent-or-better checks in WiX
  500. this.suppressICEs.Add("ICE08");
  501. this.suppressICEs.Add("ICE33");
  502. this.suppressICEs.Add("ICE47");
  503. this.suppressICEs.Add("ICE66");
  504. // set the ICEs
  505. string[] iceArray = new string[this.ices.Count];
  506. this.ices.CopyTo(iceArray, 0);
  507. this.validator.ICEs = iceArray;
  508. // set the suppressed ICEs
  509. string[] suppressICEArray = new string[this.suppressICEs.Count];
  510. this.suppressICEs.CopyTo(suppressICEArray, 0);
  511. this.validator.SuppressedICEs = suppressICEArray;
  512. }
  513. else
  514. {
  515. this.validator = null;
  516. }
  517. this.core = new BinderCore(this.MessageHandler);
  518. this.FileManager.MessageHandler = this.core;
  519. foreach (BinderExtension extension in this.extensions)
  520. {
  521. extension.Core = this.core;
  522. }
  523. if (null == output)
  524. {
  525. throw new ArgumentNullException("output");
  526. }
  527. this.core.EncounteredError = false;
  528. switch (output.Type)
  529. {
  530. case OutputType.Bundle:
  531. return this.BindBundle(output, file);
  532. case OutputType.Transform:
  533. return this.BindTransform(output, file);
  534. default:
  535. return this.BindDatabase(output, file);
  536. }
  537. }
  538. /// <summary>
  539. /// Does any housekeeping after Bind.
  540. /// </summary>
  541. /// <param name="tidy">Whether or not any actual tidying should be done.</param>
  542. public override void Cleanup(bool tidy)
  543. {
  544. // If Bind hasn't been called yet, core will be null. There will be
  545. // nothing to cleanup.
  546. if (this.core == null)
  547. {
  548. return;
  549. }
  550. if (tidy)
  551. {
  552. if (!this.DeleteTempFiles())
  553. {
  554. this.core.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation));
  555. }
  556. }
  557. else
  558. {
  559. this.core.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation));
  560. }
  561. if (null != this.validator && !String.IsNullOrEmpty(this.validator.TempFilesLocation))
  562. {
  563. if (tidy)
  564. {
  565. if (!this.validator.DeleteTempFiles())
  566. {
  567. this.core.OnMessage(WixWarnings.FailedToDeleteTempDir(this.validator.TempFilesLocation));
  568. }
  569. }
  570. else
  571. {
  572. this.core.OnMessage(WixVerboses.ValidatorTempDirLocatedAt(this.validator.TempFilesLocation));
  573. }
  574. }
  575. }
  576. /// <summary>
  577. /// Cleans up the temp files used by the Binder.
  578. /// </summary>
  579. /// <returns>True if all files were deleted, false otherwise.</returns>
  580. public override bool DeleteTempFiles()
  581. {
  582. bool deleted = base.DeleteTempFiles();
  583. if (deleted)
  584. {
  585. this.emptyFile = null;
  586. }
  587. return deleted;
  588. }
  589. /// <summary>
  590. /// Process a list of loaded extensions.
  591. /// </summary>
  592. /// <param name="loadedExtensionList">The list of loaded extensions.</param>
  593. public override void ProcessExtensions(WixExtension[] loadedExtensionList)
  594. {
  595. bool binderFileManagerLoaded = false;
  596. bool validatorExtensionLoaded = false;
  597. foreach (WixExtension wixExtension in loadedExtensionList)
  598. {
  599. if (null != wixExtension.BinderFileManager)
  600. {
  601. if (binderFileManagerLoaded)
  602. {
  603. core.OnMessage(WixErrors.CannotLoadBinderFileManager(wixExtension.BinderFileManager.GetType().ToString(), this.FileManager.ToString()));
  604. }
  605. this.FileManager = wixExtension.BinderFileManager;
  606. binderFileManagerLoaded = true;
  607. }
  608. ValidatorExtension validatorExtension = wixExtension.ValidatorExtension;
  609. if (null != validatorExtension)
  610. {
  611. if (validatorExtensionLoaded)
  612. {
  613. core.OnMessage(WixErrors.CannotLoadLinkerExtension(validatorExtension.GetType().ToString(), this.validator.Extension.ToString()));
  614. }
  615. this.validator.Extension = validatorExtension;
  616. validatorExtensionLoaded = true;
  617. }
  618. }
  619. this.FileManager.CabCachePath = this.cabCachePath;
  620. this.FileManager.ReuseCabinets = this.reuseCabinets;
  621. }
  622. /// <summary>
  623. /// Creates the MSI/MSM/PCP database.
  624. /// </summary>
  625. /// <param name="output">Output to create database for.</param>
  626. /// <param name="databaseFile">The database file to create.</param>
  627. /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
  628. /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
  629. internal void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
  630. {
  631. // add the _Validation rows
  632. if (!this.suppressAddingValidationRows)
  633. {
  634. Table validationTable = output.EnsureTable(this.core.TableDefinitions["_Validation"]);
  635. foreach (Table table in output.Tables)
  636. {
  637. if (!table.Definition.IsUnreal)
  638. {
  639. // add the validation rows for this table
  640. table.Definition.AddValidationRows(validationTable);
  641. }
  642. }
  643. }
  644. // set the base directory
  645. string baseDirectory = this.TempFilesLocation;
  646. if (useSubdirectory)
  647. {
  648. string filename = Path.GetFileNameWithoutExtension(databaseFile);
  649. baseDirectory = Path.Combine(baseDirectory, filename);
  650. // make sure the directory exists
  651. Directory.CreateDirectory(baseDirectory);
  652. }
  653. try
  654. {
  655. OpenDatabase type = OpenDatabase.CreateDirect;
  656. // set special flag for patch files
  657. if (OutputType.Patch == output.Type)
  658. {
  659. type |= OpenDatabase.OpenPatchFile;
  660. }
  661. // try to create the database
  662. using (Database db = new Database(databaseFile, type))
  663. {
  664. // localize the codepage if a value was specified by the localizer
  665. if (null != this.Localizer && -1 != this.Localizer.Codepage)
  666. {
  667. output.Codepage = this.Localizer.Codepage;
  668. }
  669. // if we're not using the default codepage, import a new one into our
  670. // database before we add any tables (or the tables would be added
  671. // with the wrong codepage)
  672. if (0 != output.Codepage)
  673. {
  674. this.SetDatabaseCodepage(db, output);
  675. }
  676. foreach (Table table in output.Tables)
  677. {
  678. Table importTable = table;
  679. bool hasBinaryColumn = false;
  680. // skip all unreal tables other than _Streams
  681. if (table.Definition.IsUnreal && "_Streams" != table.Name)
  682. {
  683. continue;
  684. }
  685. // Do not put the _Validation table in patches, it is not needed
  686. if (OutputType.Patch == output.Type && "_Validation" == table.Name)
  687. {
  688. continue;
  689. }
  690. // The only way to import binary data is to copy it to a local subdirectory first.
  691. // To avoid this extra copying and perf hit, import an empty table with the same
  692. // definition and later import the binary data from source using records.
  693. foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
  694. {
  695. if (ColumnType.Object == columnDefinition.Type)
  696. {
  697. importTable = new Table(table.Section, table.Definition);
  698. hasBinaryColumn = true;
  699. break;
  700. }
  701. }
  702. // create the table via IDT import
  703. if ("_Streams" != importTable.Name)
  704. {
  705. try
  706. {
  707. db.ImportTable(output.Codepage, this.core, importTable, baseDirectory, keepAddedColumns);
  708. }
  709. catch (WixInvalidIdtException)
  710. {
  711. // If ValidateRows finds anything it doesn't like, it throws
  712. importTable.ValidateRows();
  713. // Otherwise we rethrow the InvalidIdt
  714. throw;
  715. }
  716. }
  717. // insert the rows via SQL query if this table contains object fields
  718. if (hasBinaryColumn)
  719. {
  720. StringBuilder query = new StringBuilder("SELECT ");
  721. // build the query for the view
  722. bool firstColumn = true;
  723. foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
  724. {
  725. if (!firstColumn)
  726. {
  727. query.Append(",");
  728. }
  729. query.AppendFormat(" `{0}`", columnDefinition.Name);
  730. firstColumn = false;
  731. }
  732. query.AppendFormat(" FROM `{0}`", table.Name);
  733. using (View tableView = db.OpenExecuteView(query.ToString()))
  734. {
  735. // import each row containing a stream
  736. foreach (Row row in table.Rows)
  737. {
  738. using (Record record = new Record(table.Definition.Columns.Count))
  739. {
  740. StringBuilder streamName = new StringBuilder();
  741. // the _Streams table doesn't prepend the table name (or a period)
  742. if ("_Streams" != table.Name)
  743. {
  744. streamName.Append(table.Name);
  745. }
  746. for (int i = 0; i < table.Definition.Columns.Count; i++)
  747. {
  748. ColumnDefinition columnDefinition = table.Definition.Columns[i];
  749. switch (columnDefinition.Type)
  750. {
  751. case ColumnType.Localized:
  752. case ColumnType.Preserved:
  753. case ColumnType.String:
  754. if (columnDefinition.IsPrimaryKey)
  755. {
  756. if (0 < streamName.Length)
  757. {
  758. streamName.Append(".");
  759. }
  760. streamName.Append((string)row[i]);
  761. }
  762. record.SetString(i + 1, (string)row[i]);
  763. break;
  764. case ColumnType.Number:
  765. record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture));
  766. break;
  767. case ColumnType.Object:
  768. if (null != row[i])
  769. {
  770. try
  771. {
  772. record.SetStream(i + 1, (string)row[i]);
  773. }
  774. catch (Win32Exception e)
  775. {
  776. if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
  777. {
  778. throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i]));
  779. }
  780. else
  781. {
  782. throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
  783. }
  784. }
  785. }
  786. break;
  787. }
  788. }
  789. // stream names are created by concatenating the name of the table with the values
  790. // of the primary key (delimited by periods)
  791. // check for a stream name that is more than 62 characters long (the maximum allowed length)
  792. if (MsiInterop.MsiMaxStreamNameLength < streamName.Length)
  793. {
  794. this.core.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
  795. }
  796. else // add the row to the database
  797. {
  798. tableView.Modify(ModifyView.Assign, record);
  799. }
  800. }
  801. }
  802. }
  803. // Remove rows from the _Streams table for wixpdbs.
  804. if ("_Streams" == table.Name)
  805. {
  806. table.Rows.Clear();
  807. }
  808. }
  809. }
  810. // insert substorages (like transforms inside a patch)
  811. if (0 < output.SubStorages.Count)
  812. {
  813. using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
  814. {
  815. foreach (SubStorage subStorage in output.SubStorages)
  816. {
  817. string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst"));
  818. // bind the transform
  819. if (this.BindTransform(subStorage.Data, transformFile))
  820. {
  821. // add the storage
  822. using (Record record = new Record(2))
  823. {
  824. record.SetString(1, subStorage.Name);
  825. record.SetStream(2, transformFile);
  826. storagesView.Modify(ModifyView.Assign, record);
  827. }
  828. }
  829. }
  830. }
  831. }
  832. // we're good, commit the changes to the new MSI
  833. db.Commit();
  834. }
  835. }
  836. catch (IOException)
  837. {
  838. // TODO: this error message doesn't seem specific enough
  839. throw new WixFileNotFoundException(SourceLineNumberCollection.FromFileName(databaseFile), databaseFile);
  840. }
  841. }
  842. /// <summary>
  843. /// Get the source path of a directory.
  844. /// </summary>
  845. /// <param name="directories">All cached directories.</param>
  846. /// <param name="componentIdGenSeeds">Hash table of Component GUID generation seeds indexed by directory id.</param>
  847. /// <param name="directory">Directory identifier.</param>
  848. /// <param name="canonicalize">Canonicalize the path for standard directories.</param>
  849. /// <returns>Source path of a directory.</returns>
  850. [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Changing the way this string normalizes would result " +
  851. "in a change to the way autogenerated GUIDs are generated. Furthermore, there is no security hole here, as the strings won't need to " +
  852. "make a round trip")]
  853. private static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize)
  854. {
  855. if (!directories.Contains(directory))
  856. {
  857. throw new WixException(WixErrors.ExpectedDirectory(directory));
  858. }
  859. ResolvedDirectory resolvedDirectory = (ResolvedDirectory)directories[directory];
  860. if (null == resolvedDirectory.Path)
  861. {
  862. if (null != componentIdGenSeeds && componentIdGenSeeds.Contains(directory))
  863. {
  864. resolvedDirectory.Path = (string)componentIdGenSeeds[directory];
  865. }
  866. else if (canonicalize && Util.IsStandardDirectory(directory))
  867. {
  868. // when canonicalization is on, standard directories are treated equally
  869. resolvedDirectory.Path = directory;
  870. }
  871. else
  872. {
  873. string name = resolvedDirectory.Name;
  874. if (canonicalize && null != name)
  875. {
  876. name = name.ToLower(CultureInfo.InvariantCulture);
  877. }
  878. if (String.IsNullOrEmpty(resolvedDirectory.DirectoryParent))
  879. {
  880. resolvedDirectory.Path = name;
  881. }
  882. else
  883. {
  884. string parentPath = GetDirectoryPath(directories, componentIdGenSeeds, resolvedDirectory.DirectoryParent, canonicalize);
  885. if (null != resolvedDirectory.Name)
  886. {
  887. resolvedDirectory.Path = Path.Combine(parentPath, name);
  888. }
  889. else
  890. {
  891. resolvedDirectory.Path = parentPath;
  892. }
  893. }
  894. }
  895. }
  896. return resolvedDirectory.Path;
  897. }
  898. /// <summary>
  899. /// Gets the source path of a file.
  900. /// </summary>
  901. /// <param name="directories">All cached directories in <see cref="ResolvedDirectory"/>.</param>
  902. /// <param name="directoryId">Parent directory identifier.</param>
  903. /// <param name="fileName">File name (in long|source format).</param>
  904. /// <param name="compressed">Specifies the package is compressed.</param>
  905. /// <param name="useLongName">Specifies the package uses long file names.</param>
  906. /// <returns>Source path of file relative to package directory.</returns>
  907. internal static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName)
  908. {
  909. string fileSourcePath = Installer.GetName(fileName, true, useLongName);
  910. if (compressed)
  911. {
  912. // Use just the file name of the file since all uncompressed files must appear
  913. // in the root of the image in a compressed package.
  914. }
  915. else
  916. {
  917. // Get the relative path of where we want the file to be layed out as specified
  918. // in the Directory table.
  919. string directoryPath = Binder.GetDirectoryPath(directories, null, directoryId, false);
  920. fileSourcePath = Path.Combine(directoryPath, fileSourcePath);
  921. }
  922. // Strip off "SourceDir" if it's still on there.
  923. if (fileSourcePath.StartsWith("SourceDir\\", StringComparison.Ordinal))
  924. {
  925. fileSourcePath = fileSourcePath.Substring(10);
  926. }
  927. return fileSourcePath;
  928. }
  929. /// <summary>
  930. /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
  931. /// create a new row.
  932. /// </summary>
  933. /// <param name="output">The output to bind.</param>
  934. /// <param name="assemblyNameTable">MsiAssemblyName table.</param>
  935. /// <param name="fileRow">FileRow containing the assembly read for the MsiAssemblyName row.</param>
  936. /// <param name="name">MsiAssemblyName name.</param>
  937. /// <param name="value">MsiAssemblyName value.</param>
  938. /// <param name="infoCache">Cache to populate with file information (optional).</param>
  939. /// <param name="modularizationGuid">The modularization GUID (in the case of merge modules).</param>
  940. [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "This string is not round tripped, and not used for any security decisions")]
  941. private void SetMsiAssemblyName(Output output, Table assemblyNameTable, FileRow fileRow, string name, string value, IDictionary<string, string> infoCache, string modularizationGuid)
  942. {
  943. // check for null value (this can occur when grabbing the file version from an assembly without one)
  944. if (null == value || 0 == value.Length)
  945. {
  946. this.core.OnMessage(WixWarnings.NullMsiAssemblyNameValue(fileRow.SourceLineNumbers, fileRow.Component, name));
  947. }
  948. else
  949. {
  950. Row assemblyNameRow = null;
  951. // override directly authored value
  952. foreach (Row row in assemblyNameTable.Rows)
  953. {
  954. if ((string)row[0] == fileRow.Component && (string)row[1] == name)
  955. {
  956. assemblyNameRow = row;
  957. break;
  958. }
  959. }
  960. // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
  961. if ("name" == name && FileAssemblyType.DotNetAssembly == fileRow.AssemblyType && String.IsNullOrEmpty(fileRow.AssemblyApplication) && !String.Equals(Path.GetFileNameWithoutExtension(fileRow.LongFileName), value, StringComparison.OrdinalIgnoreCase))
  962. {
  963. this.core.OnMessage(WixErrors.GACAssemblyIdentityWarning(fileRow.SourceLineNumbers, Path.GetFileNameWithoutExtension(fileRow.LongFileName), value));
  964. }
  965. if (null == assemblyNameRow)
  966. {
  967. assemblyNameRow = assemblyNameTable.CreateRow(fileRow.SourceLineNumbers);
  968. assemblyNameRow[0] = fileRow.Component;
  969. assemblyNameRow[1] = name;
  970. assemblyNameRow[2] = value;
  971. // put the MsiAssemblyName row in the same section as the related File row
  972. assemblyNameRow.SectionId = fileRow.SectionId;
  973. if (null == fileRow.AssemblyNameRows)
  974. {
  975. fileRow.AssemblyNameRows = new RowCollection();
  976. }
  977. fileRow.AssemblyNameRows.Add(assemblyNameRow);
  978. }
  979. else
  980. {
  981. assemblyNameRow[2] = value;
  982. }
  983. if (infoCache != null)
  984. {
  985. string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, Demodularize(output, modularizationGuid, fileRow.File)).ToLower(CultureInfo.InvariantCulture);
  986. infoCache[key] = (string)assemblyNameRow[2];
  987. }
  988. }
  989. }
  990. /// <summary>
  991. /// Merge data from a row in the WixPatchSymbolsPaths table into an associated FileRow.
  992. /// </summary>
  993. /// <param name="row">Row from the WixPatchSymbolsPaths table.</param>
  994. /// <param name="fileRow">FileRow into which to set symbol information.</param>
  995. /// <comment>This includes PreviousData as well.</comment>
  996. private static void MergeSymbolPaths(Row row, FileRow fileRow)
  997. {
  998. if (null == fileRow.Symbols)
  999. {
  1000. fileRow.Symbols = (string)row[2];
  1001. }
  1002. else
  1003. {
  1004. fileRow.Symbols = String.Concat(fileRow.Symbols, ";", (string)row[2]);

Large files files are truncated, but you can click here to view the full file