diff options
| author | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
|---|---|---|
| committer | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
| commit | dbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch) | |
| tree | 0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core.Burn | |
| parent | fbf986eb97f68396797a89fc7d40dec07b775440 (diff) | |
| download | wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2 wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip | |
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core.Burn')
30 files changed, 5096 insertions, 0 deletions
diff --git a/src/WixToolset.Core.Burn/BackendFactory.cs b/src/WixToolset.Core.Burn/BackendFactory.cs new file mode 100644 index 00000000..042fa254 --- /dev/null +++ b/src/WixToolset.Core.Burn/BackendFactory.cs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | |||
| 9 | internal class BackendFactory : IBackendFactory | ||
| 10 | { | ||
| 11 | public bool TryCreateBackend(string outputType, string outputFile, IBindContext context, out IBackend backend) | ||
| 12 | { | ||
| 13 | if (String.IsNullOrEmpty(outputType)) | ||
| 14 | { | ||
| 15 | outputType = Path.GetExtension(outputFile); | ||
| 16 | } | ||
| 17 | |||
| 18 | switch (outputType.ToLowerInvariant()) | ||
| 19 | { | ||
| 20 | case "bundle": | ||
| 21 | case ".exe": | ||
| 22 | backend = new BundleBackend(); | ||
| 23 | return true; | ||
| 24 | } | ||
| 25 | |||
| 26 | backend = null; | ||
| 27 | return false; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs new file mode 100644 index 00000000..212b1e81 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs | |||
| @@ -0,0 +1,942 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Linq; | ||
| 11 | using System.Reflection; | ||
| 12 | using WixToolset.Bind; | ||
| 13 | using WixToolset.Core.Bind; | ||
| 14 | using WixToolset.Core.Burn.Bundles; | ||
| 15 | using WixToolset.Data; | ||
| 16 | using WixToolset.Data.Bind; | ||
| 17 | using WixToolset.Data.Rows; | ||
| 18 | using WixToolset.Extensibility; | ||
| 19 | |||
| 20 | // TODO: (4.0) Refactor so that these don't need to be copied. | ||
| 21 | // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs | ||
| 22 | [Flags] | ||
| 23 | internal enum WixFileSearchAttributes | ||
| 24 | { | ||
| 25 | Default = 0x001, | ||
| 26 | MinVersionInclusive = 0x002, | ||
| 27 | MaxVersionInclusive = 0x004, | ||
| 28 | MinSizeInclusive = 0x008, | ||
| 29 | MaxSizeInclusive = 0x010, | ||
| 30 | MinDateInclusive = 0x020, | ||
| 31 | MaxDateInclusive = 0x040, | ||
| 32 | WantVersion = 0x080, | ||
| 33 | WantExists = 0x100, | ||
| 34 | IsDirectory = 0x200, | ||
| 35 | } | ||
| 36 | |||
| 37 | [Flags] | ||
| 38 | internal enum WixRegistrySearchAttributes | ||
| 39 | { | ||
| 40 | Raw = 0x01, | ||
| 41 | Compatible = 0x02, | ||
| 42 | ExpandEnvironmentVariables = 0x04, | ||
| 43 | WantValue = 0x08, | ||
| 44 | WantExists = 0x10, | ||
| 45 | Win64 = 0x20, | ||
| 46 | } | ||
| 47 | |||
| 48 | internal enum WixComponentSearchAttributes | ||
| 49 | { | ||
| 50 | KeyPath = 0x1, | ||
| 51 | State = 0x2, | ||
| 52 | WantDirectory = 0x4, | ||
| 53 | } | ||
| 54 | |||
| 55 | [Flags] | ||
| 56 | internal enum WixProductSearchAttributes | ||
| 57 | { | ||
| 58 | Version = 0x1, | ||
| 59 | Language = 0x2, | ||
| 60 | State = 0x4, | ||
| 61 | Assignment = 0x8, | ||
| 62 | UpgradeCode = 0x10, | ||
| 63 | } | ||
| 64 | |||
| 65 | /// <summary> | ||
| 66 | /// Binds a this.bundle. | ||
| 67 | /// </summary> | ||
| 68 | internal class BindBundleCommand | ||
| 69 | { | ||
| 70 | public BindBundleCommand(IBindContext context) | ||
| 71 | { | ||
| 72 | this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); | ||
| 73 | |||
| 74 | this.DelayedFields = context.DelayedFields; | ||
| 75 | this.ExpectedEmbeddedFiles = context.ExpectedEmbeddedFiles; | ||
| 76 | |||
| 77 | this.BackendExtensions = context.ExtensionManager.Create<IBurnBackendExtension>(); | ||
| 78 | } | ||
| 79 | |||
| 80 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
| 81 | |||
| 82 | public IEnumerable<IDelayedField> DelayedFields { get; } | ||
| 83 | |||
| 84 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; } | ||
| 85 | |||
| 86 | private IEnumerable<IBurnBackendExtension> BackendExtensions { get; } | ||
| 87 | |||
| 88 | public IEnumerable<IBinderExtension> Extensions { private get; set; } | ||
| 89 | |||
| 90 | public Output Output { private get; set; } | ||
| 91 | |||
| 92 | public string OutputPath { private get; set; } | ||
| 93 | |||
| 94 | public string PdbFile { private get; set; } | ||
| 95 | |||
| 96 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 97 | |||
| 98 | public string IntermediateFolder { private get; set; } | ||
| 99 | |||
| 100 | public IBindVariableResolver WixVariableResolver { private get; set; } | ||
| 101 | |||
| 102 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 103 | |||
| 104 | public IEnumerable<string> ContentFilePaths { get; private set; } | ||
| 105 | |||
| 106 | public void Execute() | ||
| 107 | { | ||
| 108 | this.FileTransfers = Enumerable.Empty<FileTransfer>(); | ||
| 109 | this.ContentFilePaths = Enumerable.Empty<string>(); | ||
| 110 | |||
| 111 | // First look for data we expect to find... Chain, WixGroups, etc. | ||
| 112 | |||
| 113 | // We shouldn't really get past the linker phase if there are | ||
| 114 | // no group items... that means that there's no UX, no Chain, | ||
| 115 | // *and* no Containers! | ||
| 116 | Table chainPackageTable = this.GetRequiredTable("WixBundlePackage"); | ||
| 117 | |||
| 118 | Table wixGroupTable = this.GetRequiredTable("WixGroup"); | ||
| 119 | |||
| 120 | // Ensure there is one and only one row in the WixBundle table. | ||
| 121 | // The compiler and linker behavior should have colluded to get | ||
| 122 | // this behavior. | ||
| 123 | WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle"); | ||
| 124 | |||
| 125 | bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user. | ||
| 126 | |||
| 127 | // Ensure there is one and only one row in the WixBootstrapperApplication table. | ||
| 128 | // The compiler and linker behavior should have colluded to get | ||
| 129 | // this behavior. | ||
| 130 | Row baRow = this.GetSingleRowTable("WixBootstrapperApplication"); | ||
| 131 | |||
| 132 | // Ensure there is one and only one row in the WixChain table. | ||
| 133 | // The compiler and linker behavior should have colluded to get | ||
| 134 | // this behavior. | ||
| 135 | WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain"); | ||
| 136 | |||
| 137 | if (Messaging.Instance.EncounteredError) | ||
| 138 | { | ||
| 139 | return; | ||
| 140 | } | ||
| 141 | |||
| 142 | // If there are any fields to resolve later, create the cache to populate during bind. | ||
| 143 | IDictionary<string, string> variableCache = null; | ||
| 144 | if (this.DelayedFields.Any()) | ||
| 145 | { | ||
| 146 | variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); | ||
| 147 | } | ||
| 148 | |||
| 149 | // TODO: Although the WixSearch tables are defined in the Util extension, | ||
| 150 | // the Bundle Binder has to know all about them. We hope to revisit all | ||
| 151 | // of this in the 4.0 timeframe. | ||
| 152 | IEnumerable<WixSearchInfo> orderedSearches = this.OrderSearches(); | ||
| 153 | |||
| 154 | // Extract files that come from cabinet files (this does not extract files from merge modules). | ||
| 155 | { | ||
| 156 | var extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand(); | ||
| 157 | extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = ExpectedEmbeddedFiles; | ||
| 158 | extractEmbeddedFilesCommand.Execute(); | ||
| 159 | } | ||
| 160 | |||
| 161 | // Get the explicit payloads. | ||
| 162 | RowDictionary<WixBundlePayloadRow> payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]); | ||
| 163 | |||
| 164 | // Update explicitly authored payloads with their parent package and container (as appropriate) | ||
| 165 | // to make it easier to gather the payloads later. | ||
| 166 | foreach (WixGroupRow row in wixGroupTable.RowsAs<WixGroupRow>()) | ||
| 167 | { | ||
| 168 | if (ComplexReferenceChildType.Payload == row.ChildType) | ||
| 169 | { | ||
| 170 | WixBundlePayloadRow payload = payloads.Get(row.ChildId); | ||
| 171 | |||
| 172 | if (ComplexReferenceParentType.Package == row.ParentType) | ||
| 173 | { | ||
| 174 | Debug.Assert(String.IsNullOrEmpty(payload.Package)); | ||
| 175 | payload.Package = row.ParentId; | ||
| 176 | } | ||
| 177 | else if (ComplexReferenceParentType.Container == row.ParentType) | ||
| 178 | { | ||
| 179 | Debug.Assert(String.IsNullOrEmpty(payload.Container)); | ||
| 180 | payload.Container = row.ParentId; | ||
| 181 | } | ||
| 182 | else if (ComplexReferenceParentType.Layout == row.ParentType) | ||
| 183 | { | ||
| 184 | payload.LayoutOnly = true; | ||
| 185 | } | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 190 | string layoutDirectory = Path.GetDirectoryName(this.OutputPath); | ||
| 191 | |||
| 192 | // Process the explicitly authored payloads. | ||
| 193 | ISet<string> processedPayloads; | ||
| 194 | { | ||
| 195 | ProcessPayloadsCommand command = new ProcessPayloadsCommand(); | ||
| 196 | command.Payloads = payloads.Values; | ||
| 197 | command.DefaultPackaging = bundleRow.DefaultPackagingType; | ||
| 198 | command.LayoutDirectory = layoutDirectory; | ||
| 199 | command.Execute(); | ||
| 200 | |||
| 201 | fileTransfers.AddRange(command.FileTransfers); | ||
| 202 | |||
| 203 | processedPayloads = new HashSet<string>(payloads.Keys); | ||
| 204 | } | ||
| 205 | |||
| 206 | IDictionary<string, PackageFacade> facades; | ||
| 207 | { | ||
| 208 | GetPackageFacadesCommand command = new GetPackageFacadesCommand(); | ||
| 209 | command.PackageTable = chainPackageTable; | ||
| 210 | command.ExePackageTable = this.Output.Tables["WixBundleExePackage"]; | ||
| 211 | command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"]; | ||
| 212 | command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"]; | ||
| 213 | command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"]; | ||
| 214 | command.Execute(); | ||
| 215 | |||
| 216 | facades = command.PackageFacades; | ||
| 217 | } | ||
| 218 | |||
| 219 | // Process each package facade. Note this is likely to add payloads and other rows to tables so | ||
| 220 | // note that any indexes created above may be out of date now. | ||
| 221 | foreach (PackageFacade facade in facades.Values) | ||
| 222 | { | ||
| 223 | switch (facade.Package.Type) | ||
| 224 | { | ||
| 225 | case WixBundlePackageType.Exe: | ||
| 226 | { | ||
| 227 | ProcessExePackageCommand command = new ProcessExePackageCommand(); | ||
| 228 | command.AuthoredPayloads = payloads; | ||
| 229 | command.Facade = facade; | ||
| 230 | command.Execute(); | ||
| 231 | |||
| 232 | // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer); | ||
| 233 | } | ||
| 234 | break; | ||
| 235 | |||
| 236 | case WixBundlePackageType.Msi: | ||
| 237 | { | ||
| 238 | var command = new ProcessMsiPackageCommand(); | ||
| 239 | command.AuthoredPayloads = payloads; | ||
| 240 | command.Facade = facade; | ||
| 241 | command.BackendExtensions = this.BackendExtensions; | ||
| 242 | command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]); | ||
| 243 | command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]); | ||
| 244 | command.PayloadTable = this.Output.Tables["WixBundlePayload"]; | ||
| 245 | command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]); | ||
| 246 | command.Execute(); | ||
| 247 | |||
| 248 | if (null != variableCache) | ||
| 249 | { | ||
| 250 | variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString()); | ||
| 251 | |||
| 252 | if (null != facade.MsiPackage.Manufacturer) | ||
| 253 | { | ||
| 254 | variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | } | ||
| 259 | break; | ||
| 260 | |||
| 261 | case WixBundlePackageType.Msp: | ||
| 262 | { | ||
| 263 | ProcessMspPackageCommand command = new ProcessMspPackageCommand(); | ||
| 264 | command.AuthoredPayloads = payloads; | ||
| 265 | command.Facade = facade; | ||
| 266 | command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); | ||
| 267 | command.Execute(); | ||
| 268 | } | ||
| 269 | break; | ||
| 270 | |||
| 271 | case WixBundlePackageType.Msu: | ||
| 272 | { | ||
| 273 | ProcessMsuPackageCommand command = new ProcessMsuPackageCommand(); | ||
| 274 | command.Facade = facade; | ||
| 275 | command.Execute(); | ||
| 276 | } | ||
| 277 | break; | ||
| 278 | } | ||
| 279 | |||
| 280 | if (null != variableCache) | ||
| 281 | { | ||
| 282 | BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache); | ||
| 283 | } | ||
| 284 | } | ||
| 285 | |||
| 286 | // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later) | ||
| 287 | // are present. | ||
| 288 | payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]); | ||
| 289 | |||
| 290 | // Process the payloads that were added by processing the packages. | ||
| 291 | { | ||
| 292 | ProcessPayloadsCommand command = new ProcessPayloadsCommand(); | ||
| 293 | command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList(); | ||
| 294 | command.DefaultPackaging = bundleRow.DefaultPackagingType; | ||
| 295 | command.LayoutDirectory = layoutDirectory; | ||
| 296 | command.Execute(); | ||
| 297 | |||
| 298 | fileTransfers.AddRange(command.FileTransfers); | ||
| 299 | |||
| 300 | processedPayloads = null; | ||
| 301 | } | ||
| 302 | |||
| 303 | // Set the package metadata from the payloads now that we have the complete payload information. | ||
| 304 | ILookup<string, WixBundlePayloadRow> payloadsByPackage = payloads.Values.ToLookup(p => p.Package); | ||
| 305 | |||
| 306 | { | ||
| 307 | foreach (PackageFacade facade in facades.Values) | ||
| 308 | { | ||
| 309 | facade.Package.Size = 0; | ||
| 310 | |||
| 311 | IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[facade.Package.WixChainItemId]; | ||
| 312 | |||
| 313 | foreach (WixBundlePayloadRow payload in packagePayloads) | ||
| 314 | { | ||
| 315 | facade.Package.Size += payload.FileSize; | ||
| 316 | } | ||
| 317 | |||
| 318 | if (!facade.Package.InstallSize.HasValue) | ||
| 319 | { | ||
| 320 | facade.Package.InstallSize = facade.Package.Size; | ||
| 321 | |||
| 322 | } | ||
| 323 | |||
| 324 | WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload]; | ||
| 325 | |||
| 326 | if (String.IsNullOrEmpty(facade.Package.Description)) | ||
| 327 | { | ||
| 328 | facade.Package.Description = packagePayload.Description; | ||
| 329 | } | ||
| 330 | |||
| 331 | if (String.IsNullOrEmpty(facade.Package.DisplayName)) | ||
| 332 | { | ||
| 333 | facade.Package.DisplayName = packagePayload.DisplayName; | ||
| 334 | } | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | |||
| 339 | // Give the UX payloads their embedded IDs... | ||
| 340 | int uxPayloadIndex = 0; | ||
| 341 | { | ||
| 342 | foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container)) | ||
| 343 | { | ||
| 344 | // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even | ||
| 345 | // downloaded. The current engine requires the UX to be fully present before any downloading starts, | ||
| 346 | // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads | ||
| 347 | // into the temporary UX directory correctly, so we don't allow external either. | ||
| 348 | if (PackagingType.Embedded != payload.Packaging) | ||
| 349 | { | ||
| 350 | Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); | ||
| 351 | payload.Packaging = PackagingType.Embedded; | ||
| 352 | } | ||
| 353 | |||
| 354 | payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); | ||
| 355 | ++uxPayloadIndex; | ||
| 356 | } | ||
| 357 | |||
| 358 | if (0 == uxPayloadIndex) | ||
| 359 | { | ||
| 360 | // If we didn't get any UX payloads, it's an error! | ||
| 361 | throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); | ||
| 362 | } | ||
| 363 | |||
| 364 | // Give the embedded payloads without an embedded id yet an embedded id. | ||
| 365 | int payloadIndex = 0; | ||
| 366 | foreach (WixBundlePayloadRow payload in payloads.Values) | ||
| 367 | { | ||
| 368 | Debug.Assert(PackagingType.Unknown != payload.Packaging); | ||
| 369 | |||
| 370 | if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) | ||
| 371 | { | ||
| 372 | payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex); | ||
| 373 | ++payloadIndex; | ||
| 374 | } | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | // Determine patches to automatically slipstream. | ||
| 379 | { | ||
| 380 | AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand(); | ||
| 381 | command.PackageFacades = facades.Values; | ||
| 382 | command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]); | ||
| 383 | command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]); | ||
| 384 | command.Execute(); | ||
| 385 | } | ||
| 386 | |||
| 387 | // If catalog files exist, non-embedded payloads should validate with the catalogs. | ||
| 388 | IEnumerable<WixBundleCatalogRow> catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs<WixBundleCatalogRow>(); | ||
| 389 | |||
| 390 | if (catalogs.Any()) | ||
| 391 | { | ||
| 392 | VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand(); | ||
| 393 | command.Catalogs = catalogs; | ||
| 394 | command.Payloads = payloads.Values; | ||
| 395 | command.Execute(); | ||
| 396 | } | ||
| 397 | |||
| 398 | if (Messaging.Instance.EncounteredError) | ||
| 399 | { | ||
| 400 | return; | ||
| 401 | } | ||
| 402 | |||
| 403 | IEnumerable<PackageFacade> orderedFacades; | ||
| 404 | IEnumerable<WixBundleRollbackBoundaryRow> boundaries; | ||
| 405 | { | ||
| 406 | OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand(); | ||
| 407 | command.Boundaries = new RowDictionary<WixBundleRollbackBoundaryRow>(this.Output.Tables["WixBundleRollbackBoundary"]); | ||
| 408 | command.PackageFacades = facades; | ||
| 409 | command.WixGroupTable = wixGroupTable; | ||
| 410 | command.Execute(); | ||
| 411 | |||
| 412 | orderedFacades = command.OrderedPackageFacades; | ||
| 413 | boundaries = command.UsedRollbackBoundaries; | ||
| 414 | } | ||
| 415 | |||
| 416 | // Resolve any delayed fields before generating the manifest. | ||
| 417 | if (this.DelayedFields.Any()) | ||
| 418 | { | ||
| 419 | var resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand(); | ||
| 420 | resolveDelayedFieldsCommand.OutputType = this.Output.Type; | ||
| 421 | resolveDelayedFieldsCommand.DelayedFields = this.DelayedFields; | ||
| 422 | resolveDelayedFieldsCommand.ModularizationGuid = null; | ||
| 423 | resolveDelayedFieldsCommand.VariableCache = variableCache; | ||
| 424 | resolveDelayedFieldsCommand.Execute(); | ||
| 425 | } | ||
| 426 | |||
| 427 | // Set the overridable bundle provider key. | ||
| 428 | this.SetBundleProviderKey(this.Output, bundleRow); | ||
| 429 | |||
| 430 | // Import or generate dependency providers for packages in the manifest. | ||
| 431 | this.ProcessDependencyProviders(this.Output, facades); | ||
| 432 | |||
| 433 | // Update the bundle per-machine/per-user scope based on the chained packages. | ||
| 434 | this.ResolveBundleInstallScope(bundleRow, orderedFacades); | ||
| 435 | |||
| 436 | // Generate the core-defined BA manifest tables... | ||
| 437 | { | ||
| 438 | CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand(); | ||
| 439 | command.BundleRow = bundleRow; | ||
| 440 | command.ChainPackages = orderedFacades; | ||
| 441 | command.LastUXPayloadIndex = uxPayloadIndex; | ||
| 442 | command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>(); | ||
| 443 | command.Output = this.Output; | ||
| 444 | command.Payloads = payloads; | ||
| 445 | command.TableDefinitions = this.TableDefinitions; | ||
| 446 | command.TempFilesLocation = this.IntermediateFolder; | ||
| 447 | command.Execute(); | ||
| 448 | |||
| 449 | WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow; | ||
| 450 | payloads.Add(baManifestPayload); | ||
| 451 | } | ||
| 452 | |||
| 453 | //foreach (BinderExtension extension in this.Extensions) | ||
| 454 | //{ | ||
| 455 | // extension.PostBind(this.Context); | ||
| 456 | //} | ||
| 457 | |||
| 458 | // Create all the containers except the UX container first so the manifest (that goes in the UX container) | ||
| 459 | // can contain all size and hash information about the non-UX containers. | ||
| 460 | RowDictionary<WixBundleContainerRow> containers = new RowDictionary<WixBundleContainerRow>(this.Output.Tables["WixBundleContainer"]); | ||
| 461 | |||
| 462 | ILookup<string, WixBundlePayloadRow> payloadsByContainer = payloads.Values.ToLookup(p => p.Container); | ||
| 463 | |||
| 464 | int attachedContainerIndex = 1; // count starts at one because UX container is "0". | ||
| 465 | |||
| 466 | IEnumerable<WixBundlePayloadRow> uxContainerPayloads = Enumerable.Empty<WixBundlePayloadRow>(); | ||
| 467 | |||
| 468 | foreach (WixBundleContainerRow container in containers.Values) | ||
| 469 | { | ||
| 470 | IEnumerable<WixBundlePayloadRow> containerPayloads = payloadsByContainer[container.Id]; | ||
| 471 | |||
| 472 | if (!containerPayloads.Any()) | ||
| 473 | { | ||
| 474 | if (container.Id != Compiler.BurnDefaultAttachedContainerId) | ||
| 475 | { | ||
| 476 | // TODO: display warning that we're ignoring container that ended up with no paylods in it. | ||
| 477 | } | ||
| 478 | } | ||
| 479 | else if (Compiler.BurnUXContainerId == container.Id) | ||
| 480 | { | ||
| 481 | container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); | ||
| 482 | container.AttachedContainerIndex = 0; | ||
| 483 | |||
| 484 | // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first | ||
| 485 | // in the list since that is the Payload that Burn attempts to load. | ||
| 486 | List<WixBundlePayloadRow> uxPayloads = new List<WixBundlePayloadRow>(); | ||
| 487 | |||
| 488 | string baPayloadId = baRow.FieldAsString(0); | ||
| 489 | |||
| 490 | foreach (WixBundlePayloadRow uxPayload in containerPayloads) | ||
| 491 | { | ||
| 492 | if (uxPayload.Id == baPayloadId) | ||
| 493 | { | ||
| 494 | uxPayloads.Insert(0, uxPayload); | ||
| 495 | } | ||
| 496 | else | ||
| 497 | { | ||
| 498 | uxPayloads.Add(uxPayload); | ||
| 499 | } | ||
| 500 | } | ||
| 501 | |||
| 502 | uxContainerPayloads = uxPayloads; | ||
| 503 | } | ||
| 504 | else | ||
| 505 | { | ||
| 506 | container.WorkingPath = Path.Combine(this.IntermediateFolder, container.Name); | ||
| 507 | |||
| 508 | // Add detached containers to the list of file transfers. | ||
| 509 | if (ContainerType.Detached == container.Type) | ||
| 510 | { | ||
| 511 | FileTransfer transfer; | ||
| 512 | if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) | ||
| 513 | { | ||
| 514 | transfer.Built = true; | ||
| 515 | fileTransfers.Add(transfer); | ||
| 516 | } | ||
| 517 | } | ||
| 518 | else // update the attached container index. | ||
| 519 | { | ||
| 520 | Debug.Assert(ContainerType.Attached == container.Type); | ||
| 521 | |||
| 522 | container.AttachedContainerIndex = attachedContainerIndex; | ||
| 523 | ++attachedContainerIndex; | ||
| 524 | } | ||
| 525 | |||
| 526 | this.CreateContainer(container, containerPayloads, null); | ||
| 527 | } | ||
| 528 | } | ||
| 529 | |||
| 530 | // Create the bundle manifest then UX container. | ||
| 531 | string manifestPath = Path.Combine(this.IntermediateFolder, "bundle-manifest.xml"); | ||
| 532 | { | ||
| 533 | var command = new CreateBurnManifestCommand(); | ||
| 534 | command.BackendExtensions = this.BackendExtensions; | ||
| 535 | command.Output = this.Output; | ||
| 536 | |||
| 537 | command.BundleInfo = bundleRow; | ||
| 538 | command.Chain = chainRow; | ||
| 539 | command.Containers = containers; | ||
| 540 | command.Catalogs = catalogs; | ||
| 541 | command.ExecutableName = Path.GetFileName(this.OutputPath); | ||
| 542 | command.OrderedPackages = orderedFacades; | ||
| 543 | command.OutputPath = manifestPath; | ||
| 544 | command.RollbackBoundaries = boundaries; | ||
| 545 | command.OrderedSearches = orderedSearches; | ||
| 546 | command.Payloads = payloads; | ||
| 547 | command.UXContainerPayloads = uxContainerPayloads; | ||
| 548 | command.Execute(); | ||
| 549 | } | ||
| 550 | |||
| 551 | WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId]; | ||
| 552 | this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath); | ||
| 553 | |||
| 554 | // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note | ||
| 555 | // that today, the x64 Burn uses the x86 stub. | ||
| 556 | string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString(); | ||
| 557 | |||
| 558 | string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe"); | ||
| 559 | string bundleTempPath = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); | ||
| 560 | |||
| 561 | Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile)); | ||
| 562 | |||
| 563 | string bundleFilename = Path.GetFileName(this.OutputPath); | ||
| 564 | if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) | ||
| 565 | { | ||
| 566 | Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename)); | ||
| 567 | } | ||
| 568 | |||
| 569 | FileTransfer bundleTransfer; | ||
| 570 | if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer)) | ||
| 571 | { | ||
| 572 | bundleTransfer.Built = true; | ||
| 573 | fileTransfers.Add(bundleTransfer); | ||
| 574 | } | ||
| 575 | |||
| 576 | File.Copy(stubFile, bundleTempPath, true); | ||
| 577 | File.SetAttributes(bundleTempPath, FileAttributes.Normal); | ||
| 578 | |||
| 579 | this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow); | ||
| 580 | |||
| 581 | // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers | ||
| 582 | // if they should be attached. | ||
| 583 | using (BurnWriter writer = BurnWriter.Open(bundleTempPath)) | ||
| 584 | { | ||
| 585 | FileInfo burnStubFile = new FileInfo(bundleTempPath); | ||
| 586 | writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId); | ||
| 587 | |||
| 588 | // Always attach the UX container first | ||
| 589 | writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX); | ||
| 590 | |||
| 591 | // Now append all other attached containers | ||
| 592 | foreach (WixBundleContainerRow container in containers.Values) | ||
| 593 | { | ||
| 594 | if (ContainerType.Attached == container.Type) | ||
| 595 | { | ||
| 596 | // The container was only created if it had payloads. | ||
| 597 | if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) | ||
| 598 | { | ||
| 599 | writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached); | ||
| 600 | } | ||
| 601 | } | ||
| 602 | } | ||
| 603 | } | ||
| 604 | |||
| 605 | if (null != this.PdbFile) | ||
| 606 | { | ||
| 607 | Pdb pdb = new Pdb(); | ||
| 608 | pdb.Output = Output; | ||
| 609 | pdb.Save(this.PdbFile); | ||
| 610 | } | ||
| 611 | |||
| 612 | this.FileTransfers = fileTransfers; | ||
| 613 | this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList(); | ||
| 614 | } | ||
| 615 | |||
| 616 | private Table GetRequiredTable(string tableName) | ||
| 617 | { | ||
| 618 | Table table = this.Output.Tables[tableName]; | ||
| 619 | if (null == table || 0 == table.Rows.Count) | ||
| 620 | { | ||
| 621 | throw new WixException(WixErrors.MissingBundleInformation(tableName)); | ||
| 622 | } | ||
| 623 | |||
| 624 | return table; | ||
| 625 | } | ||
| 626 | |||
| 627 | private Row GetSingleRowTable(string tableName) | ||
| 628 | { | ||
| 629 | Table table = this.Output.Tables[tableName]; | ||
| 630 | if (null == table || 1 != table.Rows.Count) | ||
| 631 | { | ||
| 632 | throw new WixException(WixErrors.MissingBundleInformation(tableName)); | ||
| 633 | } | ||
| 634 | |||
| 635 | return table.Rows[0]; | ||
| 636 | } | ||
| 637 | |||
| 638 | private List<WixSearchInfo> OrderSearches() | ||
| 639 | { | ||
| 640 | Dictionary<string, WixSearchInfo> allSearches = new Dictionary<string, WixSearchInfo>(); | ||
| 641 | Table wixFileSearchTable = this.Output.Tables["WixFileSearch"]; | ||
| 642 | if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) | ||
| 643 | { | ||
| 644 | foreach (Row row in wixFileSearchTable.Rows) | ||
| 645 | { | ||
| 646 | WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row); | ||
| 647 | allSearches.Add(fileSearchInfo.Id, fileSearchInfo); | ||
| 648 | } | ||
| 649 | } | ||
| 650 | |||
| 651 | Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"]; | ||
| 652 | if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count) | ||
| 653 | { | ||
| 654 | foreach (Row row in wixRegistrySearchTable.Rows) | ||
| 655 | { | ||
| 656 | WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row); | ||
| 657 | allSearches.Add(registrySearchInfo.Id, registrySearchInfo); | ||
| 658 | } | ||
| 659 | } | ||
| 660 | |||
| 661 | Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"]; | ||
| 662 | if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count) | ||
| 663 | { | ||
| 664 | foreach (Row row in wixComponentSearchTable.Rows) | ||
| 665 | { | ||
| 666 | WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row); | ||
| 667 | allSearches.Add(componentSearchInfo.Id, componentSearchInfo); | ||
| 668 | } | ||
| 669 | } | ||
| 670 | |||
| 671 | Table wixProductSearchTable = this.Output.Tables["WixProductSearch"]; | ||
| 672 | if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count) | ||
| 673 | { | ||
| 674 | foreach (Row row in wixProductSearchTable.Rows) | ||
| 675 | { | ||
| 676 | WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row); | ||
| 677 | allSearches.Add(productSearchInfo.Id, productSearchInfo); | ||
| 678 | } | ||
| 679 | } | ||
| 680 | |||
| 681 | // Merge in the variable/condition info and get the canonical ordering for | ||
| 682 | // the searches. | ||
| 683 | List<WixSearchInfo> orderedSearches = new List<WixSearchInfo>(); | ||
| 684 | Table wixSearchTable = this.Output.Tables["WixSearch"]; | ||
| 685 | if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count) | ||
| 686 | { | ||
| 687 | orderedSearches.Capacity = wixSearchTable.Rows.Count; | ||
| 688 | foreach (Row row in wixSearchTable.Rows) | ||
| 689 | { | ||
| 690 | WixSearchInfo searchInfo = allSearches[(string)row[0]]; | ||
| 691 | searchInfo.AddWixSearchRowInfo(row); | ||
| 692 | orderedSearches.Add(searchInfo); | ||
| 693 | } | ||
| 694 | } | ||
| 695 | |||
| 696 | return orderedSearches; | ||
| 697 | } | ||
| 698 | |||
| 699 | /// <summary> | ||
| 700 | /// Populates the variable cache with specific package properties. | ||
| 701 | /// </summary> | ||
| 702 | /// <param name="package">The package with properties to cache.</param> | ||
| 703 | /// <param name="variableCache">The property cache.</param> | ||
| 704 | private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary<string, string> variableCache) | ||
| 705 | { | ||
| 706 | string id = package.WixChainItemId; | ||
| 707 | |||
| 708 | variableCache.Add(String.Concat("packageDescription.", id), package.Description); | ||
| 709 | //variableCache.Add(String.Concat("packageLanguage.", id), package.Language); | ||
| 710 | //variableCache.Add(String.Concat("packageManufacturer.", id), package.Manufacturer); | ||
| 711 | variableCache.Add(String.Concat("packageName.", id), package.DisplayName); | ||
| 712 | variableCache.Add(String.Concat("packageVersion.", id), package.Version); | ||
| 713 | } | ||
| 714 | |||
| 715 | private void CreateContainer(WixBundleContainerRow container, IEnumerable<WixBundlePayloadRow> containerPayloads, string manifestFile) | ||
| 716 | { | ||
| 717 | CreateContainerCommand command = new CreateContainerCommand(); | ||
| 718 | command.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
| 719 | command.Payloads = containerPayloads; | ||
| 720 | command.ManifestFile = manifestFile; | ||
| 721 | command.OutputPath = container.WorkingPath; | ||
| 722 | command.Execute(); | ||
| 723 | |||
| 724 | container.Hash = command.Hash; | ||
| 725 | container.Size = command.Size; | ||
| 726 | } | ||
| 727 | |||
| 728 | private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable<PackageFacade> facades) | ||
| 729 | { | ||
| 730 | foreach (PackageFacade facade in facades) | ||
| 731 | { | ||
| 732 | if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine) | ||
| 733 | { | ||
| 734 | Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); | ||
| 735 | |||
| 736 | bundleInfo.PerMachine = false; | ||
| 737 | break; | ||
| 738 | } | ||
| 739 | } | ||
| 740 | |||
| 741 | foreach (PackageFacade facade in facades) | ||
| 742 | { | ||
| 743 | // Update package scope from bundle scope if default. | ||
| 744 | if (YesNoDefaultType.Default == facade.Package.PerMachine) | ||
| 745 | { | ||
| 746 | facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; | ||
| 747 | } | ||
| 748 | |||
| 749 | // We will only register packages in the same scope as the bundle. Warn if any packages with providers | ||
| 750 | // are in a different scope and not permanent (permanents typically don't need a ref-count). | ||
| 751 | if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count) | ||
| 752 | { | ||
| 753 | Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId)); | ||
| 754 | } | ||
| 755 | } | ||
| 756 | } | ||
| 757 | |||
| 758 | private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo) | ||
| 759 | { | ||
| 760 | WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection(); | ||
| 761 | WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033); | ||
| 762 | |||
| 763 | version.Load(bundleTempPath); | ||
| 764 | resources.Add(version); | ||
| 765 | |||
| 766 | // Ensure the bundle info provides a full four part version. | ||
| 767 | Version fourPartVersion = new Version(bundleInfo.Version); | ||
| 768 | int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major; | ||
| 769 | int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor; | ||
| 770 | int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build; | ||
| 771 | int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision; | ||
| 772 | |||
| 773 | if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision) | ||
| 774 | { | ||
| 775 | throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version)); | ||
| 776 | } | ||
| 777 | |||
| 778 | fourPartVersion = new Version(major, minor, build, revision); | ||
| 779 | version.FileVersion = fourPartVersion; | ||
| 780 | version.ProductVersion = fourPartVersion; | ||
| 781 | |||
| 782 | WixToolset.Dtf.Resources.VersionStringTable strings = version[1033]; | ||
| 783 | strings["LegalCopyright"] = bundleInfo.Copyright; | ||
| 784 | strings["OriginalFilename"] = Path.GetFileName(outputPath); | ||
| 785 | strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts. | ||
| 786 | strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts. | ||
| 787 | |||
| 788 | if (!String.IsNullOrEmpty(bundleInfo.Name)) | ||
| 789 | { | ||
| 790 | strings["ProductName"] = bundleInfo.Name; | ||
| 791 | strings["FileDescription"] = bundleInfo.Name; | ||
| 792 | } | ||
| 793 | |||
| 794 | if (!String.IsNullOrEmpty(bundleInfo.Publisher)) | ||
| 795 | { | ||
| 796 | strings["CompanyName"] = bundleInfo.Publisher; | ||
| 797 | } | ||
| 798 | else | ||
| 799 | { | ||
| 800 | strings["CompanyName"] = String.Empty; | ||
| 801 | } | ||
| 802 | |||
| 803 | if (!String.IsNullOrEmpty(bundleInfo.IconPath)) | ||
| 804 | { | ||
| 805 | Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033); | ||
| 806 | iconGroup.ReadFromFile(bundleInfo.IconPath); | ||
| 807 | resources.Add(iconGroup); | ||
| 808 | |||
| 809 | foreach (Dtf.Resources.Resource icon in iconGroup.Icons) | ||
| 810 | { | ||
| 811 | resources.Add(icon); | ||
| 812 | } | ||
| 813 | } | ||
| 814 | |||
| 815 | if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath)) | ||
| 816 | { | ||
| 817 | Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033); | ||
| 818 | bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath); | ||
| 819 | resources.Add(bitmap); | ||
| 820 | } | ||
| 821 | |||
| 822 | resources.Save(bundleTempPath); | ||
| 823 | } | ||
| 824 | |||
| 825 | #region DependencyExtension | ||
| 826 | /// <summary> | ||
| 827 | /// Imports authored dependency providers for each package in the manifest, | ||
| 828 | /// and generates dependency providers for certain package types that do not | ||
| 829 | /// have a provider defined. | ||
| 830 | /// </summary> | ||
| 831 | /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param> | ||
| 832 | /// <param name="facades">An indexed collection of chained packages.</param> | ||
| 833 | private void ProcessDependencyProviders(Output bundle, IDictionary<string, PackageFacade> facades) | ||
| 834 | { | ||
| 835 | // First import any authored dependencies. These may merge with imported provides from MSI packages. | ||
| 836 | Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; | ||
| 837 | if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) | ||
| 838 | { | ||
| 839 | // Add package information for each dependency provider authored into the manifest. | ||
| 840 | foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) | ||
| 841 | { | ||
| 842 | string packageId = (string)wixDependencyProviderRow[1]; | ||
| 843 | |||
| 844 | PackageFacade facade = null; | ||
| 845 | if (facades.TryGetValue(packageId, out facade)) | ||
| 846 | { | ||
| 847 | ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow); | ||
| 848 | |||
| 849 | if (String.IsNullOrEmpty(dependency.Key)) | ||
| 850 | { | ||
| 851 | switch (facade.Package.Type) | ||
| 852 | { | ||
| 853 | // The WixDependencyExtension allows an empty Key for MSIs and MSPs. | ||
| 854 | case WixBundlePackageType.Msi: | ||
| 855 | dependency.Key = facade.MsiPackage.ProductCode; | ||
| 856 | break; | ||
| 857 | case WixBundlePackageType.Msp: | ||
| 858 | dependency.Key = facade.MspPackage.PatchCode; | ||
| 859 | break; | ||
| 860 | } | ||
| 861 | } | ||
| 862 | |||
| 863 | if (String.IsNullOrEmpty(dependency.Version)) | ||
| 864 | { | ||
| 865 | dependency.Version = facade.Package.Version; | ||
| 866 | } | ||
| 867 | |||
| 868 | // If the version is still missing, a version could not be harvested from the package and was not authored. | ||
| 869 | if (String.IsNullOrEmpty(dependency.Version)) | ||
| 870 | { | ||
| 871 | Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId)); | ||
| 872 | } | ||
| 873 | |||
| 874 | if (String.IsNullOrEmpty(dependency.DisplayName)) | ||
| 875 | { | ||
| 876 | dependency.DisplayName = facade.Package.DisplayName; | ||
| 877 | } | ||
| 878 | |||
| 879 | if (!facade.Provides.Merge(dependency)) | ||
| 880 | { | ||
| 881 | Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); | ||
| 882 | } | ||
| 883 | } | ||
| 884 | } | ||
| 885 | } | ||
| 886 | |||
| 887 | // Generate providers for MSI packages that still do not have providers. | ||
| 888 | foreach (PackageFacade facade in facades.Values) | ||
| 889 | { | ||
| 890 | string key = null; | ||
| 891 | |||
| 892 | if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count) | ||
| 893 | { | ||
| 894 | key = facade.MsiPackage.ProductCode; | ||
| 895 | } | ||
| 896 | else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count) | ||
| 897 | { | ||
| 898 | key = facade.MspPackage.PatchCode; | ||
| 899 | } | ||
| 900 | |||
| 901 | if (!String.IsNullOrEmpty(key)) | ||
| 902 | { | ||
| 903 | ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0); | ||
| 904 | |||
| 905 | if (!facade.Provides.Merge(dependency)) | ||
| 906 | { | ||
| 907 | Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId)); | ||
| 908 | } | ||
| 909 | } | ||
| 910 | } | ||
| 911 | } | ||
| 912 | |||
| 913 | /// <summary> | ||
| 914 | /// Sets the provider key for the bundle. | ||
| 915 | /// </summary> | ||
| 916 | /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param> | ||
| 917 | /// <param name="bundleInfo">The <see cref="BundleInfo"/> containing the provider key and other information for the bundle.</param> | ||
| 918 | private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo) | ||
| 919 | { | ||
| 920 | // From DependencyCommon.cs in the WixDependencyExtension. | ||
| 921 | const int ProvidesAttributesBundle = 0x10000; | ||
| 922 | |||
| 923 | Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; | ||
| 924 | if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) | ||
| 925 | { | ||
| 926 | // Search the WixDependencyProvider table for the single bundle provider key. | ||
| 927 | foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) | ||
| 928 | { | ||
| 929 | object attributes = wixDependencyProviderRow[5]; | ||
| 930 | if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes)) | ||
| 931 | { | ||
| 932 | bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2]; | ||
| 933 | break; | ||
| 934 | } | ||
| 935 | } | ||
| 936 | } | ||
| 937 | |||
| 938 | // Defaults to the bundle ID as the provider key. | ||
| 939 | } | ||
| 940 | #endregion | ||
| 941 | } | ||
| 942 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs b/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs new file mode 100644 index 00000000..e64773b4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Represents an authored or imported dependency provider. | ||
| 11 | /// </summary> | ||
| 12 | internal sealed class ProvidesDependency | ||
| 13 | { | ||
| 14 | /// <summary> | ||
| 15 | /// Creates a new instance of the <see cref="ProviderDependency"/> class from a <see cref="Row"/>. | ||
| 16 | /// </summary> | ||
| 17 | /// <param name="row">The <see cref="Row"/> from which data is imported.</param> | ||
| 18 | internal ProvidesDependency(Row row) | ||
| 19 | : this((string)row[2], (string)row[3], (string)row[4], (int?)row[5]) | ||
| 20 | { | ||
| 21 | } | ||
| 22 | |||
| 23 | /// <summary> | ||
| 24 | /// Creates a new instance of the <see cref="ProviderDependency"/> class. | ||
| 25 | /// </summary> | ||
| 26 | /// <param name="key">The unique key of the dependency.</param> | ||
| 27 | /// <param name="attributes">Additional attributes for the dependency.</param> | ||
| 28 | internal ProvidesDependency(string key, string version, string displayName, int? attributes) | ||
| 29 | { | ||
| 30 | this.Key = key; | ||
| 31 | this.Version = version; | ||
| 32 | this.DisplayName = displayName; | ||
| 33 | this.Attributes = attributes; | ||
| 34 | } | ||
| 35 | |||
| 36 | /// <summary> | ||
| 37 | /// Gets or sets the unique key of the package provider. | ||
| 38 | /// </summary> | ||
| 39 | internal string Key { get; set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets or sets the version of the package provider. | ||
| 43 | /// </summary> | ||
| 44 | internal string Version { get; set; } | ||
| 45 | |||
| 46 | /// <summary> | ||
| 47 | /// Gets or sets the display name of the package provider. | ||
| 48 | /// </summary> | ||
| 49 | internal string DisplayName { get; set; } | ||
| 50 | |||
| 51 | /// <summary> | ||
| 52 | /// Gets or sets the attributes for the dependency. | ||
| 53 | /// </summary> | ||
| 54 | internal int? Attributes { get; set; } | ||
| 55 | |||
| 56 | /// <summary> | ||
| 57 | /// Gets or sets whether the dependency was imported from the package. | ||
| 58 | /// </summary> | ||
| 59 | internal bool Imported { get; set; } | ||
| 60 | |||
| 61 | /// <summary> | ||
| 62 | /// Gets whether certain properties are the same. | ||
| 63 | /// </summary> | ||
| 64 | /// <param name="other">Another <see cref="ProvidesDependency"/> to compare.</param> | ||
| 65 | /// <remarks>This is not the same as object equality, but only checks a subset of properties | ||
| 66 | /// to determine if the objects are similar and could be merged into a collection.</remarks> | ||
| 67 | /// <returns>True if certain properties are the same.</returns> | ||
| 68 | internal bool Equals(ProvidesDependency other) | ||
| 69 | { | ||
| 70 | if (null != other) | ||
| 71 | { | ||
| 72 | return this.Key == other.Key && | ||
| 73 | this.Version == other.Version && | ||
| 74 | this.DisplayName == other.DisplayName; | ||
| 75 | } | ||
| 76 | |||
| 77 | return false; | ||
| 78 | } | ||
| 79 | |||
| 80 | /// <summary> | ||
| 81 | /// Writes the dependency to the bundle XML manifest. | ||
| 82 | /// </summary> | ||
| 83 | /// <param name="writer">The <see cref="XmlTextWriter"/> for the bundle XML manifest.</param> | ||
| 84 | internal void WriteXml(XmlTextWriter writer) | ||
| 85 | { | ||
| 86 | writer.WriteStartElement("Provides"); | ||
| 87 | writer.WriteAttributeString("Key", this.Key); | ||
| 88 | |||
| 89 | if (!String.IsNullOrEmpty(this.Version)) | ||
| 90 | { | ||
| 91 | writer.WriteAttributeString("Version", this.Version); | ||
| 92 | } | ||
| 93 | |||
| 94 | if (!String.IsNullOrEmpty(this.DisplayName)) | ||
| 95 | { | ||
| 96 | writer.WriteAttributeString("DisplayName", this.DisplayName); | ||
| 97 | } | ||
| 98 | |||
| 99 | if (this.Imported) | ||
| 100 | { | ||
| 101 | // The package dependency was explicitly authored into the manifest. | ||
| 102 | writer.WriteAttributeString("Imported", "yes"); | ||
| 103 | } | ||
| 104 | |||
| 105 | writer.WriteEndElement(); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs b/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs new file mode 100644 index 00000000..668b81d3 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.ObjectModel; | ||
| 7 | |||
| 8 | /// <summary> | ||
| 9 | /// A case-insensitive collection of unique <see cref="ProvidesDependency"/> objects. | ||
| 10 | /// </summary> | ||
| 11 | internal sealed class ProvidesDependencyCollection : KeyedCollection<string, ProvidesDependency> | ||
| 12 | { | ||
| 13 | /// <summary> | ||
| 14 | /// Creates a case-insensitive collection of unique <see cref="ProvidesDependency"/> objects. | ||
| 15 | /// </summary> | ||
| 16 | internal ProvidesDependencyCollection() | ||
| 17 | : base(StringComparer.InvariantCultureIgnoreCase) | ||
| 18 | { | ||
| 19 | } | ||
| 20 | |||
| 21 | /// <summary> | ||
| 22 | /// Adds the <see cref="ProvidesDependency"/> to the collection if it doesn't already exist. | ||
| 23 | /// </summary> | ||
| 24 | /// <param name="dependency">The <see cref="ProvidesDependency"/> to add to the collection.</param> | ||
| 25 | /// <returns>True if the <see cref="ProvidesDependency"/> was added to the collection; otherwise, false.</returns> | ||
| 26 | /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception> | ||
| 27 | internal bool Merge(ProvidesDependency dependency) | ||
| 28 | { | ||
| 29 | if (null == dependency) | ||
| 30 | { | ||
| 31 | throw new ArgumentNullException("dependency"); | ||
| 32 | } | ||
| 33 | |||
| 34 | // If the dependency key is already in the collection, verify equality for a subset of properties. | ||
| 35 | if (this.Contains(dependency.Key)) | ||
| 36 | { | ||
| 37 | ProvidesDependency current = this[dependency.Key]; | ||
| 38 | if (!current.Equals(dependency)) | ||
| 39 | { | ||
| 40 | return false; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | base.Add(dependency); | ||
| 45 | return true; | ||
| 46 | } | ||
| 47 | |||
| 48 | /// <summary> | ||
| 49 | /// Gets the <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>. | ||
| 50 | /// </summary> | ||
| 51 | /// <param name="dependency">The dependency to index.</param> | ||
| 52 | /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception> | ||
| 53 | /// <returns>The <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>.</returns> | ||
| 54 | protected override string GetKeyForItem(ProvidesDependency dependency) | ||
| 55 | { | ||
| 56 | if (null == dependency) | ||
| 57 | { | ||
| 58 | throw new ArgumentNullException("dependency"); | ||
| 59 | } | ||
| 60 | |||
| 61 | return dependency.Key; | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs new file mode 100644 index 00000000..f605d7c7 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Utility class for all WixComponentSearches. | ||
| 11 | /// </summary> | ||
| 12 | internal class WixComponentSearchInfo : WixSearchInfo | ||
| 13 | { | ||
| 14 | public WixComponentSearchInfo(Row row) | ||
| 15 | : this((string)row[0], (string)row[1], (string)row[2], (int)row[3]) | ||
| 16 | { | ||
| 17 | } | ||
| 18 | |||
| 19 | public WixComponentSearchInfo(string id, string guid, string productCode, int attributes) | ||
| 20 | : base(id) | ||
| 21 | { | ||
| 22 | this.Guid = guid; | ||
| 23 | this.ProductCode = productCode; | ||
| 24 | this.Attributes = (WixComponentSearchAttributes)attributes; | ||
| 25 | } | ||
| 26 | |||
| 27 | public string Guid { get; private set; } | ||
| 28 | public string ProductCode { get; private set; } | ||
| 29 | public WixComponentSearchAttributes Attributes { get; private set; } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Generates Burn manifest and ParameterInfo-style markup for a component search. | ||
| 33 | /// </summary> | ||
| 34 | /// <param name="writer"></param> | ||
| 35 | public override void WriteXml(XmlTextWriter writer) | ||
| 36 | { | ||
| 37 | writer.WriteStartElement("MsiComponentSearch"); | ||
| 38 | this.WriteWixSearchAttributes(writer); | ||
| 39 | |||
| 40 | writer.WriteAttributeString("ComponentId", this.Guid); | ||
| 41 | |||
| 42 | if (!String.IsNullOrEmpty(this.ProductCode)) | ||
| 43 | { | ||
| 44 | writer.WriteAttributeString("ProductCode", this.ProductCode); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (0 != (this.Attributes & WixComponentSearchAttributes.KeyPath)) | ||
| 48 | { | ||
| 49 | writer.WriteAttributeString("Type", "keyPath"); | ||
| 50 | } | ||
| 51 | else if (0 != (this.Attributes & WixComponentSearchAttributes.State)) | ||
| 52 | { | ||
| 53 | writer.WriteAttributeString("Type", "state"); | ||
| 54 | } | ||
| 55 | else if (0 != (this.Attributes & WixComponentSearchAttributes.WantDirectory)) | ||
| 56 | { | ||
| 57 | writer.WriteAttributeString("Type", "directory"); | ||
| 58 | } | ||
| 59 | |||
| 60 | writer.WriteEndElement(); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs new file mode 100644 index 00000000..ea955db4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Utility class for all WixFileSearches (file and directory searches). | ||
| 11 | /// </summary> | ||
| 12 | internal class WixFileSearchInfo : WixSearchInfo | ||
| 13 | { | ||
| 14 | public WixFileSearchInfo(Row row) | ||
| 15 | : this((string)row[0], (string)row[1], (int)row[9]) | ||
| 16 | { | ||
| 17 | } | ||
| 18 | |||
| 19 | public WixFileSearchInfo(string id, string path, int attributes) | ||
| 20 | : base(id) | ||
| 21 | { | ||
| 22 | this.Path = path; | ||
| 23 | this.Attributes = (WixFileSearchAttributes)attributes; | ||
| 24 | } | ||
| 25 | |||
| 26 | public string Path { get; private set; } | ||
| 27 | public WixFileSearchAttributes Attributes { get; private set; } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Generates Burn manifest and ParameterInfo-style markup for a file/directory search. | ||
| 31 | /// </summary> | ||
| 32 | /// <param name="writer"></param> | ||
| 33 | public override void WriteXml(XmlTextWriter writer) | ||
| 34 | { | ||
| 35 | writer.WriteStartElement((0 == (this.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); | ||
| 36 | this.WriteWixSearchAttributes(writer); | ||
| 37 | writer.WriteAttributeString("Path", this.Path); | ||
| 38 | if (WixFileSearchAttributes.WantExists == (this.Attributes & WixFileSearchAttributes.WantExists)) | ||
| 39 | { | ||
| 40 | writer.WriteAttributeString("Type", "exists"); | ||
| 41 | } | ||
| 42 | else if (WixFileSearchAttributes.WantVersion == (this.Attributes & WixFileSearchAttributes.WantVersion)) | ||
| 43 | { | ||
| 44 | // Can never get here for DirectorySearch. | ||
| 45 | writer.WriteAttributeString("Type", "version"); | ||
| 46 | } | ||
| 47 | else | ||
| 48 | { | ||
| 49 | writer.WriteAttributeString("Type", "path"); | ||
| 50 | } | ||
| 51 | writer.WriteEndElement(); | ||
| 52 | } | ||
| 53 | } | ||
| 54 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs new file mode 100644 index 00000000..b3bf5fee --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs | |||
| @@ -0,0 +1,67 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Utility class for all WixProductSearches. | ||
| 11 | /// </summary> | ||
| 12 | internal class WixProductSearchInfo : WixSearchInfo | ||
| 13 | { | ||
| 14 | public WixProductSearchInfo(Row row) | ||
| 15 | : this((string)row[0], (string)row[1], (int)row[2]) | ||
| 16 | { | ||
| 17 | } | ||
| 18 | |||
| 19 | public WixProductSearchInfo(string id, string guid, int attributes) | ||
| 20 | : base(id) | ||
| 21 | { | ||
| 22 | this.Guid = guid; | ||
| 23 | this.Attributes = (WixProductSearchAttributes)attributes; | ||
| 24 | } | ||
| 25 | |||
| 26 | public string Guid { get; private set; } | ||
| 27 | public WixProductSearchAttributes Attributes { get; private set; } | ||
| 28 | |||
| 29 | /// <summary> | ||
| 30 | /// Generates Burn manifest and ParameterInfo-style markup for a product search. | ||
| 31 | /// </summary> | ||
| 32 | /// <param name="writer"></param> | ||
| 33 | public override void WriteXml(XmlTextWriter writer) | ||
| 34 | { | ||
| 35 | writer.WriteStartElement("MsiProductSearch"); | ||
| 36 | this.WriteWixSearchAttributes(writer); | ||
| 37 | |||
| 38 | if (0 != (this.Attributes & WixProductSearchAttributes.UpgradeCode)) | ||
| 39 | { | ||
| 40 | writer.WriteAttributeString("UpgradeCode", this.Guid); | ||
| 41 | } | ||
| 42 | else | ||
| 43 | { | ||
| 44 | writer.WriteAttributeString("ProductCode", this.Guid); | ||
| 45 | } | ||
| 46 | |||
| 47 | if (0 != (this.Attributes & WixProductSearchAttributes.Version)) | ||
| 48 | { | ||
| 49 | writer.WriteAttributeString("Type", "version"); | ||
| 50 | } | ||
| 51 | else if (0 != (this.Attributes & WixProductSearchAttributes.Language)) | ||
| 52 | { | ||
| 53 | writer.WriteAttributeString("Type", "language"); | ||
| 54 | } | ||
| 55 | else if (0 != (this.Attributes & WixProductSearchAttributes.State)) | ||
| 56 | { | ||
| 57 | writer.WriteAttributeString("Type", "state"); | ||
| 58 | } | ||
| 59 | else if (0 != (this.Attributes & WixProductSearchAttributes.Assignment)) | ||
| 60 | { | ||
| 61 | writer.WriteAttributeString("Type", "assignment"); | ||
| 62 | } | ||
| 63 | |||
| 64 | writer.WriteEndElement(); | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs new file mode 100644 index 00000000..e25f25f4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Xml; | ||
| 7 | using WixToolset.Data; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Utility class for all WixRegistrySearches. | ||
| 11 | /// </summary> | ||
| 12 | internal class WixRegistrySearchInfo : WixSearchInfo | ||
| 13 | { | ||
| 14 | public WixRegistrySearchInfo(Row row) | ||
| 15 | : this((string)row[0], (int)row[1], (string)row[2], (string)row[3], (int)row[4]) | ||
| 16 | { | ||
| 17 | } | ||
| 18 | |||
| 19 | public WixRegistrySearchInfo(string id, int root, string key, string value, int attributes) | ||
| 20 | : base(id) | ||
| 21 | { | ||
| 22 | this.Root = root; | ||
| 23 | this.Key = key; | ||
| 24 | this.Value = value; | ||
| 25 | this.Attributes = (WixRegistrySearchAttributes)attributes; | ||
| 26 | } | ||
| 27 | |||
| 28 | public int Root { get; private set; } | ||
| 29 | public string Key { get; private set; } | ||
| 30 | public string Value { get; private set; } | ||
| 31 | public WixRegistrySearchAttributes Attributes { get; private set; } | ||
| 32 | |||
| 33 | /// <summary> | ||
| 34 | /// Generates Burn manifest and ParameterInfo-style markup for a registry search. | ||
| 35 | /// </summary> | ||
| 36 | /// <param name="writer"></param> | ||
| 37 | public override void WriteXml(XmlTextWriter writer) | ||
| 38 | { | ||
| 39 | writer.WriteStartElement("RegistrySearch"); | ||
| 40 | this.WriteWixSearchAttributes(writer); | ||
| 41 | |||
| 42 | switch (this.Root) | ||
| 43 | { | ||
| 44 | case Core.Native.MsiInterop.MsidbRegistryRootClassesRoot: | ||
| 45 | writer.WriteAttributeString("Root", "HKCR"); | ||
| 46 | break; | ||
| 47 | case Core.Native.MsiInterop.MsidbRegistryRootCurrentUser: | ||
| 48 | writer.WriteAttributeString("Root", "HKCU"); | ||
| 49 | break; | ||
| 50 | case Core.Native.MsiInterop.MsidbRegistryRootLocalMachine: | ||
| 51 | writer.WriteAttributeString("Root", "HKLM"); | ||
| 52 | break; | ||
| 53 | case Core.Native.MsiInterop.MsidbRegistryRootUsers: | ||
| 54 | writer.WriteAttributeString("Root", "HKU"); | ||
| 55 | break; | ||
| 56 | } | ||
| 57 | |||
| 58 | writer.WriteAttributeString("Key", this.Key); | ||
| 59 | |||
| 60 | if (!String.IsNullOrEmpty(this.Value)) | ||
| 61 | { | ||
| 62 | writer.WriteAttributeString("Value", this.Value); | ||
| 63 | } | ||
| 64 | |||
| 65 | bool existenceOnly = 0 != (this.Attributes & WixRegistrySearchAttributes.WantExists); | ||
| 66 | |||
| 67 | writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); | ||
| 68 | |||
| 69 | if (0 != (this.Attributes & WixRegistrySearchAttributes.Win64)) | ||
| 70 | { | ||
| 71 | writer.WriteAttributeString("Win64", "yes"); | ||
| 72 | } | ||
| 73 | |||
| 74 | if (!existenceOnly) | ||
| 75 | { | ||
| 76 | if (0 != (this.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) | ||
| 77 | { | ||
| 78 | writer.WriteAttributeString("ExpandEnvironment", "yes"); | ||
| 79 | } | ||
| 80 | |||
| 81 | // We *always* say this is VariableType="string". If we end up | ||
| 82 | // needing to be more specific, we will have to expand the "Format" | ||
| 83 | // attribute to allow "number" and "version". | ||
| 84 | |||
| 85 | writer.WriteAttributeString("VariableType", "string"); | ||
| 86 | } | ||
| 87 | |||
| 88 | writer.WriteEndElement(); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs b/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs new file mode 100644 index 00000000..9ebca4ae --- /dev/null +++ b/src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.Xml; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Utility base class for all WixSearches. | ||
| 12 | /// </summary> | ||
| 13 | internal abstract class WixSearchInfo | ||
| 14 | { | ||
| 15 | public WixSearchInfo(string id) | ||
| 16 | { | ||
| 17 | this.Id = id; | ||
| 18 | } | ||
| 19 | |||
| 20 | public void AddWixSearchRowInfo(Row row) | ||
| 21 | { | ||
| 22 | Debug.Assert((string)row[0] == Id); | ||
| 23 | Variable = (string)row[1]; | ||
| 24 | Condition = (string)row[2]; | ||
| 25 | } | ||
| 26 | |||
| 27 | public string Id { get; private set; } | ||
| 28 | public string Variable { get; private set; } | ||
| 29 | public string Condition { get; private set; } | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Generates Burn manifest and ParameterInfo-style markup a search. | ||
| 33 | /// </summary> | ||
| 34 | /// <param name="writer"></param> | ||
| 35 | public virtual void WriteXml(XmlTextWriter writer) | ||
| 36 | { | ||
| 37 | } | ||
| 38 | |||
| 39 | /// <summary> | ||
| 40 | /// Writes attributes common to all WixSearch elements. | ||
| 41 | /// </summary> | ||
| 42 | /// <param name="writer"></param> | ||
| 43 | protected void WriteWixSearchAttributes(XmlTextWriter writer) | ||
| 44 | { | ||
| 45 | writer.WriteAttributeString("Id", this.Id); | ||
| 46 | writer.WriteAttributeString("Variable", this.Variable); | ||
| 47 | if (!String.IsNullOrEmpty(this.Condition)) | ||
| 48 | { | ||
| 49 | writer.WriteAttributeString("Condition", this.Condition); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/src/WixToolset.Core.Burn/BundleBackend.cs b/src/WixToolset.Core.Burn/BundleBackend.cs new file mode 100644 index 00000000..ef4d362c --- /dev/null +++ b/src/WixToolset.Core.Burn/BundleBackend.cs | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Core.Burn.Bundles; | ||
| 8 | using WixToolset.Core.Burn.Inscribe; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Bind; | ||
| 11 | using WixToolset.Extensibility; | ||
| 12 | |||
| 13 | internal class BundleBackend : IBackend | ||
| 14 | { | ||
| 15 | public BindResult Bind(IBindContext context) | ||
| 16 | { | ||
| 17 | BindBundleCommand command = new BindBundleCommand(context); | ||
| 18 | //command.DefaultCompressionLevel = context.DefaultCompressionLevel; | ||
| 19 | //command.Extensions = context.Extensions; | ||
| 20 | //command.IntermediateFolder = context.IntermediateFolder; | ||
| 21 | //command.Output = context.IntermediateRepresentation; | ||
| 22 | //command.OutputPath = context.OutputPath; | ||
| 23 | //command.PdbFile = context.OutputPdbPath; | ||
| 24 | //command.WixVariableResolver = context.WixVariableResolver; | ||
| 25 | command.Execute(); | ||
| 26 | |||
| 27 | return new BindResult(command.FileTransfers, command.ContentFilePaths); | ||
| 28 | } | ||
| 29 | |||
| 30 | public bool Inscribe(IInscribeContext context) | ||
| 31 | { | ||
| 32 | if (String.IsNullOrEmpty(context.SignedEngineFile)) | ||
| 33 | { | ||
| 34 | var command = new InscribeBundleCommand(context); | ||
| 35 | return command.Execute(); | ||
| 36 | } | ||
| 37 | else | ||
| 38 | { | ||
| 39 | var command = new InscribeBundleEngineCommand(context); | ||
| 40 | return command.Execute(); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | public Output Unbind(IUnbindContext context) | ||
| 45 | { | ||
| 46 | string uxExtractPath = Path.Combine(context.ExportBasePath, "UX"); | ||
| 47 | string acExtractPath = Path.Combine(context.ExportBasePath, "AttachedContainer"); | ||
| 48 | |||
| 49 | using (BurnReader reader = BurnReader.Open(context.InputFilePath)) | ||
| 50 | { | ||
| 51 | reader.ExtractUXContainer(uxExtractPath, context.IntermediateFolder); | ||
| 52 | reader.ExtractAttachedContainer(acExtractPath, context.IntermediateFolder); | ||
| 53 | } | ||
| 54 | |||
| 55 | return null; | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs new file mode 100644 index 00000000..bac8633b --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs | |||
| @@ -0,0 +1,112 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Linq; | ||
| 9 | using WixToolset.Data; | ||
| 10 | using WixToolset.Data.Rows; | ||
| 11 | |||
| 12 | internal class AutomaticallySlipstreamPatchesCommand | ||
| 13 | { | ||
| 14 | public IEnumerable<PackageFacade> PackageFacades { private get; set; } | ||
| 15 | |||
| 16 | public Table WixBundlePatchTargetCodeTable { private get; set; } | ||
| 17 | |||
| 18 | public Table SlipstreamMspTable { private get; set; } | ||
| 19 | |||
| 20 | public void Execute() | ||
| 21 | { | ||
| 22 | List<WixBundleMsiPackageRow> msiPackages = new List<WixBundleMsiPackageRow>(); | ||
| 23 | Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); | ||
| 24 | Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); | ||
| 25 | |||
| 26 | foreach (PackageFacade facade in this.PackageFacades) | ||
| 27 | { | ||
| 28 | if (WixBundlePackageType.Msi == facade.Package.Type) | ||
| 29 | { | ||
| 30 | // Keep track of all MSI packages. | ||
| 31 | msiPackages.Add(facade.MsiPackage); | ||
| 32 | } | ||
| 33 | else if (WixBundlePackageType.Msp == facade.Package.Type && facade.MspPackage.Slipstream) | ||
| 34 | { | ||
| 35 | IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodeRows = this.WixBundlePatchTargetCodeTable.RowsAs<WixBundlePatchTargetCodeRow>().Where(r => r.MspPackageId == facade.Package.WixChainItemId); | ||
| 36 | |||
| 37 | // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. | ||
| 38 | foreach (WixBundlePatchTargetCodeRow row in patchTargetCodeRows) | ||
| 39 | { | ||
| 40 | if (row.TargetsProductCode) | ||
| 41 | { | ||
| 42 | List<WixBundlePatchTargetCodeRow> rows; | ||
| 43 | if (!targetsProductCode.TryGetValue(row.TargetCode, out rows)) | ||
| 44 | { | ||
| 45 | rows = new List<WixBundlePatchTargetCodeRow>(); | ||
| 46 | targetsProductCode.Add(row.TargetCode, rows); | ||
| 47 | } | ||
| 48 | |||
| 49 | rows.Add(row); | ||
| 50 | } | ||
| 51 | else if (row.TargetsUpgradeCode) | ||
| 52 | { | ||
| 53 | List<WixBundlePatchTargetCodeRow> rows; | ||
| 54 | if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows)) | ||
| 55 | { | ||
| 56 | rows = new List<WixBundlePatchTargetCodeRow>(); | ||
| 57 | targetsUpgradeCode.Add(row.TargetCode, rows); | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | RowIndexedList<Row> slipstreamMspRows = new RowIndexedList<Row>(SlipstreamMspTable); | ||
| 65 | |||
| 66 | // Loop through the MSI and slipstream patches targeting it. | ||
| 67 | foreach (WixBundleMsiPackageRow msi in msiPackages) | ||
| 68 | { | ||
| 69 | List<WixBundlePatchTargetCodeRow> rows; | ||
| 70 | if (targetsProductCode.TryGetValue(msi.ProductCode, out rows)) | ||
| 71 | { | ||
| 72 | foreach (WixBundlePatchTargetCodeRow row in rows) | ||
| 73 | { | ||
| 74 | Debug.Assert(row.TargetsProductCode); | ||
| 75 | Debug.Assert(!row.TargetsUpgradeCode); | ||
| 76 | |||
| 77 | Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); | ||
| 78 | slipstreamMspRow[0] = msi.ChainPackageId; | ||
| 79 | slipstreamMspRow[1] = row.MspPackageId; | ||
| 80 | |||
| 81 | if (slipstreamMspRows.TryAdd(slipstreamMspRow)) | ||
| 82 | { | ||
| 83 | SlipstreamMspTable.Rows.Add(slipstreamMspRow); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | rows = null; | ||
| 88 | } | ||
| 89 | |||
| 90 | if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows)) | ||
| 91 | { | ||
| 92 | foreach (WixBundlePatchTargetCodeRow row in rows) | ||
| 93 | { | ||
| 94 | Debug.Assert(!row.TargetsProductCode); | ||
| 95 | Debug.Assert(row.TargetsUpgradeCode); | ||
| 96 | |||
| 97 | Row slipstreamMspRow = SlipstreamMspTable.CreateRow(row.SourceLineNumbers, false); | ||
| 98 | slipstreamMspRow[0] = msi.ChainPackageId; | ||
| 99 | slipstreamMspRow[1] = row.MspPackageId; | ||
| 100 | |||
| 101 | if (slipstreamMspRows.TryAdd(slipstreamMspRow)) | ||
| 102 | { | ||
| 103 | SlipstreamMspTable.Rows.Add(slipstreamMspRow); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | rows = null; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs new file mode 100644 index 00000000..0baa6094 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/BurnCommon.cs | |||
| @@ -0,0 +1,378 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Common functionality for Burn PE Writer & Reader for the WiX toolset. | ||
| 12 | /// </summary> | ||
| 13 | /// <remarks>This class encapsulates common functionality related to | ||
| 14 | /// bundled/chained setup packages.</remarks> | ||
| 15 | /// <example> | ||
| 16 | /// </example> | ||
| 17 | internal abstract class BurnCommon : IDisposable | ||
| 18 | { | ||
| 19 | public const string BurnNamespace = "http://wixtoolset.org/schemas/v4/2008/Burn"; | ||
| 20 | public const string BurnUXContainerEmbeddedIdFormat = "u{0}"; | ||
| 21 | public const string BurnUXContainerPayloadIdFormat = "p{0}"; | ||
| 22 | public const string BurnAttachedContainerEmbeddedIdFormat = "a{0}"; | ||
| 23 | |||
| 24 | // See WinNT.h for details about the PE format, including the | ||
| 25 | // structure and offsets for IMAGE_DOS_HEADER, IMAGE_NT_HEADERS32, | ||
| 26 | // IMAGE_FILE_HEADER, etc. | ||
| 27 | protected const UInt32 IMAGE_DOS_HEADER_SIZE = 64; | ||
| 28 | protected const UInt32 IMAGE_DOS_HEADER_OFFSET_MAGIC = 0; | ||
| 29 | protected const UInt32 IMAGE_DOS_HEADER_OFFSET_NTHEADER = 60; | ||
| 30 | |||
| 31 | protected const UInt32 IMAGE_NT_HEADER_SIZE = 24; // signature DWORD (4) + IMAGE_FILE_HEADER (20) | ||
| 32 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIGNATURE = 0; | ||
| 33 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS = 6; | ||
| 34 | protected const UInt32 IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER = 20; | ||
| 35 | |||
| 36 | protected const UInt32 IMAGE_OPTIONAL_OFFSET_CHECKSUM = 4 * 16; // checksum is 16 DWORDs into IMAGE_OPTIONAL_HEADER which is right after the IMAGE_NT_HEADER. | ||
| 37 | protected const UInt32 IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE = (IMAGE_DATA_DIRECTORY_SIZE * (IMAGE_NUMBEROF_DIRECTORY_ENTRIES - IMAGE_DIRECTORY_ENTRY_SECURITY)); | ||
| 38 | |||
| 39 | protected const UInt32 IMAGE_SECTION_HEADER_SIZE = 40; | ||
| 40 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_NAME = 0; | ||
| 41 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_VIRTUALSIZE = 8; | ||
| 42 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA = 16; | ||
| 43 | protected const UInt32 IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA = 20; | ||
| 44 | |||
| 45 | protected const UInt32 IMAGE_DATA_DIRECTORY_SIZE = 8; // struct of two DWORDs. | ||
| 46 | protected const UInt32 IMAGE_DIRECTORY_ENTRY_SECURITY = 4; | ||
| 47 | protected const UInt32 IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16; | ||
| 48 | |||
| 49 | protected const UInt16 IMAGE_DOS_SIGNATURE = 0x5A4D; | ||
| 50 | protected const UInt32 IMAGE_NT_SIGNATURE = 0x00004550; | ||
| 51 | protected const UInt64 IMAGE_SECTION_WIXBURN_NAME = 0x6E7275627869772E; // ".wixburn", as a qword. | ||
| 52 | |||
| 53 | // The ".wixburn" section contains: | ||
| 54 | // 0- 3: magic number | ||
| 55 | // 4- 7: version | ||
| 56 | // 8-23: bundle GUID | ||
| 57 | // 24-27: engine (stub) size | ||
| 58 | // 28-31: original checksum | ||
| 59 | // 32-35: original signature offset | ||
| 60 | // 36-39: original signature size | ||
| 61 | // 40-43: container type (1 = CAB) | ||
| 62 | // 44-47: container count | ||
| 63 | // 48-51: byte count of manifest + UX container | ||
| 64 | // 52-55: byte count of attached container | ||
| 65 | protected const UInt32 BURN_SECTION_OFFSET_MAGIC = 0; | ||
| 66 | protected const UInt32 BURN_SECTION_OFFSET_VERSION = 4; | ||
| 67 | protected const UInt32 BURN_SECTION_OFFSET_BUNDLEGUID = 8; | ||
| 68 | protected const UInt32 BURN_SECTION_OFFSET_STUBSIZE = 24; | ||
| 69 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALCHECKSUM = 28; | ||
| 70 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET = 32; | ||
| 71 | protected const UInt32 BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE = 36; | ||
| 72 | protected const UInt32 BURN_SECTION_OFFSET_FORMAT = 40; | ||
| 73 | protected const UInt32 BURN_SECTION_OFFSET_COUNT = 44; | ||
| 74 | protected const UInt32 BURN_SECTION_OFFSET_UXSIZE = 48; | ||
| 75 | protected const UInt32 BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE = 52; | ||
| 76 | protected const UInt32 BURN_SECTION_SIZE = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE + 4; // last field + sizeof(DWORD) | ||
| 77 | |||
| 78 | protected const UInt32 BURN_SECTION_MAGIC = 0x00f14300; | ||
| 79 | protected const UInt32 BURN_SECTION_VERSION = 0x00000002; | ||
| 80 | |||
| 81 | protected string fileExe; | ||
| 82 | protected UInt32 peOffset = UInt32.MaxValue; | ||
| 83 | protected UInt16 sections = UInt16.MaxValue; | ||
| 84 | protected UInt32 firstSectionOffset = UInt32.MaxValue; | ||
| 85 | protected UInt32 checksumOffset; | ||
| 86 | protected UInt32 certificateTableSignatureOffset; | ||
| 87 | protected UInt32 certificateTableSignatureSize; | ||
| 88 | protected UInt32 wixburnDataOffset = UInt32.MaxValue; | ||
| 89 | |||
| 90 | // TODO: does this enum exist in another form somewhere? | ||
| 91 | /// <summary> | ||
| 92 | /// The types of attached containers that BurnWriter supports. | ||
| 93 | /// </summary> | ||
| 94 | public enum Container | ||
| 95 | { | ||
| 96 | Nothing = 0, | ||
| 97 | UX, | ||
| 98 | Attached | ||
| 99 | } | ||
| 100 | |||
| 101 | /// <summary> | ||
| 102 | /// Creates a BurnCommon for re-writing a PE file. | ||
| 103 | /// </summary> | ||
| 104 | /// <param name="fileExe">File to modify in-place.</param> | ||
| 105 | /// <param name="bundleGuid">GUID for the bundle.</param> | ||
| 106 | public BurnCommon(string fileExe) | ||
| 107 | { | ||
| 108 | this.fileExe = fileExe; | ||
| 109 | } | ||
| 110 | |||
| 111 | public UInt32 Checksum { get; protected set; } | ||
| 112 | public UInt32 SignatureOffset { get; protected set; } | ||
| 113 | public UInt32 SignatureSize { get; protected set; } | ||
| 114 | public UInt32 Version { get; protected set; } | ||
| 115 | public UInt32 StubSize { get; protected set; } | ||
| 116 | public UInt32 OriginalChecksum { get; protected set; } | ||
| 117 | public UInt32 OriginalSignatureOffset { get; protected set; } | ||
| 118 | public UInt32 OriginalSignatureSize { get; protected set; } | ||
| 119 | public UInt32 EngineSize { get; protected set; } | ||
| 120 | public UInt32 ContainerCount { get; protected set; } | ||
| 121 | public UInt32 UXAddress { get; protected set; } | ||
| 122 | public UInt32 UXSize { get; protected set; } | ||
| 123 | public UInt32 AttachedContainerAddress { get; protected set; } | ||
| 124 | public UInt32 AttachedContainerSize { get; protected set; } | ||
| 125 | |||
| 126 | public void Dispose() | ||
| 127 | { | ||
| 128 | Dispose(true); | ||
| 129 | |||
| 130 | GC.SuppressFinalize(this); | ||
| 131 | } | ||
| 132 | |||
| 133 | /// <summary> | ||
| 134 | /// Copies one stream to another. | ||
| 135 | /// </summary> | ||
| 136 | /// <param name="input">Input stream.</param> | ||
| 137 | /// <param name="output">Output stream.</param> | ||
| 138 | /// <param name="size">Optional count of bytes to copy. 0 indicates whole input stream from current should be copied.</param> | ||
| 139 | protected static int CopyStream(Stream input, Stream output, int size) | ||
| 140 | { | ||
| 141 | byte[] bytes = new byte[4096]; | ||
| 142 | int total = 0; | ||
| 143 | int read = 0; | ||
| 144 | do | ||
| 145 | { | ||
| 146 | read = Math.Min(bytes.Length, size - total); | ||
| 147 | read = input.Read(bytes, 0, read); | ||
| 148 | if (0 == read) | ||
| 149 | { | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | |||
| 153 | output.Write(bytes, 0, read); | ||
| 154 | total += read; | ||
| 155 | } while (0 == size || total < size); | ||
| 156 | |||
| 157 | return total; | ||
| 158 | } | ||
| 159 | |||
| 160 | /// <summary> | ||
| 161 | /// Initialize the common information about a Burn engine. | ||
| 162 | /// </summary> | ||
| 163 | /// <param name="reader">Binary reader open against a Burn engine.</param> | ||
| 164 | /// <returns>True if initialized.</returns> | ||
| 165 | protected bool Initialize(BinaryReader reader) | ||
| 166 | { | ||
| 167 | if (!GetWixburnSectionInfo(reader)) | ||
| 168 | { | ||
| 169 | return false; | ||
| 170 | } | ||
| 171 | |||
| 172 | reader.BaseStream.Seek(this.wixburnDataOffset, SeekOrigin.Begin); | ||
| 173 | byte[] bytes = reader.ReadBytes((int)BURN_SECTION_SIZE); | ||
| 174 | UInt32 uint32 = 0; | ||
| 175 | |||
| 176 | uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_MAGIC); | ||
| 177 | if (BURN_SECTION_MAGIC != uint32) | ||
| 178 | { | ||
| 179 | Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); | ||
| 180 | return false; | ||
| 181 | } | ||
| 182 | |||
| 183 | this.Version = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_VERSION); | ||
| 184 | if (BURN_SECTION_VERSION != this.Version) | ||
| 185 | { | ||
| 186 | Messaging.Instance.OnMessage(WixErrors.BundleTooNew(this.fileExe, this.Version)); | ||
| 187 | return false; | ||
| 188 | } | ||
| 189 | |||
| 190 | uint32 = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_FORMAT); // We only know how to deal with CABs right now | ||
| 191 | if (1 != uint32) | ||
| 192 | { | ||
| 193 | Messaging.Instance.OnMessage(WixErrors.InvalidBundle(this.fileExe)); | ||
| 194 | return false; | ||
| 195 | } | ||
| 196 | |||
| 197 | this.StubSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_STUBSIZE); | ||
| 198 | this.OriginalChecksum = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALCHECKSUM); | ||
| 199 | this.OriginalSignatureOffset = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET); | ||
| 200 | this.OriginalSignatureSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE); | ||
| 201 | |||
| 202 | this.ContainerCount = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_COUNT); | ||
| 203 | this.UXAddress = this.StubSize; | ||
| 204 | this.UXSize = BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_UXSIZE); | ||
| 205 | |||
| 206 | // If there is an original signature use that to determine the engine size. | ||
| 207 | if (0 < this.OriginalSignatureOffset) | ||
| 208 | { | ||
| 209 | this.EngineSize = this.OriginalSignatureOffset + this.OriginalSignatureSize; | ||
| 210 | } | ||
| 211 | else if (0 < this.SignatureOffset && 2 > this.ContainerCount) // if there is a signature and no attached containers, use the current signature. | ||
| 212 | { | ||
| 213 | this.EngineSize = this.SignatureOffset + this.SignatureSize; | ||
| 214 | } | ||
| 215 | else // just use the stub and UX container as the size of the engine. | ||
| 216 | { | ||
| 217 | this.EngineSize = this.StubSize + this.UXSize; | ||
| 218 | } | ||
| 219 | |||
| 220 | this.AttachedContainerAddress = this.ContainerCount > 1 ? this.EngineSize : 0; | ||
| 221 | this.AttachedContainerSize = this.ContainerCount > 1 ? BurnCommon.ReadUInt32(bytes, BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE) : 0; | ||
| 222 | |||
| 223 | return true; | ||
| 224 | } | ||
| 225 | |||
| 226 | protected virtual void Dispose(bool disposing) | ||
| 227 | { | ||
| 228 | } | ||
| 229 | |||
| 230 | /// <summary> | ||
| 231 | /// Finds the ".wixburn" section in the current exe. | ||
| 232 | /// </summary> | ||
| 233 | /// <returns>true if the ".wixburn" section is successfully found; false otherwise</returns> | ||
| 234 | private bool GetWixburnSectionInfo(BinaryReader reader) | ||
| 235 | { | ||
| 236 | if (UInt32.MaxValue == this.wixburnDataOffset) | ||
| 237 | { | ||
| 238 | if (!EnsureNTHeader(reader)) | ||
| 239 | { | ||
| 240 | return false; | ||
| 241 | } | ||
| 242 | |||
| 243 | UInt32 wixburnSectionOffset = UInt32.MaxValue; | ||
| 244 | byte[] bytes = new byte[IMAGE_SECTION_HEADER_SIZE]; | ||
| 245 | |||
| 246 | reader.BaseStream.Seek(this.firstSectionOffset, SeekOrigin.Begin); | ||
| 247 | for (UInt16 sectionIndex = 0; sectionIndex < this.sections; ++sectionIndex) | ||
| 248 | { | ||
| 249 | reader.Read(bytes, 0, bytes.Length); | ||
| 250 | |||
| 251 | if (IMAGE_SECTION_WIXBURN_NAME == BurnCommon.ReadUInt64(bytes, IMAGE_SECTION_HEADER_OFFSET_NAME)) | ||
| 252 | { | ||
| 253 | wixburnSectionOffset = this.firstSectionOffset + (IMAGE_SECTION_HEADER_SIZE * sectionIndex); | ||
| 254 | break; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | if (UInt32.MaxValue == wixburnSectionOffset) | ||
| 259 | { | ||
| 260 | Messaging.Instance.OnMessage(WixErrors.StubMissingWixburnSection(this.fileExe)); | ||
| 261 | return false; | ||
| 262 | } | ||
| 263 | |||
| 264 | // we need 56 bytes for the manifest header, which is always going to fit in | ||
| 265 | // the smallest alignment (512 bytes), but just to be paranoid... | ||
| 266 | if (BURN_SECTION_SIZE > BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_SIZEOFRAWDATA)) | ||
| 267 | { | ||
| 268 | Messaging.Instance.OnMessage(WixErrors.StubWixburnSectionTooSmall(this.fileExe)); | ||
| 269 | return false; | ||
| 270 | } | ||
| 271 | |||
| 272 | this.wixburnDataOffset = BurnCommon.ReadUInt32(bytes, IMAGE_SECTION_HEADER_OFFSET_POINTERTORAWDATA); | ||
| 273 | } | ||
| 274 | |||
| 275 | return true; | ||
| 276 | } | ||
| 277 | |||
| 278 | /// <summary> | ||
| 279 | /// Checks for a valid Windows PE signature (IMAGE_NT_SIGNATURE) in the current exe. | ||
| 280 | /// </summary> | ||
| 281 | /// <returns>true if the exe is a Windows executable; false otherwise</returns> | ||
| 282 | private bool EnsureNTHeader(BinaryReader reader) | ||
| 283 | { | ||
| 284 | if (UInt32.MaxValue == this.firstSectionOffset) | ||
| 285 | { | ||
| 286 | if (!EnsureDosHeader(reader)) | ||
| 287 | { | ||
| 288 | return false; | ||
| 289 | } | ||
| 290 | |||
| 291 | reader.BaseStream.Seek(this.peOffset, SeekOrigin.Begin); | ||
| 292 | byte[] bytes = reader.ReadBytes((int)IMAGE_NT_HEADER_SIZE); | ||
| 293 | |||
| 294 | // Verify the NT signature... | ||
| 295 | if (IMAGE_NT_SIGNATURE != BurnCommon.ReadUInt32(bytes, IMAGE_NT_HEADER_OFFSET_SIGNATURE)) | ||
| 296 | { | ||
| 297 | Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); | ||
| 298 | return false; | ||
| 299 | } | ||
| 300 | |||
| 301 | ushort sizeOptionalHeader = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_SIZEOFOPTIONALHEADER); | ||
| 302 | |||
| 303 | this.sections = BurnCommon.ReadUInt16(bytes, IMAGE_NT_HEADER_OFFSET_NUMBEROFSECTIONS); | ||
| 304 | this.firstSectionOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader; | ||
| 305 | |||
| 306 | this.checksumOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + IMAGE_OPTIONAL_OFFSET_CHECKSUM; | ||
| 307 | this.certificateTableSignatureOffset = this.peOffset + IMAGE_NT_HEADER_SIZE + sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE; | ||
| 308 | this.certificateTableSignatureSize = this.certificateTableSignatureOffset + 4; // size is in the DWORD after the offset. | ||
| 309 | |||
| 310 | bytes = reader.ReadBytes(sizeOptionalHeader); | ||
| 311 | this.Checksum = BurnCommon.ReadUInt32(bytes, IMAGE_OPTIONAL_OFFSET_CHECKSUM); | ||
| 312 | this.SignatureOffset = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE); | ||
| 313 | this.SignatureSize = BurnCommon.ReadUInt32(bytes, sizeOptionalHeader - IMAGE_OPTIONAL_NEGATIVE_OFFSET_CERTIFICATETABLE + 4); | ||
| 314 | } | ||
| 315 | |||
| 316 | return true; | ||
| 317 | } | ||
| 318 | |||
| 319 | /// <summary> | ||
| 320 | /// Checks for a valid DOS header in the current exe. | ||
| 321 | /// </summary> | ||
| 322 | /// <returns>true if the exe starts with a DOS stub; false otherwise</returns> | ||
| 323 | private bool EnsureDosHeader(BinaryReader reader) | ||
| 324 | { | ||
| 325 | if (UInt32.MaxValue == this.peOffset) | ||
| 326 | { | ||
| 327 | byte[] bytes = reader.ReadBytes((int)IMAGE_DOS_HEADER_SIZE); | ||
| 328 | |||
| 329 | // Verify the DOS 'MZ' signature. | ||
| 330 | if (IMAGE_DOS_SIGNATURE != BurnCommon.ReadUInt16(bytes, IMAGE_DOS_HEADER_OFFSET_MAGIC)) | ||
| 331 | { | ||
| 332 | Messaging.Instance.OnMessage(WixErrors.InvalidStubExe(this.fileExe)); | ||
| 333 | return false; | ||
| 334 | } | ||
| 335 | |||
| 336 | this.peOffset = BurnCommon.ReadUInt32(bytes, IMAGE_DOS_HEADER_OFFSET_NTHEADER); | ||
| 337 | } | ||
| 338 | |||
| 339 | return true; | ||
| 340 | } | ||
| 341 | |||
| 342 | /// <summary> | ||
| 343 | /// Reads a UInt16 value in little-endian format from an offset in an array of bytes. | ||
| 344 | /// </summary> | ||
| 345 | /// <param name="bytes">Array from which to read.</param> | ||
| 346 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 347 | /// <returns>value at offset</returns> | ||
| 348 | private static UInt16 ReadUInt16(byte[] bytes, UInt32 offset) | ||
| 349 | { | ||
| 350 | Debug.Assert(offset + 2 <= bytes.Length); | ||
| 351 | return (UInt16)(bytes[offset] + (bytes[offset + 1] << 8)); | ||
| 352 | } | ||
| 353 | |||
| 354 | /// <summary> | ||
| 355 | /// Reads a UInt32 value in little-endian format from an offset in an array of bytes. | ||
| 356 | /// </summary> | ||
| 357 | /// <param name="bytes">Array from which to read.</param> | ||
| 358 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 359 | /// <returns>value at offset</returns> | ||
| 360 | private static UInt32 ReadUInt32(byte[] bytes, UInt32 offset) | ||
| 361 | { | ||
| 362 | Debug.Assert(offset + 4 <= bytes.Length); | ||
| 363 | return (UInt32)(bytes[offset] + (bytes[offset + 1] << 8) + (bytes[offset + 2] << 16) + (bytes[offset + 3] << 24)); | ||
| 364 | } | ||
| 365 | |||
| 366 | /// <summary> | ||
| 367 | /// Reads a UInt64 value in little-endian format from an offset in an array of bytes. | ||
| 368 | /// </summary> | ||
| 369 | /// <param name="bytes">Array from which to read.</param> | ||
| 370 | /// <param name="offset">Beginning offset from which to read.</param> | ||
| 371 | /// <returns>value at offset</returns> | ||
| 372 | private static UInt64 ReadUInt64(byte[] bytes, UInt32 offset) | ||
| 373 | { | ||
| 374 | Debug.Assert(offset + 8 <= bytes.Length); | ||
| 375 | return BurnCommon.ReadUInt32(bytes, offset) + ((UInt64)(BurnCommon.ReadUInt32(bytes, offset + 4)) << 32); | ||
| 376 | } | ||
| 377 | } | ||
| 378 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/WixToolset.Core.Burn/Bundles/BurnReader.cs new file mode 100644 index 00000000..261ef7b4 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/BurnReader.cs | |||
| @@ -0,0 +1,220 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.IO; | ||
| 9 | using System.Xml; | ||
| 10 | using WixToolset.Core.Cab; | ||
| 11 | |||
| 12 | /// <summary> | ||
| 13 | /// Burn PE reader for the WiX toolset. | ||
| 14 | /// </summary> | ||
| 15 | /// <remarks>This class encapsulates reading from a stub EXE with containers attached | ||
| 16 | /// for dissecting bundled/chained setup packages.</remarks> | ||
| 17 | /// <example> | ||
| 18 | /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) | ||
| 19 | /// { | ||
| 20 | /// reader.ExtractUXContainer(file1, tempFolder); | ||
| 21 | /// } | ||
| 22 | /// </example> | ||
| 23 | internal class BurnReader : BurnCommon | ||
| 24 | { | ||
| 25 | private bool disposed; | ||
| 26 | |||
| 27 | private bool invalidBundle; | ||
| 28 | private BinaryReader binaryReader; | ||
| 29 | private List<DictionaryEntry> attachedContainerPayloadNames; | ||
| 30 | |||
| 31 | /// <summary> | ||
| 32 | /// Creates a BurnReader for reading a PE file. | ||
| 33 | /// </summary> | ||
| 34 | /// <param name="fileExe">File to read.</param> | ||
| 35 | private BurnReader(string fileExe) | ||
| 36 | : base(fileExe) | ||
| 37 | { | ||
| 38 | this.attachedContainerPayloadNames = new List<DictionaryEntry>(); | ||
| 39 | } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Gets the underlying stream. | ||
| 43 | /// </summary> | ||
| 44 | public Stream Stream | ||
| 45 | { | ||
| 46 | get | ||
| 47 | { | ||
| 48 | return (null != this.binaryReader) ? this.binaryReader.BaseStream : null; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | internal static BurnReader Open(object inputFilePath) | ||
| 53 | { | ||
| 54 | throw new NotImplementedException(); | ||
| 55 | } | ||
| 56 | |||
| 57 | /// <summary> | ||
| 58 | /// Opens a Burn reader. | ||
| 59 | /// </summary> | ||
| 60 | /// <param name="fileExe">Path to file.</param> | ||
| 61 | /// <returns>Burn reader.</returns> | ||
| 62 | public static BurnReader Open(string fileExe) | ||
| 63 | { | ||
| 64 | BurnReader reader = new BurnReader(fileExe); | ||
| 65 | |||
| 66 | reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); | ||
| 67 | if (!reader.Initialize(reader.binaryReader)) | ||
| 68 | { | ||
| 69 | reader.invalidBundle = true; | ||
| 70 | } | ||
| 71 | |||
| 72 | return reader; | ||
| 73 | } | ||
| 74 | |||
| 75 | /// <summary> | ||
| 76 | /// Gets the UX container from the exe and extracts its contents to the output directory. | ||
| 77 | /// </summary> | ||
| 78 | /// <param name="outputDirectory">Directory to write extracted files to.</param> | ||
| 79 | /// <returns>True if successful, false otherwise</returns> | ||
| 80 | public bool ExtractUXContainer(string outputDirectory, string tempDirectory) | ||
| 81 | { | ||
| 82 | // No UX container to extract | ||
| 83 | if (this.UXAddress == 0 || this.UXSize == 0) | ||
| 84 | { | ||
| 85 | return false; | ||
| 86 | } | ||
| 87 | |||
| 88 | if (this.invalidBundle) | ||
| 89 | { | ||
| 90 | return false; | ||
| 91 | } | ||
| 92 | |||
| 93 | Directory.CreateDirectory(outputDirectory); | ||
| 94 | string tempCabPath = Path.Combine(tempDirectory, "ux.cab"); | ||
| 95 | string manifestOriginalPath = Path.Combine(outputDirectory, "0"); | ||
| 96 | string manifestPath = Path.Combine(outputDirectory, "manifest.xml"); | ||
| 97 | |||
| 98 | this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); | ||
| 99 | using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) | ||
| 100 | { | ||
| 101 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize); | ||
| 102 | } | ||
| 103 | |||
| 104 | using (var extract = new WixExtractCab()) | ||
| 105 | { | ||
| 106 | extract.Extract(tempCabPath, outputDirectory); | ||
| 107 | } | ||
| 108 | |||
| 109 | Directory.CreateDirectory(Path.GetDirectoryName(manifestPath)); | ||
| 110 | File.Delete(manifestPath); | ||
| 111 | File.Move(manifestOriginalPath, manifestPath); | ||
| 112 | |||
| 113 | XmlDocument document = new XmlDocument(); | ||
| 114 | document.Load(manifestPath); | ||
| 115 | XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable); | ||
| 116 | namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace); | ||
| 117 | XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); | ||
| 118 | XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); | ||
| 119 | |||
| 120 | foreach (XmlNode uxPayload in uxPayloads) | ||
| 121 | { | ||
| 122 | XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); | ||
| 123 | XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); | ||
| 124 | |||
| 125 | string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); | ||
| 126 | string destinationPath = Path.Combine(outputDirectory, filePathNode.Value); | ||
| 127 | |||
| 128 | Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); | ||
| 129 | File.Delete(destinationPath); | ||
| 130 | File.Move(sourcePath, destinationPath); | ||
| 131 | } | ||
| 132 | |||
| 133 | foreach (XmlNode payload in payloads) | ||
| 134 | { | ||
| 135 | XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); | ||
| 136 | XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath"); | ||
| 137 | XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging"); | ||
| 138 | |||
| 139 | string sourcePath = sourcePathNode.Value; | ||
| 140 | string destinationPath = filePathNode.Value; | ||
| 141 | string packaging = packagingNode.Value; | ||
| 142 | |||
| 143 | if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) | ||
| 144 | { | ||
| 145 | this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | return true; | ||
| 150 | } | ||
| 151 | |||
| 152 | internal void ExtractUXContainer(string uxExtractPath, object intermediateFolder) | ||
| 153 | { | ||
| 154 | throw new NotImplementedException(); | ||
| 155 | } | ||
| 156 | |||
| 157 | /// <summary> | ||
| 158 | /// Gets the attached container from the exe and extracts its contents to the output directory. | ||
| 159 | /// </summary> | ||
| 160 | /// <param name="outputDirectory">Directory to write extracted files to.</param> | ||
| 161 | /// <returns>True if successful, false otherwise</returns> | ||
| 162 | public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory) | ||
| 163 | { | ||
| 164 | // No attached container to extract | ||
| 165 | if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0) | ||
| 166 | { | ||
| 167 | return false; | ||
| 168 | } | ||
| 169 | |||
| 170 | if (this.invalidBundle) | ||
| 171 | { | ||
| 172 | return false; | ||
| 173 | } | ||
| 174 | |||
| 175 | Directory.CreateDirectory(outputDirectory); | ||
| 176 | string tempCabPath = Path.Combine(tempDirectory, "attached.cab"); | ||
| 177 | |||
| 178 | this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin); | ||
| 179 | using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write)) | ||
| 180 | { | ||
| 181 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize); | ||
| 182 | } | ||
| 183 | |||
| 184 | using (WixExtractCab extract = new WixExtractCab()) | ||
| 185 | { | ||
| 186 | extract.Extract(tempCabPath, outputDirectory); | ||
| 187 | } | ||
| 188 | |||
| 189 | foreach (DictionaryEntry entry in this.attachedContainerPayloadNames) | ||
| 190 | { | ||
| 191 | string sourcePath = Path.Combine(outputDirectory, (string)entry.Key); | ||
| 192 | string destinationPath = Path.Combine(outputDirectory, (string)entry.Value); | ||
| 193 | |||
| 194 | Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); | ||
| 195 | File.Delete(destinationPath); | ||
| 196 | File.Move(sourcePath, destinationPath); | ||
| 197 | } | ||
| 198 | |||
| 199 | return true; | ||
| 200 | } | ||
| 201 | |||
| 202 | /// <summary> | ||
| 203 | /// Dispose object. | ||
| 204 | /// </summary> | ||
| 205 | /// <param name="disposing">True when releasing managed objects.</param> | ||
| 206 | protected override void Dispose(bool disposing) | ||
| 207 | { | ||
| 208 | if (!this.disposed) | ||
| 209 | { | ||
| 210 | if (disposing && this.binaryReader != null) | ||
| 211 | { | ||
| 212 | this.binaryReader.Close(); | ||
| 213 | this.binaryReader = null; | ||
| 214 | } | ||
| 215 | |||
| 216 | this.disposed = true; | ||
| 217 | } | ||
| 218 | } | ||
| 219 | } | ||
| 220 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs b/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs new file mode 100644 index 00000000..e7365212 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/BurnWriter.cs | |||
| @@ -0,0 +1,239 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Diagnostics; | ||
| 7 | using System.IO; | ||
| 8 | using WixToolset.Data; | ||
| 9 | |||
| 10 | /// <summary> | ||
| 11 | /// Burn PE writer for the WiX toolset. | ||
| 12 | /// </summary> | ||
| 13 | /// <remarks>This class encapsulates reading/writing to a stub EXE for | ||
| 14 | /// creating bundled/chained setup packages.</remarks> | ||
| 15 | /// <example> | ||
| 16 | /// using (BurnWriter writer = new BurnWriter(fileExe, this.core, guid)) | ||
| 17 | /// { | ||
| 18 | /// writer.AppendContainer(file1, BurnWriter.Container.UX); | ||
| 19 | /// writer.AppendContainer(file2, BurnWriter.Container.Attached); | ||
| 20 | /// } | ||
| 21 | /// </example> | ||
| 22 | internal class BurnWriter : BurnCommon | ||
| 23 | { | ||
| 24 | private bool disposed; | ||
| 25 | private bool invalidBundle; | ||
| 26 | private BinaryWriter binaryWriter; | ||
| 27 | |||
| 28 | /// <summary> | ||
| 29 | /// Creates a BurnWriter for re-writing a PE file. | ||
| 30 | /// </summary> | ||
| 31 | /// <param name="fileExe">File to modify in-place.</param> | ||
| 32 | /// <param name="bundleGuid">GUID for the bundle.</param> | ||
| 33 | private BurnWriter(string fileExe) | ||
| 34 | : base(fileExe) | ||
| 35 | { | ||
| 36 | } | ||
| 37 | |||
| 38 | /// <summary> | ||
| 39 | /// Opens a Burn writer. | ||
| 40 | /// </summary> | ||
| 41 | /// <param name="fileExe">Path to file.</param> | ||
| 42 | /// <returns>Burn writer.</returns> | ||
| 43 | public static BurnWriter Open(string fileExe) | ||
| 44 | { | ||
| 45 | BurnWriter writer = new BurnWriter(fileExe); | ||
| 46 | |||
| 47 | using (BinaryReader binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))) | ||
| 48 | { | ||
| 49 | if (!writer.Initialize(binaryReader)) | ||
| 50 | { | ||
| 51 | writer.invalidBundle = true; | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | if (!writer.invalidBundle) | ||
| 56 | { | ||
| 57 | writer.binaryWriter = new BinaryWriter(File.Open(fileExe, FileMode.Open, FileAccess.ReadWrite, FileShare.Read | FileShare.Delete)); | ||
| 58 | } | ||
| 59 | |||
| 60 | return writer; | ||
| 61 | } | ||
| 62 | |||
| 63 | /// <summary> | ||
| 64 | /// Update the ".wixburn" section data. | ||
| 65 | /// </summary> | ||
| 66 | /// <param name="stubSize">Size of the stub engine "burn.exe".</param> | ||
| 67 | /// <param name="bundleId">Unique identifier for this bundle.</param> | ||
| 68 | /// <returns></returns> | ||
| 69 | public bool InitializeBundleSectionData(long stubSize, Guid bundleId) | ||
| 70 | { | ||
| 71 | if (this.invalidBundle) | ||
| 72 | { | ||
| 73 | return false; | ||
| 74 | } | ||
| 75 | |||
| 76 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_MAGIC, BURN_SECTION_MAGIC); | ||
| 77 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_VERSION, BURN_SECTION_VERSION); | ||
| 78 | |||
| 79 | Messaging.Instance.OnMessage(WixVerboses.BundleGuid(bundleId.ToString("B"))); | ||
| 80 | this.binaryWriter.BaseStream.Seek(this.wixburnDataOffset + BURN_SECTION_OFFSET_BUNDLEGUID, SeekOrigin.Begin); | ||
| 81 | this.binaryWriter.Write(bundleId.ToByteArray()); | ||
| 82 | |||
| 83 | this.StubSize = (uint)stubSize; | ||
| 84 | |||
| 85 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_STUBSIZE, this.StubSize); | ||
| 86 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, 0); | ||
| 87 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, 0); | ||
| 88 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, 0); | ||
| 89 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_FORMAT, 1); // Hard-coded to CAB for now. | ||
| 90 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, 0); | ||
| 91 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_UXSIZE, 0); | ||
| 92 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE, 0); | ||
| 93 | this.binaryWriter.BaseStream.Flush(); | ||
| 94 | |||
| 95 | this.EngineSize = this.StubSize; | ||
| 96 | |||
| 97 | return true; | ||
| 98 | } | ||
| 99 | |||
| 100 | /// <summary> | ||
| 101 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. | ||
| 102 | /// </summary> | ||
| 103 | /// <param name="fileContainer">File path to append to the current exe.</param> | ||
| 104 | /// <param name="container">Container section represented by the fileContainer.</param> | ||
| 105 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 106 | public bool AppendContainer(string fileContainer, BurnCommon.Container container) | ||
| 107 | { | ||
| 108 | using (FileStream reader = File.OpenRead(fileContainer)) | ||
| 109 | { | ||
| 110 | return this.AppendContainer(reader, reader.Length, container); | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | /// <summary> | ||
| 115 | /// Appends a UX or Attached container to the exe and updates the ".wixburn" section data to point to it. | ||
| 116 | /// </summary> | ||
| 117 | /// <param name="containerStream">File stream to append to the current exe.</param> | ||
| 118 | /// <param name="containerSize">Size of container to append.</param> | ||
| 119 | /// <param name="container">Container section represented by the fileContainer.</param> | ||
| 120 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 121 | public bool AppendContainer(Stream containerStream, long containerSize, BurnCommon.Container container) | ||
| 122 | { | ||
| 123 | UInt32 burnSectionCount = 0; | ||
| 124 | UInt32 burnSectionOffsetSize = 0; | ||
| 125 | |||
| 126 | switch (container) | ||
| 127 | { | ||
| 128 | case Container.UX: | ||
| 129 | burnSectionCount = 1; | ||
| 130 | burnSectionOffsetSize = BURN_SECTION_OFFSET_UXSIZE; | ||
| 131 | // TODO: verify that the size in the section data is 0 or the same size. | ||
| 132 | this.EngineSize += (uint)containerSize; | ||
| 133 | this.UXSize = (uint)containerSize; | ||
| 134 | break; | ||
| 135 | |||
| 136 | case Container.Attached: | ||
| 137 | burnSectionCount = 2; | ||
| 138 | burnSectionOffsetSize = BURN_SECTION_OFFSET_ATTACHEDCONTAINERSIZE; | ||
| 139 | // TODO: verify that the size in the section data is 0 or the same size. | ||
| 140 | this.AttachedContainerSize = (uint)containerSize; | ||
| 141 | break; | ||
| 142 | |||
| 143 | default: | ||
| 144 | Debug.Assert(false); | ||
| 145 | return false; | ||
| 146 | } | ||
| 147 | |||
| 148 | return AppendContainer(containerStream, (UInt32)containerSize, burnSectionOffsetSize, burnSectionCount); | ||
| 149 | } | ||
| 150 | |||
| 151 | public void RememberThenResetSignature() | ||
| 152 | { | ||
| 153 | if (this.invalidBundle) | ||
| 154 | { | ||
| 155 | return; | ||
| 156 | } | ||
| 157 | |||
| 158 | this.OriginalChecksum = this.Checksum; | ||
| 159 | this.OriginalSignatureOffset = this.SignatureOffset; | ||
| 160 | this.OriginalSignatureSize = this.SignatureSize; | ||
| 161 | |||
| 162 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALCHECKSUM, this.OriginalChecksum); | ||
| 163 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATUREOFFSET, this.OriginalSignatureOffset); | ||
| 164 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_ORIGINALSIGNATURESIZE, this.OriginalSignatureSize); | ||
| 165 | |||
| 166 | this.Checksum = 0; | ||
| 167 | this.SignatureOffset = 0; | ||
| 168 | this.SignatureSize = 0; | ||
| 169 | |||
| 170 | this.WriteToOffset(this.checksumOffset, this.Checksum); | ||
| 171 | this.WriteToOffset(this.certificateTableSignatureOffset, this.SignatureOffset); | ||
| 172 | this.WriteToOffset(this.certificateTableSignatureSize, this.SignatureSize); | ||
| 173 | } | ||
| 174 | |||
| 175 | /// <summary> | ||
| 176 | /// Dispose object. | ||
| 177 | /// </summary> | ||
| 178 | /// <param name="disposing">True when releasing managed objects.</param> | ||
| 179 | protected override void Dispose(bool disposing) | ||
| 180 | { | ||
| 181 | if (!this.disposed) | ||
| 182 | { | ||
| 183 | if (disposing && this.binaryWriter != null) | ||
| 184 | { | ||
| 185 | this.binaryWriter.Close(); | ||
| 186 | this.binaryWriter = null; | ||
| 187 | } | ||
| 188 | |||
| 189 | this.disposed = true; | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | /// <summary> | ||
| 194 | /// Appends a container to the exe and updates the ".wixburn" section data to point to it. | ||
| 195 | /// </summary> | ||
| 196 | /// <param name="containerStream">File stream to append to the current exe.</param> | ||
| 197 | /// <param name="burnSectionOffsetSize">Offset of size field for this container in ".wixburn" section data.</param> | ||
| 198 | /// <returns>true if the container data is successfully appended; false otherwise</returns> | ||
| 199 | private bool AppendContainer(Stream containerStream, UInt32 containerSize, UInt32 burnSectionOffsetSize, UInt32 burnSectionCount) | ||
| 200 | { | ||
| 201 | if (this.invalidBundle) | ||
| 202 | { | ||
| 203 | return false; | ||
| 204 | } | ||
| 205 | |||
| 206 | // Update the ".wixburn" section data | ||
| 207 | this.WriteToBurnSectionOffset(BURN_SECTION_OFFSET_COUNT, burnSectionCount); | ||
| 208 | this.WriteToBurnSectionOffset(burnSectionOffsetSize, containerSize); | ||
| 209 | |||
| 210 | // Append the container to the end of the existing bits. | ||
| 211 | this.binaryWriter.BaseStream.Seek(0, SeekOrigin.End); | ||
| 212 | BurnCommon.CopyStream(containerStream, this.binaryWriter.BaseStream, (int)containerSize); | ||
| 213 | this.binaryWriter.BaseStream.Flush(); | ||
| 214 | |||
| 215 | return true; | ||
| 216 | } | ||
| 217 | |||
| 218 | /// <summary> | ||
| 219 | /// Writes the value to an offset in the Burn section data. | ||
| 220 | /// </summary> | ||
| 221 | /// <param name="offset">Offset in to the Burn section data.</param> | ||
| 222 | /// <param name="value">Value to write.</param> | ||
| 223 | private void WriteToBurnSectionOffset(uint offset, uint value) | ||
| 224 | { | ||
| 225 | this.WriteToOffset(this.wixburnDataOffset + offset, value); | ||
| 226 | } | ||
| 227 | |||
| 228 | /// <summary> | ||
| 229 | /// Writes the value to an offset in the Burn stub. | ||
| 230 | /// </summary> | ||
| 231 | /// <param name="offset">Offset in to the Burn stub.</param> | ||
| 232 | /// <param name="value">Value to write.</param> | ||
| 233 | private void WriteToOffset(uint offset, uint value) | ||
| 234 | { | ||
| 235 | this.binaryWriter.BaseStream.Seek((int)offset, SeekOrigin.Begin); | ||
| 236 | this.binaryWriter.Write(value); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs new file mode 100644 index 00000000..58814efc --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs | |||
| @@ -0,0 +1,241 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | |||
| 15 | internal class CreateBootstrapperApplicationManifestCommand | ||
| 16 | { | ||
| 17 | public WixBundleRow BundleRow { private get; set; } | ||
| 18 | |||
| 19 | public IEnumerable<PackageFacade> ChainPackages { private get; set; } | ||
| 20 | |||
| 21 | public int LastUXPayloadIndex { private get; set; } | ||
| 22 | |||
| 23 | public IEnumerable<WixBundleMsiFeatureRow> MsiFeatures { private get; set; } | ||
| 24 | |||
| 25 | public Output Output { private get; set; } | ||
| 26 | |||
| 27 | public RowDictionary<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 28 | |||
| 29 | public TableDefinitionCollection TableDefinitions { private get; set; } | ||
| 30 | |||
| 31 | public string TempFilesLocation { private get; set; } | ||
| 32 | |||
| 33 | public WixBundlePayloadRow BootstrapperApplicationManifestPayloadRow { get; private set; } | ||
| 34 | |||
| 35 | public void Execute() | ||
| 36 | { | ||
| 37 | this.GenerateBAManifestBundleTables(); | ||
| 38 | |||
| 39 | this.GenerateBAManifestMsiFeatureTables(); | ||
| 40 | |||
| 41 | this.GenerateBAManifestPackageTables(); | ||
| 42 | |||
| 43 | this.GenerateBAManifestPayloadTables(); | ||
| 44 | |||
| 45 | string baManifestPath = Path.Combine(this.TempFilesLocation, "wix-badata.xml"); | ||
| 46 | |||
| 47 | this.CreateBootstrapperApplicationManifest(baManifestPath); | ||
| 48 | |||
| 49 | this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(baManifestPath); | ||
| 50 | } | ||
| 51 | |||
| 52 | private void GenerateBAManifestBundleTables() | ||
| 53 | { | ||
| 54 | Table wixBundlePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleProperties"]); | ||
| 55 | |||
| 56 | Row row = wixBundlePropertiesTable.CreateRow(this.BundleRow.SourceLineNumbers); | ||
| 57 | row[0] = this.BundleRow.Name; | ||
| 58 | row[1] = this.BundleRow.LogPathVariable; | ||
| 59 | row[2] = (YesNoDefaultType.Yes == this.BundleRow.Compressed) ? "yes" : "no"; | ||
| 60 | row[3] = this.BundleRow.BundleId.ToString("B"); | ||
| 61 | row[4] = this.BundleRow.UpgradeCode; | ||
| 62 | row[5] = this.BundleRow.PerMachine ? "yes" : "no"; | ||
| 63 | } | ||
| 64 | |||
| 65 | private void GenerateBAManifestPackageTables() | ||
| 66 | { | ||
| 67 | Table wixPackagePropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageProperties"]); | ||
| 68 | |||
| 69 | foreach (PackageFacade package in this.ChainPackages) | ||
| 70 | { | ||
| 71 | WixBundlePayloadRow packagePayload = this.Payloads[package.Package.PackagePayload]; | ||
| 72 | |||
| 73 | Row row = wixPackagePropertiesTable.CreateRow(package.Package.SourceLineNumbers); | ||
| 74 | row[0] = package.Package.WixChainItemId; | ||
| 75 | row[1] = (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"; | ||
| 76 | row[2] = package.Package.DisplayName; | ||
| 77 | row[3] = package.Package.Description; | ||
| 78 | row[4] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?) | ||
| 79 | row[5] = package.Package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed) | ||
| 80 | row[6] = package.Package.InstallSize.Value.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space) | ||
| 81 | row[7] = package.Package.Type.ToString(); | ||
| 82 | row[8] = package.Package.Permanent ? "yes" : "no"; | ||
| 83 | row[9] = package.Package.LogPathVariable; | ||
| 84 | row[10] = package.Package.RollbackLogPathVariable; | ||
| 85 | row[11] = (PackagingType.Embedded == packagePayload.Packaging) ? "yes" : "no"; | ||
| 86 | |||
| 87 | if (WixBundlePackageType.Msi == package.Package.Type) | ||
| 88 | { | ||
| 89 | row[12] = package.MsiPackage.DisplayInternalUI ? "yes" : "no"; | ||
| 90 | |||
| 91 | if (!String.IsNullOrEmpty(package.MsiPackage.ProductCode)) | ||
| 92 | { | ||
| 93 | row[13] = package.MsiPackage.ProductCode; | ||
| 94 | } | ||
| 95 | |||
| 96 | if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) | ||
| 97 | { | ||
| 98 | row[14] = package.MsiPackage.UpgradeCode; | ||
| 99 | } | ||
| 100 | } | ||
| 101 | else if (WixBundlePackageType.Msp == package.Package.Type) | ||
| 102 | { | ||
| 103 | row[12] = package.MspPackage.DisplayInternalUI ? "yes" : "no"; | ||
| 104 | |||
| 105 | if (!String.IsNullOrEmpty(package.MspPackage.PatchCode)) | ||
| 106 | { | ||
| 107 | row[13] = package.MspPackage.PatchCode; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | if (!String.IsNullOrEmpty(package.Package.Version)) | ||
| 112 | { | ||
| 113 | row[15] = package.Package.Version; | ||
| 114 | } | ||
| 115 | |||
| 116 | if (!String.IsNullOrEmpty(package.Package.InstallCondition)) | ||
| 117 | { | ||
| 118 | row[16] = package.Package.InstallCondition; | ||
| 119 | } | ||
| 120 | |||
| 121 | switch (package.Package.Cache) | ||
| 122 | { | ||
| 123 | case YesNoAlwaysType.No: | ||
| 124 | row[17] = "no"; | ||
| 125 | break; | ||
| 126 | case YesNoAlwaysType.Yes: | ||
| 127 | row[17] = "yes"; | ||
| 128 | break; | ||
| 129 | case YesNoAlwaysType.Always: | ||
| 130 | row[17] = "always"; | ||
| 131 | break; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | private void GenerateBAManifestMsiFeatureTables() | ||
| 137 | { | ||
| 138 | Table wixPackageFeatureInfoTable = this.Output.EnsureTable(this.TableDefinitions["WixPackageFeatureInfo"]); | ||
| 139 | |||
| 140 | foreach (WixBundleMsiFeatureRow feature in this.MsiFeatures) | ||
| 141 | { | ||
| 142 | Row row = wixPackageFeatureInfoTable.CreateRow(feature.SourceLineNumbers); | ||
| 143 | row[0] = feature.ChainPackageId; | ||
| 144 | row[1] = feature.Name; | ||
| 145 | row[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture); | ||
| 146 | row[3] = feature.Parent; | ||
| 147 | row[4] = feature.Title; | ||
| 148 | row[5] = feature.Description; | ||
| 149 | row[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture); | ||
| 150 | row[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture); | ||
| 151 | row[8] = feature.Directory; | ||
| 152 | row[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture); | ||
| 153 | } | ||
| 154 | |||
| 155 | } | ||
| 156 | |||
| 157 | private void GenerateBAManifestPayloadTables() | ||
| 158 | { | ||
| 159 | Table wixPayloadPropertiesTable = this.Output.EnsureTable(this.TableDefinitions["WixPayloadProperties"]); | ||
| 160 | |||
| 161 | foreach (WixBundlePayloadRow payload in this.Payloads.Values) | ||
| 162 | { | ||
| 163 | WixPayloadPropertiesRow row = (WixPayloadPropertiesRow)wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers); | ||
| 164 | row.Id = payload.Id; | ||
| 165 | row.Package = payload.Package; | ||
| 166 | row.Container = payload.Container; | ||
| 167 | row.Name = payload.Name; | ||
| 168 | row.Size = payload.FileSize.ToString(); | ||
| 169 | row.DownloadUrl = payload.DownloadUrl; | ||
| 170 | row.LayoutOnly = payload.LayoutOnly ? "yes" : "no"; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | private void CreateBootstrapperApplicationManifest(string path) | ||
| 175 | { | ||
| 176 | using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.Unicode)) | ||
| 177 | { | ||
| 178 | writer.Formatting = Formatting.Indented; | ||
| 179 | writer.WriteStartDocument(); | ||
| 180 | writer.WriteStartElement("BootstrapperApplicationData", "http://wixtoolset.org/schemas/v4/2010/BootstrapperApplicationData"); | ||
| 181 | |||
| 182 | foreach (Table table in this.Output.Tables) | ||
| 183 | { | ||
| 184 | if (table.Definition.BootstrapperApplicationData) | ||
| 185 | { | ||
| 186 | // We simply assert that the table (and field) name is valid, because | ||
| 187 | // this is up to the extension developer to get right. An author will | ||
| 188 | // only affect the attribute value, and that will get properly escaped. | ||
| 189 | #if DEBUG | ||
| 190 | Debug.Assert(Common.IsIdentifier(table.Name)); | ||
| 191 | foreach (ColumnDefinition column in table.Definition.Columns) | ||
| 192 | { | ||
| 193 | Debug.Assert(Common.IsIdentifier(column.Name)); | ||
| 194 | } | ||
| 195 | #endif // DEBUG | ||
| 196 | |||
| 197 | foreach (Row row in table.Rows) | ||
| 198 | { | ||
| 199 | writer.WriteStartElement(table.Name); | ||
| 200 | |||
| 201 | foreach (Field field in row.Fields) | ||
| 202 | { | ||
| 203 | if (null != field.Data) | ||
| 204 | { | ||
| 205 | writer.WriteAttributeString(field.Column.Name, field.Data.ToString()); | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | writer.WriteEndElement(); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | } | ||
| 213 | |||
| 214 | writer.WriteEndElement(); | ||
| 215 | writer.WriteEndDocument(); | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | private WixBundlePayloadRow CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) | ||
| 220 | { | ||
| 221 | Table payloadTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePayload"]); | ||
| 222 | WixBundlePayloadRow row = (WixBundlePayloadRow)payloadTable.CreateRow(this.BundleRow.SourceLineNumbers); | ||
| 223 | row.Id = Common.GenerateIdentifier("ux", "BootstrapperApplicationData.xml"); | ||
| 224 | row.Name = "BootstrapperApplicationData.xml"; | ||
| 225 | row.SourceFile = baManifestPath; | ||
| 226 | row.Compressed = YesNoDefaultType.Yes; | ||
| 227 | row.UnresolvedSourceFile = baManifestPath; | ||
| 228 | row.Container = Compiler.BurnUXContainerId; | ||
| 229 | row.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex); | ||
| 230 | row.Packaging = PackagingType.Embedded; | ||
| 231 | |||
| 232 | FileInfo fileInfo = new FileInfo(row.SourceFile); | ||
| 233 | |||
| 234 | row.FileSize = (int)fileInfo.Length; | ||
| 235 | |||
| 236 | row.Hash = Common.GetFileHash(fileInfo.FullName); | ||
| 237 | |||
| 238 | return row; | ||
| 239 | } | ||
| 240 | } | ||
| 241 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs new file mode 100644 index 00000000..772265a0 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs | |||
| @@ -0,0 +1,686 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.Linq; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | |||
| 16 | internal class CreateBurnManifestCommand | ||
| 17 | { | ||
| 18 | public IEnumerable<IBurnBackendExtension> BackendExtensions { private get; set; } | ||
| 19 | |||
| 20 | public Output Output { private get; set; } | ||
| 21 | |||
| 22 | public string ExecutableName { private get; set; } | ||
| 23 | |||
| 24 | public WixBundleRow BundleInfo { private get; set; } | ||
| 25 | |||
| 26 | public WixChainRow Chain { private get; set; } | ||
| 27 | |||
| 28 | public string OutputPath { private get; set; } | ||
| 29 | |||
| 30 | public IEnumerable<WixBundleRollbackBoundaryRow> RollbackBoundaries { private get; set; } | ||
| 31 | |||
| 32 | public IEnumerable<PackageFacade> OrderedPackages { private get; set; } | ||
| 33 | |||
| 34 | public IEnumerable<WixSearchInfo> OrderedSearches { private get; set; } | ||
| 35 | |||
| 36 | public Dictionary<string, WixBundlePayloadRow> Payloads { private get; set; } | ||
| 37 | |||
| 38 | public Dictionary<string, WixBundleContainerRow> Containers { private get; set; } | ||
| 39 | |||
| 40 | public IEnumerable<WixBundlePayloadRow> UXContainerPayloads { private get; set; } | ||
| 41 | |||
| 42 | public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; } | ||
| 43 | |||
| 44 | public void Execute() | ||
| 45 | { | ||
| 46 | using (XmlTextWriter writer = new XmlTextWriter(this.OutputPath, Encoding.UTF8)) | ||
| 47 | { | ||
| 48 | writer.WriteStartDocument(); | ||
| 49 | |||
| 50 | writer.WriteStartElement("BurnManifest", BurnCommon.BurnNamespace); | ||
| 51 | |||
| 52 | // Write the condition, if there is one | ||
| 53 | if (null != this.BundleInfo.Condition) | ||
| 54 | { | ||
| 55 | writer.WriteElementString("Condition", this.BundleInfo.Condition); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Write the log element if default logging wasn't disabled. | ||
| 59 | if (!String.IsNullOrEmpty(this.BundleInfo.LogPrefix)) | ||
| 60 | { | ||
| 61 | writer.WriteStartElement("Log"); | ||
| 62 | if (!String.IsNullOrEmpty(this.BundleInfo.LogPathVariable)) | ||
| 63 | { | ||
| 64 | writer.WriteAttributeString("PathVariable", this.BundleInfo.LogPathVariable); | ||
| 65 | } | ||
| 66 | writer.WriteAttributeString("Prefix", this.BundleInfo.LogPrefix); | ||
| 67 | writer.WriteAttributeString("Extension", this.BundleInfo.LogExtension); | ||
| 68 | writer.WriteEndElement(); | ||
| 69 | } | ||
| 70 | |||
| 71 | |||
| 72 | // Get update if specified. | ||
| 73 | WixBundleUpdateRow updateRow = this.Output.Tables["WixBundleUpdate"].RowsAs<WixBundleUpdateRow>().FirstOrDefault(); | ||
| 74 | |||
| 75 | if (null != updateRow) | ||
| 76 | { | ||
| 77 | writer.WriteStartElement("Update"); | ||
| 78 | writer.WriteAttributeString("Location", updateRow.Location); | ||
| 79 | writer.WriteEndElement(); // </Update> | ||
| 80 | } | ||
| 81 | |||
| 82 | // Write the RelatedBundle elements | ||
| 83 | |||
| 84 | // For the related bundles with duplicated identifiers the second instance is ignored (i.e. the Duplicates | ||
| 85 | // enumeration in the index row list is not used). | ||
| 86 | RowIndexedList<WixRelatedBundleRow> relatedBundles = new RowIndexedList<WixRelatedBundleRow>(this.Output.Tables["WixRelatedBundle"]); | ||
| 87 | |||
| 88 | foreach (WixRelatedBundleRow relatedBundle in relatedBundles) | ||
| 89 | { | ||
| 90 | writer.WriteStartElement("RelatedBundle"); | ||
| 91 | writer.WriteAttributeString("Id", relatedBundle.Id); | ||
| 92 | writer.WriteAttributeString("Action", Convert.ToString(relatedBundle.Action, CultureInfo.InvariantCulture)); | ||
| 93 | writer.WriteEndElement(); | ||
| 94 | } | ||
| 95 | |||
| 96 | // Write the variables | ||
| 97 | IEnumerable<WixBundleVariableRow> variables = this.Output.Tables["WixBundleVariable"].RowsAs<WixBundleVariableRow>(); | ||
| 98 | |||
| 99 | foreach (WixBundleVariableRow variable in variables) | ||
| 100 | { | ||
| 101 | writer.WriteStartElement("Variable"); | ||
| 102 | writer.WriteAttributeString("Id", variable.Id); | ||
| 103 | if (null != variable.Type) | ||
| 104 | { | ||
| 105 | writer.WriteAttributeString("Value", variable.Value); | ||
| 106 | writer.WriteAttributeString("Type", variable.Type); | ||
| 107 | } | ||
| 108 | writer.WriteAttributeString("Hidden", variable.Hidden ? "yes" : "no"); | ||
| 109 | writer.WriteAttributeString("Persisted", variable.Persisted ? "yes" : "no"); | ||
| 110 | writer.WriteEndElement(); | ||
| 111 | } | ||
| 112 | |||
| 113 | // Write the searches | ||
| 114 | foreach (WixSearchInfo searchinfo in this.OrderedSearches) | ||
| 115 | { | ||
| 116 | searchinfo.WriteXml(writer); | ||
| 117 | } | ||
| 118 | |||
| 119 | // write the UX element | ||
| 120 | writer.WriteStartElement("UX"); | ||
| 121 | if (!String.IsNullOrEmpty(this.BundleInfo.SplashScreenBitmapPath)) | ||
| 122 | { | ||
| 123 | writer.WriteAttributeString("SplashScreen", "yes"); | ||
| 124 | } | ||
| 125 | |||
| 126 | // write the UX allPayloads... | ||
| 127 | foreach (WixBundlePayloadRow payload in this.UXContainerPayloads) | ||
| 128 | { | ||
| 129 | writer.WriteStartElement("Payload"); | ||
| 130 | this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); | ||
| 131 | writer.WriteEndElement(); | ||
| 132 | } | ||
| 133 | |||
| 134 | writer.WriteEndElement(); // </UX> | ||
| 135 | |||
| 136 | // write the catalog elements | ||
| 137 | if (this.Catalogs.Any()) | ||
| 138 | { | ||
| 139 | foreach (WixBundleCatalogRow catalog in this.Catalogs) | ||
| 140 | { | ||
| 141 | writer.WriteStartElement("Catalog"); | ||
| 142 | writer.WriteAttributeString("Id", catalog.Id); | ||
| 143 | writer.WriteAttributeString("Payload", catalog.Payload); | ||
| 144 | writer.WriteEndElement(); | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | foreach (WixBundleContainerRow container in this.Containers.Values) | ||
| 149 | { | ||
| 150 | if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id) | ||
| 151 | { | ||
| 152 | writer.WriteStartElement("Container"); | ||
| 153 | this.WriteBurnManifestContainerAttributes(writer, this.ExecutableName, container); | ||
| 154 | writer.WriteEndElement(); | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | foreach (WixBundlePayloadRow payload in this.Payloads.Values) | ||
| 159 | { | ||
| 160 | if (PackagingType.Embedded == payload.Packaging && Compiler.BurnUXContainerId != payload.Container) | ||
| 161 | { | ||
| 162 | writer.WriteStartElement("Payload"); | ||
| 163 | this.WriteBurnManifestPayloadAttributes(writer, payload, true, this.Payloads); | ||
| 164 | writer.WriteEndElement(); | ||
| 165 | } | ||
| 166 | else if (PackagingType.External == payload.Packaging) | ||
| 167 | { | ||
| 168 | writer.WriteStartElement("Payload"); | ||
| 169 | this.WriteBurnManifestPayloadAttributes(writer, payload, false, this.Payloads); | ||
| 170 | writer.WriteEndElement(); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | foreach (WixBundleRollbackBoundaryRow rollbackBoundary in this.RollbackBoundaries) | ||
| 175 | { | ||
| 176 | writer.WriteStartElement("RollbackBoundary"); | ||
| 177 | writer.WriteAttributeString("Id", rollbackBoundary.ChainPackageId); | ||
| 178 | writer.WriteAttributeString("Vital", YesNoType.Yes == rollbackBoundary.Vital ? "yes" : "no"); | ||
| 179 | writer.WriteAttributeString("Transaction", YesNoType.Yes == rollbackBoundary.Transaction ? "yes" : "no"); | ||
| 180 | writer.WriteEndElement(); | ||
| 181 | } | ||
| 182 | |||
| 183 | // Write the registration information... | ||
| 184 | writer.WriteStartElement("Registration"); | ||
| 185 | |||
| 186 | writer.WriteAttributeString("Id", this.BundleInfo.BundleId.ToString("B")); | ||
| 187 | writer.WriteAttributeString("ExecutableName", this.ExecutableName); | ||
| 188 | writer.WriteAttributeString("PerMachine", this.BundleInfo.PerMachine ? "yes" : "no"); | ||
| 189 | writer.WriteAttributeString("Tag", this.BundleInfo.Tag); | ||
| 190 | writer.WriteAttributeString("Version", this.BundleInfo.Version); | ||
| 191 | writer.WriteAttributeString("ProviderKey", this.BundleInfo.ProviderKey); | ||
| 192 | |||
| 193 | writer.WriteStartElement("Arp"); | ||
| 194 | writer.WriteAttributeString("Register", (0 < this.BundleInfo.DisableModify && this.BundleInfo.DisableRemove) ? "no" : "yes"); // do not register if disabled modify and remove. | ||
| 195 | writer.WriteAttributeString("DisplayName", this.BundleInfo.Name); | ||
| 196 | writer.WriteAttributeString("DisplayVersion", this.BundleInfo.Version); | ||
| 197 | |||
| 198 | if (!String.IsNullOrEmpty(this.BundleInfo.Publisher)) | ||
| 199 | { | ||
| 200 | writer.WriteAttributeString("Publisher", this.BundleInfo.Publisher); | ||
| 201 | } | ||
| 202 | |||
| 203 | if (!String.IsNullOrEmpty(this.BundleInfo.HelpLink)) | ||
| 204 | { | ||
| 205 | writer.WriteAttributeString("HelpLink", this.BundleInfo.HelpLink); | ||
| 206 | } | ||
| 207 | |||
| 208 | if (!String.IsNullOrEmpty(this.BundleInfo.HelpTelephone)) | ||
| 209 | { | ||
| 210 | writer.WriteAttributeString("HelpTelephone", this.BundleInfo.HelpTelephone); | ||
| 211 | } | ||
| 212 | |||
| 213 | if (!String.IsNullOrEmpty(this.BundleInfo.AboutUrl)) | ||
| 214 | { | ||
| 215 | writer.WriteAttributeString("AboutUrl", this.BundleInfo.AboutUrl); | ||
| 216 | } | ||
| 217 | |||
| 218 | if (!String.IsNullOrEmpty(this.BundleInfo.UpdateUrl)) | ||
| 219 | { | ||
| 220 | writer.WriteAttributeString("UpdateUrl", this.BundleInfo.UpdateUrl); | ||
| 221 | } | ||
| 222 | |||
| 223 | if (!String.IsNullOrEmpty(this.BundleInfo.ParentName)) | ||
| 224 | { | ||
| 225 | writer.WriteAttributeString("ParentDisplayName", this.BundleInfo.ParentName); | ||
| 226 | } | ||
| 227 | |||
| 228 | if (1 == this.BundleInfo.DisableModify) | ||
| 229 | { | ||
| 230 | writer.WriteAttributeString("DisableModify", "yes"); | ||
| 231 | } | ||
| 232 | else if (2 == this.BundleInfo.DisableModify) | ||
| 233 | { | ||
| 234 | writer.WriteAttributeString("DisableModify", "button"); | ||
| 235 | } | ||
| 236 | |||
| 237 | if (this.BundleInfo.DisableRemove) | ||
| 238 | { | ||
| 239 | writer.WriteAttributeString("DisableRemove", "yes"); | ||
| 240 | } | ||
| 241 | writer.WriteEndElement(); // </Arp> | ||
| 242 | |||
| 243 | // Get update registration if specified. | ||
| 244 | WixUpdateRegistrationRow updateRegistrationInfo = this.Output.Tables["WixUpdateRegistration"].RowsAs<WixUpdateRegistrationRow>().FirstOrDefault(); | ||
| 245 | |||
| 246 | if (null != updateRegistrationInfo) | ||
| 247 | { | ||
| 248 | writer.WriteStartElement("Update"); // <Update> | ||
| 249 | writer.WriteAttributeString("Manufacturer", updateRegistrationInfo.Manufacturer); | ||
| 250 | |||
| 251 | if (!String.IsNullOrEmpty(updateRegistrationInfo.Department)) | ||
| 252 | { | ||
| 253 | writer.WriteAttributeString("Department", updateRegistrationInfo.Department); | ||
| 254 | } | ||
| 255 | |||
| 256 | if (!String.IsNullOrEmpty(updateRegistrationInfo.ProductFamily)) | ||
| 257 | { | ||
| 258 | writer.WriteAttributeString("ProductFamily", updateRegistrationInfo.ProductFamily); | ||
| 259 | } | ||
| 260 | |||
| 261 | writer.WriteAttributeString("Name", updateRegistrationInfo.Name); | ||
| 262 | writer.WriteAttributeString("Classification", updateRegistrationInfo.Classification); | ||
| 263 | writer.WriteEndElement(); // </Update> | ||
| 264 | } | ||
| 265 | |||
| 266 | IEnumerable<Row> bundleTags = this.Output.Tables["WixBundleTag"].RowsAs<Row>(); | ||
| 267 | |||
| 268 | foreach (Row row in bundleTags) | ||
| 269 | { | ||
| 270 | writer.WriteStartElement("SoftwareTag"); | ||
| 271 | writer.WriteAttributeString("Filename", (string)row[0]); | ||
| 272 | writer.WriteAttributeString("Regid", (string)row[1]); | ||
| 273 | writer.WriteCData((string)row[4]); | ||
| 274 | writer.WriteEndElement(); | ||
| 275 | } | ||
| 276 | |||
| 277 | writer.WriteEndElement(); // </Register> | ||
| 278 | |||
| 279 | // write the Chain... | ||
| 280 | writer.WriteStartElement("Chain"); | ||
| 281 | if (this.Chain.DisableRollback) | ||
| 282 | { | ||
| 283 | writer.WriteAttributeString("DisableRollback", "yes"); | ||
| 284 | } | ||
| 285 | |||
| 286 | if (this.Chain.DisableSystemRestore) | ||
| 287 | { | ||
| 288 | writer.WriteAttributeString("DisableSystemRestore", "yes"); | ||
| 289 | } | ||
| 290 | |||
| 291 | if (this.Chain.ParallelCache) | ||
| 292 | { | ||
| 293 | writer.WriteAttributeString("ParallelCache", "yes"); | ||
| 294 | } | ||
| 295 | |||
| 296 | // Index a few tables by package. | ||
| 297 | ILookup<string, WixBundlePatchTargetCodeRow> targetCodesByPatch = this.Output.Tables["WixBundlePatchTargetCode"].RowsAs<WixBundlePatchTargetCodeRow>().ToLookup(r => r.MspPackageId); | ||
| 298 | ILookup<string, WixBundleMsiFeatureRow> msiFeaturesByPackage = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>().ToLookup(r => r.ChainPackageId); | ||
| 299 | ILookup<string, WixBundleMsiPropertyRow> msiPropertiesByPackage = this.Output.Tables["WixBundleMsiProperty"].RowsAs<WixBundleMsiPropertyRow>().ToLookup(r => r.ChainPackageId); | ||
| 300 | ILookup<string, WixBundlePayloadRow> payloadsByPackage = this.Payloads.Values.ToLookup(p => p.Package); | ||
| 301 | ILookup<string, WixBundleRelatedPackageRow> relatedPackagesByPackage = this.Output.Tables["WixBundleRelatedPackage"].RowsAs<WixBundleRelatedPackageRow>().ToLookup(r => r.ChainPackageId); | ||
| 302 | ILookup<string, WixBundleSlipstreamMspRow> slipstreamMspsByPackage = this.Output.Tables["WixBundleSlipstreamMsp"].RowsAs<WixBundleSlipstreamMspRow>().ToLookup(r => r.ChainPackageId); | ||
| 303 | ILookup<string, WixBundlePackageExitCodeRow> exitCodesByPackage = this.Output.Tables["WixBundlePackageExitCode"].RowsAs<WixBundlePackageExitCodeRow>().ToLookup(r => r.ChainPackageId); | ||
| 304 | ILookup<string, WixBundlePackageCommandLineRow> commandLinesByPackage = this.Output.Tables["WixBundlePackageCommandLine"].RowsAs<WixBundlePackageCommandLineRow>().ToLookup(r => r.ChainPackageId); | ||
| 305 | |||
| 306 | // Build up the list of target codes from all the MSPs in the chain. | ||
| 307 | List<WixBundlePatchTargetCodeRow> targetCodes = new List<WixBundlePatchTargetCodeRow>(); | ||
| 308 | |||
| 309 | foreach (PackageFacade package in this.OrderedPackages) | ||
| 310 | { | ||
| 311 | writer.WriteStartElement(String.Format(CultureInfo.InvariantCulture, "{0}Package", package.Package.Type)); | ||
| 312 | |||
| 313 | writer.WriteAttributeString("Id", package.Package.WixChainItemId); | ||
| 314 | |||
| 315 | switch (package.Package.Cache) | ||
| 316 | { | ||
| 317 | case YesNoAlwaysType.No: | ||
| 318 | writer.WriteAttributeString("Cache", "no"); | ||
| 319 | break; | ||
| 320 | case YesNoAlwaysType.Yes: | ||
| 321 | writer.WriteAttributeString("Cache", "yes"); | ||
| 322 | break; | ||
| 323 | case YesNoAlwaysType.Always: | ||
| 324 | writer.WriteAttributeString("Cache", "always"); | ||
| 325 | break; | ||
| 326 | } | ||
| 327 | |||
| 328 | writer.WriteAttributeString("CacheId", package.Package.CacheId); | ||
| 329 | writer.WriteAttributeString("InstallSize", Convert.ToString(package.Package.InstallSize)); | ||
| 330 | writer.WriteAttributeString("Size", Convert.ToString(package.Package.Size)); | ||
| 331 | writer.WriteAttributeString("PerMachine", YesNoDefaultType.Yes == package.Package.PerMachine ? "yes" : "no"); | ||
| 332 | writer.WriteAttributeString("Permanent", package.Package.Permanent ? "yes" : "no"); | ||
| 333 | writer.WriteAttributeString("Vital", (YesNoType.Yes == package.Package.Vital) ? "yes" : "no"); | ||
| 334 | |||
| 335 | if (null != package.Package.RollbackBoundary) | ||
| 336 | { | ||
| 337 | writer.WriteAttributeString("RollbackBoundaryForward", package.Package.RollbackBoundary); | ||
| 338 | } | ||
| 339 | |||
| 340 | if (!String.IsNullOrEmpty(package.Package.RollbackBoundaryBackward)) | ||
| 341 | { | ||
| 342 | writer.WriteAttributeString("RollbackBoundaryBackward", package.Package.RollbackBoundaryBackward); | ||
| 343 | } | ||
| 344 | |||
| 345 | if (!String.IsNullOrEmpty(package.Package.LogPathVariable)) | ||
| 346 | { | ||
| 347 | writer.WriteAttributeString("LogPathVariable", package.Package.LogPathVariable); | ||
| 348 | } | ||
| 349 | |||
| 350 | if (!String.IsNullOrEmpty(package.Package.RollbackLogPathVariable)) | ||
| 351 | { | ||
| 352 | writer.WriteAttributeString("RollbackLogPathVariable", package.Package.RollbackLogPathVariable); | ||
| 353 | } | ||
| 354 | |||
| 355 | if (!String.IsNullOrEmpty(package.Package.InstallCondition)) | ||
| 356 | { | ||
| 357 | writer.WriteAttributeString("InstallCondition", package.Package.InstallCondition); | ||
| 358 | } | ||
| 359 | |||
| 360 | if (WixBundlePackageType.Exe == package.Package.Type) | ||
| 361 | { | ||
| 362 | writer.WriteAttributeString("DetectCondition", package.ExePackage.DetectCondition); | ||
| 363 | writer.WriteAttributeString("InstallArguments", package.ExePackage.InstallCommand); | ||
| 364 | writer.WriteAttributeString("UninstallArguments", package.ExePackage.UninstallCommand); | ||
| 365 | writer.WriteAttributeString("RepairArguments", package.ExePackage.RepairCommand); | ||
| 366 | writer.WriteAttributeString("Repairable", package.ExePackage.Repairable ? "yes" : "no"); | ||
| 367 | if (!String.IsNullOrEmpty(package.ExePackage.ExeProtocol)) | ||
| 368 | { | ||
| 369 | writer.WriteAttributeString("Protocol", package.ExePackage.ExeProtocol); | ||
| 370 | } | ||
| 371 | } | ||
| 372 | else if (WixBundlePackageType.Msi == package.Package.Type) | ||
| 373 | { | ||
| 374 | writer.WriteAttributeString("ProductCode", package.MsiPackage.ProductCode); | ||
| 375 | writer.WriteAttributeString("Language", package.MsiPackage.ProductLanguage.ToString(CultureInfo.InvariantCulture)); | ||
| 376 | writer.WriteAttributeString("Version", package.MsiPackage.ProductVersion); | ||
| 377 | writer.WriteAttributeString("DisplayInternalUI", package.MsiPackage.DisplayInternalUI ? "yes" : "no"); | ||
| 378 | if (!String.IsNullOrEmpty(package.MsiPackage.UpgradeCode)) | ||
| 379 | { | ||
| 380 | writer.WriteAttributeString("UpgradeCode", package.MsiPackage.UpgradeCode); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | else if (WixBundlePackageType.Msp == package.Package.Type) | ||
| 384 | { | ||
| 385 | writer.WriteAttributeString("PatchCode", package.MspPackage.PatchCode); | ||
| 386 | writer.WriteAttributeString("PatchXml", package.MspPackage.PatchXml); | ||
| 387 | writer.WriteAttributeString("DisplayInternalUI", package.MspPackage.DisplayInternalUI ? "yes" : "no"); | ||
| 388 | |||
| 389 | // If there is still a chance that all of our patches will target a narrow set of | ||
| 390 | // product codes, add the patch list to the overall list. | ||
| 391 | if (null != targetCodes) | ||
| 392 | { | ||
| 393 | if (!package.MspPackage.TargetUnspecified) | ||
| 394 | { | ||
| 395 | IEnumerable<WixBundlePatchTargetCodeRow> patchTargetCodes = targetCodesByPatch[package.MspPackage.ChainPackageId]; | ||
| 396 | |||
| 397 | targetCodes.AddRange(patchTargetCodes); | ||
| 398 | } | ||
| 399 | else // we have a patch that targets the world, so throw the whole list away. | ||
| 400 | { | ||
| 401 | targetCodes = null; | ||
| 402 | } | ||
| 403 | } | ||
| 404 | } | ||
| 405 | else if (WixBundlePackageType.Msu == package.Package.Type) | ||
| 406 | { | ||
| 407 | writer.WriteAttributeString("DetectCondition", package.MsuPackage.DetectCondition); | ||
| 408 | writer.WriteAttributeString("KB", package.MsuPackage.MsuKB); | ||
| 409 | } | ||
| 410 | |||
| 411 | IEnumerable<WixBundleMsiFeatureRow> packageMsiFeatures = msiFeaturesByPackage[package.Package.WixChainItemId]; | ||
| 412 | |||
| 413 | foreach (WixBundleMsiFeatureRow feature in packageMsiFeatures) | ||
| 414 | { | ||
| 415 | writer.WriteStartElement("MsiFeature"); | ||
| 416 | writer.WriteAttributeString("Id", feature.Name); | ||
| 417 | writer.WriteEndElement(); | ||
| 418 | } | ||
| 419 | |||
| 420 | IEnumerable<WixBundleMsiPropertyRow> packageMsiProperties = msiPropertiesByPackage[package.Package.WixChainItemId]; | ||
| 421 | |||
| 422 | foreach (WixBundleMsiPropertyRow msiProperty in packageMsiProperties) | ||
| 423 | { | ||
| 424 | writer.WriteStartElement("MsiProperty"); | ||
| 425 | writer.WriteAttributeString("Id", msiProperty.Name); | ||
| 426 | writer.WriteAttributeString("Value", msiProperty.Value); | ||
| 427 | if (!String.IsNullOrEmpty(msiProperty.Condition)) | ||
| 428 | { | ||
| 429 | writer.WriteAttributeString("Condition", msiProperty.Condition); | ||
| 430 | } | ||
| 431 | writer.WriteEndElement(); | ||
| 432 | } | ||
| 433 | |||
| 434 | IEnumerable<WixBundleSlipstreamMspRow> packageSlipstreamMsps = slipstreamMspsByPackage[package.Package.WixChainItemId]; | ||
| 435 | |||
| 436 | foreach (WixBundleSlipstreamMspRow slipstreamMsp in packageSlipstreamMsps) | ||
| 437 | { | ||
| 438 | writer.WriteStartElement("SlipstreamMsp"); | ||
| 439 | writer.WriteAttributeString("Id", slipstreamMsp.MspPackageId); | ||
| 440 | writer.WriteEndElement(); | ||
| 441 | } | ||
| 442 | |||
| 443 | IEnumerable<WixBundlePackageExitCodeRow> packageExitCodes = exitCodesByPackage[package.Package.WixChainItemId]; | ||
| 444 | |||
| 445 | foreach (WixBundlePackageExitCodeRow exitCode in packageExitCodes) | ||
| 446 | { | ||
| 447 | writer.WriteStartElement("ExitCode"); | ||
| 448 | |||
| 449 | if (exitCode.Code.HasValue) | ||
| 450 | { | ||
| 451 | writer.WriteAttributeString("Code", unchecked((uint)exitCode.Code).ToString(CultureInfo.InvariantCulture)); | ||
| 452 | } | ||
| 453 | else | ||
| 454 | { | ||
| 455 | writer.WriteAttributeString("Code", "*"); | ||
| 456 | } | ||
| 457 | |||
| 458 | writer.WriteAttributeString("Type", ((int)exitCode.Behavior).ToString(CultureInfo.InvariantCulture)); | ||
| 459 | writer.WriteEndElement(); | ||
| 460 | } | ||
| 461 | |||
| 462 | IEnumerable<WixBundlePackageCommandLineRow> packageCommandLines = commandLinesByPackage[package.Package.WixChainItemId]; | ||
| 463 | |||
| 464 | foreach (WixBundlePackageCommandLineRow commandLine in packageCommandLines) | ||
| 465 | { | ||
| 466 | writer.WriteStartElement("CommandLine"); | ||
| 467 | writer.WriteAttributeString("InstallArgument", commandLine.InstallArgument); | ||
| 468 | writer.WriteAttributeString("UninstallArgument", commandLine.UninstallArgument); | ||
| 469 | writer.WriteAttributeString("RepairArgument", commandLine.RepairArgument); | ||
| 470 | writer.WriteAttributeString("Condition", commandLine.Condition); | ||
| 471 | writer.WriteEndElement(); | ||
| 472 | } | ||
| 473 | |||
| 474 | // Output the dependency information. | ||
| 475 | foreach (ProvidesDependency dependency in package.Provides) | ||
| 476 | { | ||
| 477 | // TODO: Add to wixpdb as an imported table, or link package wixpdbs to bundle wixpdbs. | ||
| 478 | dependency.WriteXml(writer); | ||
| 479 | } | ||
| 480 | |||
| 481 | IEnumerable<WixBundleRelatedPackageRow> packageRelatedPackages = relatedPackagesByPackage[package.Package.WixChainItemId]; | ||
| 482 | |||
| 483 | foreach (WixBundleRelatedPackageRow related in packageRelatedPackages) | ||
| 484 | { | ||
| 485 | writer.WriteStartElement("RelatedPackage"); | ||
| 486 | writer.WriteAttributeString("Id", related.Id); | ||
| 487 | if (!String.IsNullOrEmpty(related.MinVersion)) | ||
| 488 | { | ||
| 489 | writer.WriteAttributeString("MinVersion", related.MinVersion); | ||
| 490 | writer.WriteAttributeString("MinInclusive", related.MinInclusive ? "yes" : "no"); | ||
| 491 | } | ||
| 492 | if (!String.IsNullOrEmpty(related.MaxVersion)) | ||
| 493 | { | ||
| 494 | writer.WriteAttributeString("MaxVersion", related.MaxVersion); | ||
| 495 | writer.WriteAttributeString("MaxInclusive", related.MaxInclusive ? "yes" : "no"); | ||
| 496 | } | ||
| 497 | writer.WriteAttributeString("OnlyDetect", related.OnlyDetect ? "yes" : "no"); | ||
| 498 | |||
| 499 | string[] relatedLanguages = related.Languages.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); | ||
| 500 | |||
| 501 | if (0 < relatedLanguages.Length) | ||
| 502 | { | ||
| 503 | writer.WriteAttributeString("LangInclusive", related.LangInclusive ? "yes" : "no"); | ||
| 504 | foreach (string language in relatedLanguages) | ||
| 505 | { | ||
| 506 | writer.WriteStartElement("Language"); | ||
| 507 | writer.WriteAttributeString("Id", language); | ||
| 508 | writer.WriteEndElement(); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | writer.WriteEndElement(); | ||
| 512 | } | ||
| 513 | |||
| 514 | // Write any contained Payloads with the PackagePayload being first | ||
| 515 | writer.WriteStartElement("PayloadRef"); | ||
| 516 | writer.WriteAttributeString("Id", package.Package.PackagePayload); | ||
| 517 | writer.WriteEndElement(); | ||
| 518 | |||
| 519 | IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[package.Package.WixChainItemId]; | ||
| 520 | |||
| 521 | foreach (WixBundlePayloadRow payload in packagePayloads) | ||
| 522 | { | ||
| 523 | if (payload.Id != package.Package.PackagePayload) | ||
| 524 | { | ||
| 525 | writer.WriteStartElement("PayloadRef"); | ||
| 526 | writer.WriteAttributeString("Id", payload.Id); | ||
| 527 | writer.WriteEndElement(); | ||
| 528 | } | ||
| 529 | } | ||
| 530 | |||
| 531 | writer.WriteEndElement(); // </XxxPackage> | ||
| 532 | } | ||
| 533 | writer.WriteEndElement(); // </Chain> | ||
| 534 | |||
| 535 | if (null != targetCodes) | ||
| 536 | { | ||
| 537 | foreach (WixBundlePatchTargetCodeRow targetCode in targetCodes) | ||
| 538 | { | ||
| 539 | writer.WriteStartElement("PatchTargetCode"); | ||
| 540 | writer.WriteAttributeString("TargetCode", targetCode.TargetCode); | ||
| 541 | writer.WriteAttributeString("Product", targetCode.TargetsProductCode ? "yes" : "no"); | ||
| 542 | writer.WriteEndElement(); | ||
| 543 | } | ||
| 544 | } | ||
| 545 | |||
| 546 | // Write the ApprovedExeForElevation elements. | ||
| 547 | IEnumerable<WixApprovedExeForElevationRow> approvedExesForElevation = this.Output.Tables["WixApprovedExeForElevation"].RowsAs<WixApprovedExeForElevationRow>(); | ||
| 548 | |||
| 549 | foreach (WixApprovedExeForElevationRow approvedExeForElevation in approvedExesForElevation) | ||
| 550 | { | ||
| 551 | writer.WriteStartElement("ApprovedExeForElevation"); | ||
| 552 | writer.WriteAttributeString("Id", approvedExeForElevation.Id); | ||
| 553 | writer.WriteAttributeString("Key", approvedExeForElevation.Key); | ||
| 554 | |||
| 555 | if (!String.IsNullOrEmpty(approvedExeForElevation.ValueName)) | ||
| 556 | { | ||
| 557 | writer.WriteAttributeString("ValueName", approvedExeForElevation.ValueName); | ||
| 558 | } | ||
| 559 | |||
| 560 | if (approvedExeForElevation.Win64) | ||
| 561 | { | ||
| 562 | writer.WriteAttributeString("Win64", "yes"); | ||
| 563 | } | ||
| 564 | |||
| 565 | writer.WriteEndElement(); | ||
| 566 | } | ||
| 567 | |||
| 568 | writer.WriteEndDocument(); // </BurnManifest> | ||
| 569 | } | ||
| 570 | } | ||
| 571 | |||
| 572 | private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string executableName, WixBundleContainerRow container) | ||
| 573 | { | ||
| 574 | writer.WriteAttributeString("Id", container.Id); | ||
| 575 | writer.WriteAttributeString("FileSize", container.Size.ToString(CultureInfo.InvariantCulture)); | ||
| 576 | writer.WriteAttributeString("Hash", container.Hash); | ||
| 577 | |||
| 578 | if (ContainerType.Detached == container.Type) | ||
| 579 | { | ||
| 580 | string resolvedUrl = this.ResolveUrl(container.DownloadUrl, null, null, container.Id, container.Name); | ||
| 581 | if (!String.IsNullOrEmpty(resolvedUrl)) | ||
| 582 | { | ||
| 583 | writer.WriteAttributeString("DownloadUrl", resolvedUrl); | ||
| 584 | } | ||
| 585 | else if (!String.IsNullOrEmpty(container.DownloadUrl)) | ||
| 586 | { | ||
| 587 | writer.WriteAttributeString("DownloadUrl", container.DownloadUrl); | ||
| 588 | } | ||
| 589 | |||
| 590 | writer.WriteAttributeString("FilePath", container.Name); | ||
| 591 | } | ||
| 592 | else if (ContainerType.Attached == container.Type) | ||
| 593 | { | ||
| 594 | if (!String.IsNullOrEmpty(container.DownloadUrl)) | ||
| 595 | { | ||
| 596 | Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForAttachedContainers(container.SourceLineNumbers, container.Id)); | ||
| 597 | } | ||
| 598 | |||
| 599 | writer.WriteAttributeString("FilePath", executableName); // attached containers use the name of the bundle since they are attached to the executable. | ||
| 600 | writer.WriteAttributeString("AttachedIndex", container.AttachedContainerIndex.ToString(CultureInfo.InvariantCulture)); | ||
| 601 | writer.WriteAttributeString("Attached", "yes"); | ||
| 602 | writer.WriteAttributeString("Primary", "yes"); | ||
| 603 | } | ||
| 604 | } | ||
| 605 | |||
| 606 | private void WriteBurnManifestPayloadAttributes(XmlTextWriter writer, WixBundlePayloadRow payload, bool embeddedOnly, Dictionary<string, WixBundlePayloadRow> allPayloads) | ||
| 607 | { | ||
| 608 | Debug.Assert(!embeddedOnly || PackagingType.Embedded == payload.Packaging); | ||
| 609 | |||
| 610 | writer.WriteAttributeString("Id", payload.Id); | ||
| 611 | writer.WriteAttributeString("FilePath", payload.Name); | ||
| 612 | writer.WriteAttributeString("FileSize", payload.FileSize.ToString(CultureInfo.InvariantCulture)); | ||
| 613 | writer.WriteAttributeString("Hash", payload.Hash); | ||
| 614 | |||
| 615 | if (payload.LayoutOnly) | ||
| 616 | { | ||
| 617 | writer.WriteAttributeString("LayoutOnly", "yes"); | ||
| 618 | } | ||
| 619 | |||
| 620 | if (!String.IsNullOrEmpty(payload.PublicKey)) | ||
| 621 | { | ||
| 622 | writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.PublicKey); | ||
| 623 | } | ||
| 624 | |||
| 625 | if (!String.IsNullOrEmpty(payload.Thumbprint)) | ||
| 626 | { | ||
| 627 | writer.WriteAttributeString("CertificateRootThumbprint", payload.Thumbprint); | ||
| 628 | } | ||
| 629 | |||
| 630 | switch (payload.Packaging) | ||
| 631 | { | ||
| 632 | case PackagingType.Embedded: // this means it's in a container. | ||
| 633 | if (!String.IsNullOrEmpty(payload.DownloadUrl)) | ||
| 634 | { | ||
| 635 | Messaging.Instance.OnMessage(WixWarnings.DownloadUrlNotSupportedForEmbeddedPayloads(payload.SourceLineNumbers, payload.Id)); | ||
| 636 | } | ||
| 637 | |||
| 638 | writer.WriteAttributeString("Packaging", "embedded"); | ||
| 639 | writer.WriteAttributeString("SourcePath", payload.EmbeddedId); | ||
| 640 | |||
| 641 | if (Compiler.BurnUXContainerId != payload.Container) | ||
| 642 | { | ||
| 643 | writer.WriteAttributeString("Container", payload.Container); | ||
| 644 | } | ||
| 645 | break; | ||
| 646 | |||
| 647 | case PackagingType.External: | ||
| 648 | string packageId = payload.ParentPackagePayload; | ||
| 649 | string parentUrl = payload.ParentPackagePayload == null ? null : allPayloads[payload.ParentPackagePayload].DownloadUrl; | ||
| 650 | string resolvedUrl = this.ResolveUrl(payload.DownloadUrl, parentUrl, packageId, payload.Id, payload.Name); | ||
| 651 | if (!String.IsNullOrEmpty(resolvedUrl)) | ||
| 652 | { | ||
| 653 | writer.WriteAttributeString("DownloadUrl", resolvedUrl); | ||
| 654 | } | ||
| 655 | else if (!String.IsNullOrEmpty(payload.DownloadUrl)) | ||
| 656 | { | ||
| 657 | writer.WriteAttributeString("DownloadUrl", payload.DownloadUrl); | ||
| 658 | } | ||
| 659 | |||
| 660 | writer.WriteAttributeString("Packaging", "external"); | ||
| 661 | writer.WriteAttributeString("SourcePath", payload.Name); | ||
| 662 | break; | ||
| 663 | } | ||
| 664 | |||
| 665 | if (!String.IsNullOrEmpty(payload.Catalog)) | ||
| 666 | { | ||
| 667 | writer.WriteAttributeString("Catalog", payload.Catalog); | ||
| 668 | } | ||
| 669 | } | ||
| 670 | |||
| 671 | private string ResolveUrl(string url, string fallbackUrl, string packageId, string payloadId, string fileName) | ||
| 672 | { | ||
| 673 | string resolved = null; | ||
| 674 | foreach (var extension in this.BackendExtensions) | ||
| 675 | { | ||
| 676 | resolved = extension.ResolveUrl(url, fallbackUrl, packageId, payloadId, fileName); | ||
| 677 | if (!String.IsNullOrEmpty(resolved)) | ||
| 678 | { | ||
| 679 | break; | ||
| 680 | } | ||
| 681 | } | ||
| 682 | |||
| 683 | return resolved; | ||
| 684 | } | ||
| 685 | } | ||
| 686 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs new file mode 100644 index 00000000..75379713 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using System.Linq; | ||
| 10 | using WixToolset.Core.Cab; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Rows; | ||
| 13 | |||
| 14 | /// <summary> | ||
| 15 | /// Creates cabinet files. | ||
| 16 | /// </summary> | ||
| 17 | internal class CreateContainerCommand | ||
| 18 | { | ||
| 19 | public CompressionLevel DefaultCompressionLevel { private get; set; } | ||
| 20 | |||
| 21 | public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 22 | |||
| 23 | public string ManifestFile { private get; set; } | ||
| 24 | |||
| 25 | public string OutputPath { private get; set; } | ||
| 26 | |||
| 27 | public string Hash { get; private set; } | ||
| 28 | |||
| 29 | public long Size { get; private set; } | ||
| 30 | |||
| 31 | public void Execute() | ||
| 32 | { | ||
| 33 | int payloadCount = this.Payloads.Count(); // The number of embedded payloads | ||
| 34 | |||
| 35 | if (!String.IsNullOrEmpty(this.ManifestFile)) | ||
| 36 | { | ||
| 37 | ++payloadCount; | ||
| 38 | } | ||
| 39 | |||
| 40 | using (var cab = new WixCreateCab(Path.GetFileName(this.OutputPath), Path.GetDirectoryName(this.OutputPath), payloadCount, 0, 0, this.DefaultCompressionLevel)) | ||
| 41 | { | ||
| 42 | // If a manifest was provided always add it as "payload 0" to the container. | ||
| 43 | if (!String.IsNullOrEmpty(this.ManifestFile)) | ||
| 44 | { | ||
| 45 | cab.AddFile(this.ManifestFile, "0"); | ||
| 46 | } | ||
| 47 | |||
| 48 | foreach (WixBundlePayloadRow payload in this.Payloads) | ||
| 49 | { | ||
| 50 | Debug.Assert(PackagingType.Embedded == payload.Packaging); | ||
| 51 | |||
| 52 | Messaging.Instance.OnMessage(WixVerboses.LoadingPayload(payload.FullFileName)); | ||
| 53 | |||
| 54 | cab.AddFile(payload.FullFileName, payload.EmbeddedId); | ||
| 55 | } | ||
| 56 | |||
| 57 | cab.Complete(); | ||
| 58 | } | ||
| 59 | |||
| 60 | // Now that the container is created, set the outputs of the command. | ||
| 61 | FileInfo fileInfo = new FileInfo(this.OutputPath); | ||
| 62 | |||
| 63 | this.Hash = Common.GetFileHash(fileInfo.FullName); | ||
| 64 | |||
| 65 | this.Size = fileInfo.Length; | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs new file mode 100644 index 00000000..7485758c --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System.Collections.Generic; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Rows; | ||
| 8 | |||
| 9 | internal class GetPackageFacadesCommand | ||
| 10 | { | ||
| 11 | public Table PackageTable { private get; set; } | ||
| 12 | |||
| 13 | public Table ExePackageTable { private get; set; } | ||
| 14 | |||
| 15 | public Table MsiPackageTable { private get; set; } | ||
| 16 | |||
| 17 | public Table MspPackageTable { private get; set; } | ||
| 18 | |||
| 19 | public Table MsuPackageTable { private get; set; } | ||
| 20 | |||
| 21 | public IDictionary<string, PackageFacade> PackageFacades { get; private set; } | ||
| 22 | |||
| 23 | public void Execute() | ||
| 24 | { | ||
| 25 | RowDictionary<WixBundleExePackageRow> exePackages = new RowDictionary<WixBundleExePackageRow>(this.ExePackageTable); | ||
| 26 | RowDictionary<WixBundleMsiPackageRow> msiPackages = new RowDictionary<WixBundleMsiPackageRow>(this.MsiPackageTable); | ||
| 27 | RowDictionary<WixBundleMspPackageRow> mspPackages = new RowDictionary<WixBundleMspPackageRow>(this.MspPackageTable); | ||
| 28 | RowDictionary<WixBundleMsuPackageRow> msuPackages = new RowDictionary<WixBundleMsuPackageRow>(this.MsuPackageTable); | ||
| 29 | |||
| 30 | Dictionary<string, PackageFacade> facades = new Dictionary<string, PackageFacade>(this.PackageTable.Rows.Count); | ||
| 31 | |||
| 32 | foreach (WixBundlePackageRow package in this.PackageTable.Rows) | ||
| 33 | { | ||
| 34 | string id = package.WixChainItemId; | ||
| 35 | PackageFacade facade = null; | ||
| 36 | |||
| 37 | switch (package.Type) | ||
| 38 | { | ||
| 39 | case WixBundlePackageType.Exe: | ||
| 40 | facade = new PackageFacade(package, exePackages.Get(id)); | ||
| 41 | break; | ||
| 42 | |||
| 43 | case WixBundlePackageType.Msi: | ||
| 44 | facade = new PackageFacade(package, msiPackages.Get(id)); | ||
| 45 | break; | ||
| 46 | |||
| 47 | case WixBundlePackageType.Msp: | ||
| 48 | facade = new PackageFacade(package, mspPackages.Get(id)); | ||
| 49 | break; | ||
| 50 | |||
| 51 | case WixBundlePackageType.Msu: | ||
| 52 | facade = new PackageFacade(package, msuPackages.Get(id)); | ||
| 53 | break; | ||
| 54 | } | ||
| 55 | |||
| 56 | facades.Add(id, facade); | ||
| 57 | } | ||
| 58 | |||
| 59 | this.PackageFacades = facades; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs new file mode 100644 index 00000000..cb6e2748 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs | |||
| @@ -0,0 +1,145 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using WixToolset.Data; | ||
| 8 | using WixToolset.Data.Rows; | ||
| 9 | |||
| 10 | internal class OrderPackagesAndRollbackBoundariesCommand | ||
| 11 | { | ||
| 12 | public Table WixGroupTable { private get; set; } | ||
| 13 | |||
| 14 | public RowDictionary<WixBundleRollbackBoundaryRow> Boundaries { private get; set; } | ||
| 15 | |||
| 16 | public IDictionary<string, PackageFacade> PackageFacades { private get; set; } | ||
| 17 | |||
| 18 | public IEnumerable<PackageFacade> OrderedPackageFacades { get; private set; } | ||
| 19 | |||
| 20 | public IEnumerable<WixBundleRollbackBoundaryRow> UsedRollbackBoundaries { get; private set; } | ||
| 21 | |||
| 22 | public void Execute() | ||
| 23 | { | ||
| 24 | List<PackageFacade> orderedFacades = new List<PackageFacade>(); | ||
| 25 | List<WixBundleRollbackBoundaryRow> usedBoundaries = new List<WixBundleRollbackBoundaryRow>(); | ||
| 26 | |||
| 27 | // Process the chain of packages to add them in the correct order | ||
| 28 | // and assign the forward rollback boundaries as appropriate. Remember | ||
| 29 | // rollback boundaries are authored as elements in the chain which | ||
| 30 | // we re-interpret here to add them as attributes on the next available | ||
| 31 | // package in the chain. Essentially we mark some packages as being | ||
| 32 | // the start of a rollback boundary when installing and repairing. | ||
| 33 | // We handle uninstall (aka: backwards) rollback boundaries after | ||
| 34 | // we get these install/repair (aka: forward) rollback boundaries | ||
| 35 | // defined. | ||
| 36 | WixBundleRollbackBoundaryRow previousRollbackBoundary = null; | ||
| 37 | WixBundleRollbackBoundaryRow lastRollbackBoundary = null; | ||
| 38 | bool boundaryHadX86Package = false; | ||
| 39 | |||
| 40 | foreach (WixGroupRow row in this.WixGroupTable.Rows) | ||
| 41 | { | ||
| 42 | if (ComplexReferenceChildType.Package == row.ChildType && ComplexReferenceParentType.PackageGroup == row.ParentType && "WixChain" == row.ParentId) | ||
| 43 | { | ||
| 44 | PackageFacade facade = null; | ||
| 45 | if (PackageFacades.TryGetValue(row.ChildId, out facade)) | ||
| 46 | { | ||
| 47 | if (null != previousRollbackBoundary) | ||
| 48 | { | ||
| 49 | usedBoundaries.Add(previousRollbackBoundary); | ||
| 50 | facade.Package.RollbackBoundary = previousRollbackBoundary.ChainPackageId; | ||
| 51 | previousRollbackBoundary = null; | ||
| 52 | |||
| 53 | boundaryHadX86Package = (facade.Package.x64 == YesNoType.Yes); | ||
| 54 | } | ||
| 55 | |||
| 56 | // Error if MSI transaction has x86 package preceding x64 packages | ||
| 57 | if ((lastRollbackBoundary != null) && (lastRollbackBoundary.Transaction == YesNoType.Yes) | ||
| 58 | && boundaryHadX86Package | ||
| 59 | && (facade.Package.x64 == YesNoType.Yes)) | ||
| 60 | { | ||
| 61 | Messaging.Instance.OnMessage(WixErrors.MsiTransactionX86BeforeX64(lastRollbackBoundary.SourceLineNumbers)); | ||
| 62 | } | ||
| 63 | boundaryHadX86Package = boundaryHadX86Package || (facade.Package.x64 == YesNoType.No); | ||
| 64 | |||
| 65 | orderedFacades.Add(facade); | ||
| 66 | } | ||
| 67 | else // must be a rollback boundary. | ||
| 68 | { | ||
| 69 | // Discard the next rollback boundary if we have a previously defined boundary. | ||
| 70 | WixBundleRollbackBoundaryRow nextRollbackBoundary = Boundaries.Get(row.ChildId); | ||
| 71 | if (null != previousRollbackBoundary) | ||
| 72 | { | ||
| 73 | Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.ChainPackageId)); | ||
| 74 | } | ||
| 75 | else | ||
| 76 | { | ||
| 77 | previousRollbackBoundary = nextRollbackBoundary; | ||
| 78 | lastRollbackBoundary = nextRollbackBoundary; | ||
| 79 | } | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | if (null != previousRollbackBoundary) | ||
| 85 | { | ||
| 86 | Messaging.Instance.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.ChainPackageId)); | ||
| 87 | } | ||
| 88 | |||
| 89 | // With the forward rollback boundaries assigned, we can now go | ||
| 90 | // through the packages with rollback boundaries and assign backward | ||
| 91 | // rollback boundaries. Backward rollback boundaries are used when | ||
| 92 | // the chain is going "backwards" which (AFAIK) only happens during | ||
| 93 | // uninstall. | ||
| 94 | // | ||
| 95 | // Consider the scenario with three packages: A, B and C. Packages A | ||
| 96 | // and C are marked as rollback boundary packages and package B is | ||
| 97 | // not. The naive implementation would execute the chain like this | ||
| 98 | // (numbers indicate where rollback boundaries would end up): | ||
| 99 | // install: 1 A B 2 C | ||
| 100 | // uninstall: 2 C B 1 A | ||
| 101 | // | ||
| 102 | // The uninstall chain is wrong, A and B should be grouped together | ||
| 103 | // not C and B. The fix is to label packages with a "backwards" | ||
| 104 | // rollback boundary used during uninstall. The backwards rollback | ||
| 105 | // boundaries are assigned to the package *before* the next rollback | ||
| 106 | // boundary. Using our example from above again, I'll mark the | ||
| 107 | // backwards rollback boundaries prime (aka: with '). | ||
| 108 | // install: 1 A B 1' 2 C 2' | ||
| 109 | // uninstall: 2' C 2 1' B A 1 | ||
| 110 | // | ||
| 111 | // If the marked boundaries are ignored during install you get the | ||
| 112 | // same thing as above (good) and if the non-marked boundaries are | ||
| 113 | // ignored during uninstall then A and B are correctly grouped. | ||
| 114 | // Here's what it looks like without all the markers: | ||
| 115 | // install: 1 A B 2 C | ||
| 116 | // uninstall: 2 C 1 B A | ||
| 117 | // Woot! | ||
| 118 | string previousRollbackBoundaryId = null; | ||
| 119 | PackageFacade previousFacade = null; | ||
| 120 | |||
| 121 | foreach (PackageFacade package in orderedFacades) | ||
| 122 | { | ||
| 123 | if (null != package.Package.RollbackBoundary) | ||
| 124 | { | ||
| 125 | if (null != previousFacade) | ||
| 126 | { | ||
| 127 | previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; | ||
| 128 | } | ||
| 129 | |||
| 130 | previousRollbackBoundaryId = package.Package.RollbackBoundary; | ||
| 131 | } | ||
| 132 | |||
| 133 | previousFacade = package; | ||
| 134 | } | ||
| 135 | |||
| 136 | if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousFacade) | ||
| 137 | { | ||
| 138 | previousFacade.Package.RollbackBoundaryBackward = previousRollbackBoundaryId; | ||
| 139 | } | ||
| 140 | |||
| 141 | this.OrderedPackageFacades = orderedFacades; | ||
| 142 | this.UsedRollbackBoundaries = usedBoundaries; | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs new file mode 100644 index 00000000..3f2e184d --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/PackageFacade.cs | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using WixToolset.Data.Rows; | ||
| 6 | |||
| 7 | internal class PackageFacade | ||
| 8 | { | ||
| 9 | private PackageFacade(WixBundlePackageRow package) | ||
| 10 | { | ||
| 11 | this.Package = package; | ||
| 12 | this.Provides = new ProvidesDependencyCollection(); | ||
| 13 | } | ||
| 14 | |||
| 15 | public PackageFacade(WixBundlePackageRow package, WixBundleExePackageRow exePackage) | ||
| 16 | : this(package) | ||
| 17 | { | ||
| 18 | this.ExePackage = exePackage; | ||
| 19 | } | ||
| 20 | |||
| 21 | public PackageFacade(WixBundlePackageRow package, WixBundleMsiPackageRow msiPackage) | ||
| 22 | : this(package) | ||
| 23 | { | ||
| 24 | this.MsiPackage = msiPackage; | ||
| 25 | } | ||
| 26 | |||
| 27 | public PackageFacade(WixBundlePackageRow package, WixBundleMspPackageRow mspPackage) | ||
| 28 | : this(package) | ||
| 29 | { | ||
| 30 | this.MspPackage = mspPackage; | ||
| 31 | } | ||
| 32 | |||
| 33 | public PackageFacade(WixBundlePackageRow package, WixBundleMsuPackageRow msuPackage) | ||
| 34 | : this(package) | ||
| 35 | { | ||
| 36 | this.MsuPackage = msuPackage; | ||
| 37 | } | ||
| 38 | |||
| 39 | public WixBundlePackageRow Package { get; private set; } | ||
| 40 | |||
| 41 | public WixBundleExePackageRow ExePackage { get; private set; } | ||
| 42 | |||
| 43 | public WixBundleMsiPackageRow MsiPackage { get; private set; } | ||
| 44 | |||
| 45 | public WixBundleMspPackageRow MspPackage { get; private set; } | ||
| 46 | |||
| 47 | public WixBundleMsuPackageRow MsuPackage { get; private set; } | ||
| 48 | |||
| 49 | /// <summary> | ||
| 50 | /// The provides dependencies authored and imported for this package. | ||
| 51 | /// </summary> | ||
| 52 | /// <remarks> | ||
| 53 | /// TODO: Eventually this collection should turn into Rows so they are tracked in the PDB but | ||
| 54 | /// the relationship with the extension makes it much trickier to pull off. | ||
| 55 | /// </remarks> | ||
| 56 | public ProvidesDependencyCollection Provides { get; private set; } | ||
| 57 | } | ||
| 58 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs new file mode 100644 index 00000000..11512c39 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Rows; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Initializes package state from the Exe contents. | ||
| 11 | /// </summary> | ||
| 12 | internal class ProcessExePackageCommand | ||
| 13 | { | ||
| 14 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 15 | |||
| 16 | public PackageFacade Facade { private get; set; } | ||
| 17 | |||
| 18 | /// <summary> | ||
| 19 | /// Processes the Exe packages to add properties and payloads from the Exe packages. | ||
| 20 | /// </summary> | ||
| 21 | public void Execute() | ||
| 22 | { | ||
| 23 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 24 | |||
| 25 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 26 | { | ||
| 27 | this.Facade.Package.CacheId = packagePayload.Hash; | ||
| 28 | } | ||
| 29 | |||
| 30 | this.Facade.Package.Version = packagePayload.Version; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs new file mode 100644 index 00000000..322187f9 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs | |||
| @@ -0,0 +1,576 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Collections.Generic; | ||
| 8 | using System.Diagnostics; | ||
| 9 | using System.Globalization; | ||
| 10 | using System.IO; | ||
| 11 | using System.Linq; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using WixToolset.Extensibility; | ||
| 15 | using WixToolset.Core.Native; | ||
| 16 | using Dtf = WixToolset.Dtf.WindowsInstaller; | ||
| 17 | using WixToolset.Bind; | ||
| 18 | using WixToolset.Data.Bind; | ||
| 19 | |||
| 20 | /// <summary> | ||
| 21 | /// Initializes package state from the MSI contents. | ||
| 22 | /// </summary> | ||
| 23 | internal class ProcessMsiPackageCommand | ||
| 24 | { | ||
| 25 | private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'"; | ||
| 26 | |||
| 27 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 28 | |||
| 29 | public PackageFacade Facade { private get; set; } | ||
| 30 | |||
| 31 | public IEnumerable<IBurnBackendExtension> BackendExtensions { private get; set; } | ||
| 32 | |||
| 33 | public Table MsiFeatureTable { private get; set; } | ||
| 34 | |||
| 35 | public Table MsiPropertyTable { private get; set; } | ||
| 36 | |||
| 37 | public Table PayloadTable { private get; set; } | ||
| 38 | |||
| 39 | public Table RelatedPackageTable { private get; set; } | ||
| 40 | |||
| 41 | /// <summary> | ||
| 42 | /// Processes the MSI packages to add properties and payloads from the MSI packages. | ||
| 43 | /// </summary> | ||
| 44 | public void Execute() | ||
| 45 | { | ||
| 46 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 47 | |||
| 48 | string sourcePath = packagePayload.FullFileName; | ||
| 49 | bool longNamesInImage = false; | ||
| 50 | bool compressed = false; | ||
| 51 | bool x64 = false; | ||
| 52 | try | ||
| 53 | { | ||
| 54 | // Read data out of the msi database... | ||
| 55 | using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) | ||
| 56 | { | ||
| 57 | // 1 is the Word Count summary information stream bit that means | ||
| 58 | // the MSI uses short file names when set. We care about long file | ||
| 59 | // names so check when the bit is not set. | ||
| 60 | longNamesInImage = 0 == (sumInfo.WordCount & 1); | ||
| 61 | |||
| 62 | // 2 is the Word Count summary information stream bit that means | ||
| 63 | // files are compressed in the MSI by default when the bit is set. | ||
| 64 | compressed = 2 == (sumInfo.WordCount & 2); | ||
| 65 | |||
| 66 | x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64")); | ||
| 67 | |||
| 68 | // 8 is the Word Count summary information stream bit that means | ||
| 69 | // "Elevated privileges are not required to install this package." | ||
| 70 | // in MSI 4.5 and below, if this bit is 0, elevation is required. | ||
| 71 | this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No; | ||
| 72 | this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No; | ||
| 73 | } | ||
| 74 | |||
| 75 | using (Dtf.Database db = new Dtf.Database(sourcePath)) | ||
| 76 | { | ||
| 77 | this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode"); | ||
| 78 | this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode"); | ||
| 79 | this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer"); | ||
| 80 | this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture); | ||
| 81 | this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion"); | ||
| 82 | |||
| 83 | if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion)) | ||
| 84 | { | ||
| 85 | // not a proper .NET version (e.g., five fields); can we get a valid four-part version number? | ||
| 86 | string version = null; | ||
| 87 | string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.'); | ||
| 88 | int count = versionParts.Length; | ||
| 89 | if (0 < count) | ||
| 90 | { | ||
| 91 | version = versionParts[0]; | ||
| 92 | for (int i = 1; i < 4 && i < count; ++i) | ||
| 93 | { | ||
| 94 | version = String.Concat(version, ".", versionParts[i]); | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version)) | ||
| 99 | { | ||
| 100 | Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version)); | ||
| 101 | this.Facade.MsiPackage.ProductVersion = version; | ||
| 102 | } | ||
| 103 | else | ||
| 104 | { | ||
| 105 | Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath)); | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 110 | { | ||
| 111 | this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion); | ||
| 112 | } | ||
| 113 | |||
| 114 | if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) | ||
| 115 | { | ||
| 116 | this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName"); | ||
| 117 | } | ||
| 118 | |||
| 119 | if (String.IsNullOrEmpty(this.Facade.Package.Description)) | ||
| 120 | { | ||
| 121 | this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS"); | ||
| 122 | } | ||
| 123 | |||
| 124 | ISet<string> payloadNames = this.GetPayloadTargetNames(); | ||
| 125 | |||
| 126 | ISet<string> msiPropertyNames = this.GetMsiPropertyNames(); | ||
| 127 | |||
| 128 | this.SetPerMachineAppropriately(db, sourcePath); | ||
| 129 | |||
| 130 | // Ensure the MSI package is appropriately marked visible or not. | ||
| 131 | this.SetPackageVisibility(db, msiPropertyNames); | ||
| 132 | |||
| 133 | // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. | ||
| 134 | if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL")) | ||
| 135 | { | ||
| 136 | this.AddMsiProperty("MSIFASTINSTALL", "7"); | ||
| 137 | } | ||
| 138 | |||
| 139 | this.CreateRelatedPackages(db); | ||
| 140 | |||
| 141 | // If feature selection is enabled, represent the Feature table in the manifest. | ||
| 142 | if (this.Facade.MsiPackage.EnableFeatureSelection) | ||
| 143 | { | ||
| 144 | this.CreateMsiFeatures(db); | ||
| 145 | } | ||
| 146 | |||
| 147 | // Add all external cabinets as package payloads. | ||
| 148 | this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames); | ||
| 149 | |||
| 150 | // Add all external files as package payloads and calculate the total install size as the rollup of | ||
| 151 | // File table's sizes. | ||
| 152 | this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames); | ||
| 153 | |||
| 154 | // Add all dependency providers from the MSI. | ||
| 155 | this.ImportDependencyProviders(db); | ||
| 156 | } | ||
| 157 | } | ||
| 158 | catch (Dtf.InstallerException e) | ||
| 159 | { | ||
| 160 | Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message)); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | private ISet<string> GetPayloadTargetNames() | ||
| 165 | { | ||
| 166 | IEnumerable<string> payloadNames = this.PayloadTable.RowsAs<WixBundlePayloadRow>() | ||
| 167 | .Where(r => r.Package == this.Facade.Package.WixChainItemId) | ||
| 168 | .Select(r => r.Name); | ||
| 169 | |||
| 170 | return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase); | ||
| 171 | } | ||
| 172 | |||
| 173 | private ISet<string> GetMsiPropertyNames() | ||
| 174 | { | ||
| 175 | IEnumerable<string> properties = this.MsiPropertyTable.RowsAs<WixBundleMsiPropertyRow>() | ||
| 176 | .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId) | ||
| 177 | .Select(r => r.Name); | ||
| 178 | |||
| 179 | return new HashSet<string>(properties, StringComparer.Ordinal); | ||
| 180 | } | ||
| 181 | |||
| 182 | private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath) | ||
| 183 | { | ||
| 184 | if (this.Facade.MsiPackage.ForcePerMachine) | ||
| 185 | { | ||
| 186 | if (YesNoDefaultType.No == this.Facade.Package.PerMachine) | ||
| 187 | { | ||
| 188 | Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath)); | ||
| 189 | this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine. | ||
| 190 | } | ||
| 191 | |||
| 192 | // Force ALLUSERS=1 via the MSI command-line. | ||
| 193 | this.AddMsiProperty("ALLUSERS", "1"); | ||
| 194 | } | ||
| 195 | else | ||
| 196 | { | ||
| 197 | string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS"); | ||
| 198 | |||
| 199 | if (String.IsNullOrEmpty(allusers)) | ||
| 200 | { | ||
| 201 | // Not forced per-machine and no ALLUSERS property, flip back to per-user. | ||
| 202 | if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) | ||
| 203 | { | ||
| 204 | Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath)); | ||
| 205 | this.Facade.Package.PerMachine = YesNoDefaultType.No; | ||
| 206 | } | ||
| 207 | } | ||
| 208 | else if (allusers.Equals("1", StringComparison.Ordinal)) | ||
| 209 | { | ||
| 210 | if (YesNoDefaultType.No == this.Facade.Package.PerMachine) | ||
| 211 | { | ||
| 212 | Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath)); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | else if (allusers.Equals("2", StringComparison.Ordinal)) | ||
| 216 | { | ||
| 217 | Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user")); | ||
| 218 | } | ||
| 219 | else | ||
| 220 | { | ||
| 221 | Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers)); | ||
| 222 | } | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | private void SetPackageVisibility(Dtf.Database db, ISet<string> msiPropertyNames) | ||
| 227 | { | ||
| 228 | bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT"); | ||
| 229 | |||
| 230 | if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility. | ||
| 231 | { | ||
| 232 | // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. | ||
| 233 | if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) | ||
| 234 | { | ||
| 235 | this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1"); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | private void CreateRelatedPackages(Dtf.Database db) | ||
| 241 | { | ||
| 242 | // Represent the Upgrade table as related packages. | ||
| 243 | if (db.Tables.Contains("Upgrade")) | ||
| 244 | { | ||
| 245 | using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) | ||
| 246 | { | ||
| 247 | view.Execute(); | ||
| 248 | while (true) | ||
| 249 | { | ||
| 250 | using (Dtf.Record record = view.Fetch()) | ||
| 251 | { | ||
| 252 | if (null == record) | ||
| 253 | { | ||
| 254 | break; | ||
| 255 | } | ||
| 256 | |||
| 257 | WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 258 | related.ChainPackageId = this.Facade.Package.WixChainItemId; | ||
| 259 | related.Id = record.GetString(1); | ||
| 260 | related.MinVersion = record.GetString(2); | ||
| 261 | related.MaxVersion = record.GetString(3); | ||
| 262 | related.Languages = record.GetString(4); | ||
| 263 | |||
| 264 | int attributes = record.GetInteger(5); | ||
| 265 | related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect; | ||
| 266 | related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; | ||
| 267 | related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; | ||
| 268 | related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0; | ||
| 269 | } | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | private void CreateMsiFeatures(Dtf.Database db) | ||
| 276 | { | ||
| 277 | if (db.Tables.Contains("Feature")) | ||
| 278 | { | ||
| 279 | using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) | ||
| 280 | using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) | ||
| 281 | { | ||
| 282 | using (Dtf.Record featureRecord = new Dtf.Record(1)) | ||
| 283 | using (Dtf.Record componentRecord = new Dtf.Record(1)) | ||
| 284 | { | ||
| 285 | using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) | ||
| 286 | { | ||
| 287 | allFeaturesView.Execute(); | ||
| 288 | |||
| 289 | while (true) | ||
| 290 | { | ||
| 291 | using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch()) | ||
| 292 | { | ||
| 293 | if (null == allFeaturesResultRecord) | ||
| 294 | { | ||
| 295 | break; | ||
| 296 | } | ||
| 297 | |||
| 298 | string featureName = allFeaturesResultRecord.GetString(1); | ||
| 299 | |||
| 300 | // Calculate the Feature size. | ||
| 301 | featureRecord.SetString(1, featureName); | ||
| 302 | featureView.Execute(featureRecord); | ||
| 303 | |||
| 304 | // Loop over all the components for the feature to calculate the size of the feature. | ||
| 305 | long size = 0; | ||
| 306 | while (true) | ||
| 307 | { | ||
| 308 | using (Dtf.Record componentResultRecord = featureView.Fetch()) | ||
| 309 | { | ||
| 310 | if (null == componentResultRecord) | ||
| 311 | { | ||
| 312 | break; | ||
| 313 | } | ||
| 314 | string component = componentResultRecord.GetString(1); | ||
| 315 | componentRecord.SetString(1, component); | ||
| 316 | componentView.Execute(componentRecord); | ||
| 317 | |||
| 318 | while (true) | ||
| 319 | { | ||
| 320 | using (Dtf.Record fileResultRecord = componentView.Fetch()) | ||
| 321 | { | ||
| 322 | if (null == fileResultRecord) | ||
| 323 | { | ||
| 324 | break; | ||
| 325 | } | ||
| 326 | |||
| 327 | string fileSize = fileResultRecord.GetString(1); | ||
| 328 | size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | } | ||
| 333 | |||
| 334 | WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 335 | feature.ChainPackageId = this.Facade.Package.WixChainItemId; | ||
| 336 | feature.Name = featureName; | ||
| 337 | feature.Parent = allFeaturesResultRecord.GetString(2); | ||
| 338 | feature.Title = allFeaturesResultRecord.GetString(3); | ||
| 339 | feature.Description = allFeaturesResultRecord.GetString(4); | ||
| 340 | feature.Display = allFeaturesResultRecord.GetInteger(5); | ||
| 341 | feature.Level = allFeaturesResultRecord.GetInteger(6); | ||
| 342 | feature.Directory = allFeaturesResultRecord.GetString(7); | ||
| 343 | feature.Attributes = allFeaturesResultRecord.GetInteger(8); | ||
| 344 | feature.Size = size; | ||
| 345 | } | ||
| 346 | } | ||
| 347 | } | ||
| 348 | } | ||
| 349 | } | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet<string> payloadNames) | ||
| 354 | { | ||
| 355 | if (db.Tables.Contains("Media")) | ||
| 356 | { | ||
| 357 | foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) | ||
| 358 | { | ||
| 359 | if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
| 360 | { | ||
| 361 | // If we didn't find the Payload as an existing child of the package, we need to | ||
| 362 | // add it. We expect the file to exist on-disk in the same relative location as | ||
| 363 | // the MSI expects to find it... | ||
| 364 | string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet); | ||
| 365 | |||
| 366 | if (!payloadNames.Contains(cabinetName)) | ||
| 367 | { | ||
| 368 | string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet); | ||
| 369 | string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal); | ||
| 370 | |||
| 371 | WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 372 | payload.Id = generatedId; | ||
| 373 | payload.Name = cabinetName; | ||
| 374 | payload.SourceFile = payloadSourceFile; | ||
| 375 | payload.Compressed = packagePayload.Compressed; | ||
| 376 | payload.UnresolvedSourceFile = cabinetName; | ||
| 377 | payload.Package = packagePayload.Package; | ||
| 378 | payload.Container = packagePayload.Container; | ||
| 379 | payload.ContentFile = true; | ||
| 380 | payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; | ||
| 381 | payload.Packaging = packagePayload.Packaging; | ||
| 382 | payload.ParentPackagePayload = packagePayload.Id; | ||
| 383 | } | ||
| 384 | } | ||
| 385 | } | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 389 | private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames) | ||
| 390 | { | ||
| 391 | long size = 0; | ||
| 392 | |||
| 393 | if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File")) | ||
| 394 | { | ||
| 395 | Hashtable directories = new Hashtable(); | ||
| 396 | |||
| 397 | // Load up the directory hash table so we will be able to resolve source paths | ||
| 398 | // for files in the MSI database. | ||
| 399 | using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) | ||
| 400 | { | ||
| 401 | view.Execute(); | ||
| 402 | while (true) | ||
| 403 | { | ||
| 404 | using (Dtf.Record record = view.Fetch()) | ||
| 405 | { | ||
| 406 | if (null == record) | ||
| 407 | { | ||
| 408 | break; | ||
| 409 | } | ||
| 410 | |||
| 411 | string sourceName = Common.GetName(record.GetString(3), true, longNamesInImage); | ||
| 412 | directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName)); | ||
| 413 | } | ||
| 414 | } | ||
| 415 | } | ||
| 416 | |||
| 417 | // Resolve the source paths to external files and add each file size to the total | ||
| 418 | // install size of the package. | ||
| 419 | using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) | ||
| 420 | { | ||
| 421 | view.Execute(); | ||
| 422 | while (true) | ||
| 423 | { | ||
| 424 | using (Dtf.Record record = view.Fetch()) | ||
| 425 | { | ||
| 426 | if (null == record) | ||
| 427 | { | ||
| 428 | break; | ||
| 429 | } | ||
| 430 | |||
| 431 | // Skip adding the loose files as payloads if it was suppressed. | ||
| 432 | if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration) | ||
| 433 | { | ||
| 434 | // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not | ||
| 435 | // explicitly marked compressed then this is an external file. | ||
| 436 | if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) || | ||
| 437 | (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed))) | ||
| 438 | { | ||
| 439 | string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); | ||
| 440 | string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath); | ||
| 441 | |||
| 442 | if (!payloadNames.Contains(name)) | ||
| 443 | { | ||
| 444 | string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2)); | ||
| 445 | string payloadSourceFile = this.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal); | ||
| 446 | |||
| 447 | WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers); | ||
| 448 | payload.Id = generatedId; | ||
| 449 | payload.Name = name; | ||
| 450 | payload.SourceFile = payloadSourceFile; | ||
| 451 | payload.Compressed = packagePayload.Compressed; | ||
| 452 | payload.UnresolvedSourceFile = name; | ||
| 453 | payload.Package = packagePayload.Package; | ||
| 454 | payload.Container = packagePayload.Container; | ||
| 455 | payload.ContentFile = true; | ||
| 456 | payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation; | ||
| 457 | payload.Packaging = packagePayload.Packaging; | ||
| 458 | payload.ParentPackagePayload = packagePayload.Id; | ||
| 459 | } | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 463 | size += record.GetInteger(5); | ||
| 464 | } | ||
| 465 | } | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | return size; | ||
| 470 | } | ||
| 471 | |||
| 472 | private void AddMsiProperty(string name, string value) | ||
| 473 | { | ||
| 474 | WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers); | ||
| 475 | row.ChainPackageId = this.Facade.Package.WixChainItemId; | ||
| 476 | row.Name = name; | ||
| 477 | row.Value = value; | ||
| 478 | } | ||
| 479 | |||
| 480 | private void ImportDependencyProviders(Dtf.Database db) | ||
| 481 | { | ||
| 482 | if (db.Tables.Contains("WixDependencyProvider")) | ||
| 483 | { | ||
| 484 | string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; | ||
| 485 | |||
| 486 | using (Dtf.View view = db.OpenView(query)) | ||
| 487 | { | ||
| 488 | view.Execute(); | ||
| 489 | while (true) | ||
| 490 | { | ||
| 491 | using (Dtf.Record record = view.Fetch()) | ||
| 492 | { | ||
| 493 | if (null == record) | ||
| 494 | { | ||
| 495 | break; | ||
| 496 | } | ||
| 497 | |||
| 498 | // Import the provider key and attributes. | ||
| 499 | string providerKey = record.GetString(1); | ||
| 500 | string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion; | ||
| 501 | string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName; | ||
| 502 | int attributes = record.GetInteger(4); | ||
| 503 | |||
| 504 | ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes); | ||
| 505 | dependency.Imported = true; | ||
| 506 | |||
| 507 | this.Facade.Provides.Add(dependency); | ||
| 508 | } | ||
| 509 | } | ||
| 510 | } | ||
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | private string ResolveRelatedFile(string sourceFile, string relatedSource, string type, SourceLineNumber sourceLineNumbers, BindStage stage) | ||
| 515 | { | ||
| 516 | foreach (var extension in this.BackendExtensions) | ||
| 517 | { | ||
| 518 | var relatedFile = extension.ResolveRelatedFile(sourceFile, relatedSource, type, sourceLineNumbers, stage); | ||
| 519 | |||
| 520 | if (!String.IsNullOrEmpty(relatedFile)) | ||
| 521 | { | ||
| 522 | return relatedFile; | ||
| 523 | } | ||
| 524 | } | ||
| 525 | |||
| 526 | return null; | ||
| 527 | } | ||
| 528 | |||
| 529 | /// <summary> | ||
| 530 | /// Queries a Windows Installer database for a Property value. | ||
| 531 | /// </summary> | ||
| 532 | /// <param name="db">Database to query.</param> | ||
| 533 | /// <param name="property">Property to examine.</param> | ||
| 534 | /// <returns>String value for result or null if query doesn't match a single result.</returns> | ||
| 535 | private static string GetProperty(Dtf.Database db, string property) | ||
| 536 | { | ||
| 537 | try | ||
| 538 | { | ||
| 539 | return db.ExecuteScalar(PropertyQuery(property)).ToString(); | ||
| 540 | } | ||
| 541 | catch (Dtf.InstallerException) | ||
| 542 | { | ||
| 543 | } | ||
| 544 | |||
| 545 | return null; | ||
| 546 | } | ||
| 547 | |||
| 548 | /// <summary> | ||
| 549 | /// Queries a Windows Installer database to determine if one or more rows exist in the Property table. | ||
| 550 | /// </summary> | ||
| 551 | /// <param name="db">Database to query.</param> | ||
| 552 | /// <param name="property">Property to examine.</param> | ||
| 553 | /// <returns>True if query matches at least one result.</returns> | ||
| 554 | private static bool HasProperty(Dtf.Database db, string property) | ||
| 555 | { | ||
| 556 | try | ||
| 557 | { | ||
| 558 | return 0 < db.ExecuteQuery(PropertyQuery(property)).Count; | ||
| 559 | } | ||
| 560 | catch (Dtf.InstallerException) | ||
| 561 | { | ||
| 562 | } | ||
| 563 | |||
| 564 | return false; | ||
| 565 | } | ||
| 566 | |||
| 567 | private static string PropertyQuery(string property) | ||
| 568 | { | ||
| 569 | // quick sanity check that we'll be creating a valid query... | ||
| 570 | // TODO: Are there any other special characters we should be looking for? | ||
| 571 | Debug.Assert(!property.Contains("'")); | ||
| 572 | |||
| 573 | return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property); | ||
| 574 | } | ||
| 575 | } | ||
| 576 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs new file mode 100644 index 00000000..2d849d03 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs | |||
| @@ -0,0 +1,189 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.Globalization; | ||
| 9 | using System.IO; | ||
| 10 | using System.Text; | ||
| 11 | using System.Xml; | ||
| 12 | using WixToolset.Data; | ||
| 13 | using WixToolset.Data.Rows; | ||
| 14 | using Dtf = WixToolset.Dtf.WindowsInstaller; | ||
| 15 | |||
| 16 | /// <summary> | ||
| 17 | /// Initializes package state from the Msp contents. | ||
| 18 | /// </summary> | ||
| 19 | internal class ProcessMspPackageCommand | ||
| 20 | { | ||
| 21 | private const string PatchMetadataFormat = "SELECT `Value` FROM `MsiPatchMetadata` WHERE `Property` = '{0}'"; | ||
| 22 | private static readonly Encoding XmlOutputEncoding = new UTF8Encoding(false); | ||
| 23 | |||
| 24 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 25 | |||
| 26 | public PackageFacade Facade { private get; set; } | ||
| 27 | |||
| 28 | public Table WixBundlePatchTargetCodeTable { private get; set; } | ||
| 29 | |||
| 30 | /// <summary> | ||
| 31 | /// Processes the Msp packages to add properties and payloads from the Msp packages. | ||
| 32 | /// </summary> | ||
| 33 | public void Execute() | ||
| 34 | { | ||
| 35 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 36 | |||
| 37 | string sourcePath = packagePayload.FullFileName; | ||
| 38 | |||
| 39 | try | ||
| 40 | { | ||
| 41 | // Read data out of the msp database... | ||
| 42 | using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false)) | ||
| 43 | { | ||
| 44 | this.Facade.MspPackage.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); | ||
| 45 | } | ||
| 46 | |||
| 47 | using (Dtf.Database db = new Dtf.Database(sourcePath)) | ||
| 48 | { | ||
| 49 | if (String.IsNullOrEmpty(this.Facade.Package.DisplayName)) | ||
| 50 | { | ||
| 51 | this.Facade.Package.DisplayName = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "DisplayName"); | ||
| 52 | } | ||
| 53 | |||
| 54 | if (String.IsNullOrEmpty(this.Facade.Package.Description)) | ||
| 55 | { | ||
| 56 | this.Facade.Package.Description = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "Description"); | ||
| 57 | } | ||
| 58 | |||
| 59 | this.Facade.MspPackage.Manufacturer = ProcessMspPackageCommand.GetPatchMetadataProperty(db, "ManufacturerName"); | ||
| 60 | } | ||
| 61 | |||
| 62 | this.ProcessPatchXml(packagePayload, sourcePath); | ||
| 63 | } | ||
| 64 | catch (Dtf.InstallerException e) | ||
| 65 | { | ||
| 66 | Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(packagePayload.SourceLineNumbers, sourcePath, e.Message)); | ||
| 67 | return; | ||
| 68 | } | ||
| 69 | |||
| 70 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 71 | { | ||
| 72 | this.Facade.Package.CacheId = this.Facade.MspPackage.PatchCode; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | |||
| 76 | private void ProcessPatchXml(WixBundlePayloadRow packagePayload, string sourcePath) | ||
| 77 | { | ||
| 78 | HashSet<string> uniqueTargetCodes = new HashSet<string>(); | ||
| 79 | |||
| 80 | string patchXml = Dtf.Installer.ExtractPatchXmlData(sourcePath); | ||
| 81 | |||
| 82 | XmlDocument doc = new XmlDocument(); | ||
| 83 | doc.LoadXml(patchXml); | ||
| 84 | |||
| 85 | XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); | ||
| 86 | nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); | ||
| 87 | |||
| 88 | // Determine target ProductCodes and/or UpgradeCodes. | ||
| 89 | foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) | ||
| 90 | { | ||
| 91 | // If this patch targets a product code, this is the best case. | ||
| 92 | XmlNode targetCodeElement = node.SelectSingleNode("p:TargetProductCode", nsmgr); | ||
| 93 | WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; | ||
| 94 | |||
| 95 | if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) | ||
| 96 | { | ||
| 97 | attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; | ||
| 98 | } | ||
| 99 | else // maybe targets an upgrade code? | ||
| 100 | { | ||
| 101 | targetCodeElement = node.SelectSingleNode("p:UpgradeCode", nsmgr); | ||
| 102 | if (ProcessMspPackageCommand.TargetsCode(targetCodeElement)) | ||
| 103 | { | ||
| 104 | attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; | ||
| 105 | } | ||
| 106 | else // this patch targets an unknown number of products | ||
| 107 | { | ||
| 108 | this.Facade.MspPackage.Attributes |= WixBundleMspPackageAttributes.TargetUnspecified; | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | string targetCode = targetCodeElement.InnerText; | ||
| 113 | |||
| 114 | if (uniqueTargetCodes.Add(targetCode)) | ||
| 115 | { | ||
| 116 | WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)this.WixBundlePatchTargetCodeTable.CreateRow(packagePayload.SourceLineNumbers); | ||
| 117 | row.MspPackageId = packagePayload.Id; | ||
| 118 | row.TargetCode = targetCode; | ||
| 119 | row.Attributes = attributes; | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | // Suppress patch sequence data for improved performance. | ||
| 124 | XmlNode root = doc.DocumentElement; | ||
| 125 | foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) | ||
| 126 | { | ||
| 127 | root.RemoveChild(node); | ||
| 128 | } | ||
| 129 | |||
| 130 | // Save the XML as compact as possible. | ||
| 131 | using (StringWriter writer = new StringWriter()) | ||
| 132 | { | ||
| 133 | XmlWriterSettings settings = new XmlWriterSettings() | ||
| 134 | { | ||
| 135 | Encoding = ProcessMspPackageCommand.XmlOutputEncoding, | ||
| 136 | Indent = false, | ||
| 137 | NewLineChars = string.Empty, | ||
| 138 | NewLineHandling = NewLineHandling.Replace, | ||
| 139 | }; | ||
| 140 | |||
| 141 | using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) | ||
| 142 | { | ||
| 143 | doc.WriteTo(xmlWriter); | ||
| 144 | } | ||
| 145 | |||
| 146 | this.Facade.MspPackage.PatchXml = writer.ToString(); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | /// <summary> | ||
| 151 | /// Queries a Windows Installer patch database for a Property value from the MsiPatchMetadata table. | ||
| 152 | /// </summary> | ||
| 153 | /// <param name="db">Database to query.</param> | ||
| 154 | /// <param name="property">Property to examine.</param> | ||
| 155 | /// <returns>String value for result or null if query doesn't match a single result.</returns> | ||
| 156 | private static string GetPatchMetadataProperty(Dtf.Database db, string property) | ||
| 157 | { | ||
| 158 | try | ||
| 159 | { | ||
| 160 | return db.ExecuteScalar(PatchMetadataPropertyQuery(property)).ToString(); | ||
| 161 | } | ||
| 162 | catch (Dtf.InstallerException) | ||
| 163 | { | ||
| 164 | } | ||
| 165 | |||
| 166 | return null; | ||
| 167 | } | ||
| 168 | |||
| 169 | private static string PatchMetadataPropertyQuery(string property) | ||
| 170 | { | ||
| 171 | // quick sanity check that we'll be creating a valid query... | ||
| 172 | // TODO: Are there any other special characters we should be looking for? | ||
| 173 | Debug.Assert(!property.Contains("'")); | ||
| 174 | |||
| 175 | return String.Format(CultureInfo.InvariantCulture, ProcessMspPackageCommand.PatchMetadataFormat, property); | ||
| 176 | } | ||
| 177 | |||
| 178 | private static bool TargetsCode(XmlNode node) | ||
| 179 | { | ||
| 180 | if (null != node) | ||
| 181 | { | ||
| 182 | XmlAttribute attr = node.Attributes["Validate"]; | ||
| 183 | return null != attr && "true".Equals(attr.Value); | ||
| 184 | } | ||
| 185 | |||
| 186 | return false; | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs new file mode 100644 index 00000000..fcfc780c --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using WixToolset.Data; | ||
| 7 | using WixToolset.Data.Rows; | ||
| 8 | |||
| 9 | /// <summary> | ||
| 10 | /// Processes the Msu packages to add properties and payloads from the Msu packages. | ||
| 11 | /// </summary> | ||
| 12 | internal class ProcessMsuPackageCommand | ||
| 13 | { | ||
| 14 | public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; } | ||
| 15 | |||
| 16 | public PackageFacade Facade { private get; set; } | ||
| 17 | |||
| 18 | public void Execute() | ||
| 19 | { | ||
| 20 | WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload); | ||
| 21 | |||
| 22 | if (String.IsNullOrEmpty(this.Facade.Package.CacheId)) | ||
| 23 | { | ||
| 24 | this.Facade.Package.CacheId = packagePayload.Hash; | ||
| 25 | } | ||
| 26 | |||
| 27 | this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // MSUs are always per-machine. | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs new file mode 100644 index 00000000..5dbd6aaa --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs | |||
| @@ -0,0 +1,161 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.Diagnostics; | ||
| 8 | using System.IO; | ||
| 9 | using System.Security.Cryptography; | ||
| 10 | using System.Security.Cryptography.X509Certificates; | ||
| 11 | using System.Text; | ||
| 12 | using WixToolset.Bind; | ||
| 13 | using WixToolset.Data; | ||
| 14 | using WixToolset.Data.Bind; | ||
| 15 | using WixToolset.Data.Rows; | ||
| 16 | |||
| 17 | internal class ProcessPayloadsCommand | ||
| 18 | { | ||
| 19 | private static readonly Version EmptyVersion = new Version(0, 0, 0, 0); | ||
| 20 | |||
| 21 | public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 22 | |||
| 23 | public PackagingType DefaultPackaging { private get; set; } | ||
| 24 | |||
| 25 | public string LayoutDirectory { private get; set; } | ||
| 26 | |||
| 27 | public IEnumerable<FileTransfer> FileTransfers { get; private set; } | ||
| 28 | |||
| 29 | public void Execute() | ||
| 30 | { | ||
| 31 | List<FileTransfer> fileTransfers = new List<FileTransfer>(); | ||
| 32 | |||
| 33 | foreach (WixBundlePayloadRow payload in this.Payloads) | ||
| 34 | { | ||
| 35 | string normalizedPath = payload.Name.Replace('\\', '/'); | ||
| 36 | if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../")) | ||
| 37 | { | ||
| 38 | Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name)); | ||
| 39 | } | ||
| 40 | |||
| 41 | // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden | ||
| 42 | // in the .wixlib). | ||
| 43 | ObjectField field = (ObjectField)payload.Fields[2]; | ||
| 44 | payload.ContentFile = !field.EmbeddedFileIndex.HasValue; | ||
| 45 | |||
| 46 | this.UpdatePayloadPackagingType(payload); | ||
| 47 | |||
| 48 | if (String.IsNullOrEmpty(payload.SourceFile)) | ||
| 49 | { | ||
| 50 | // Remote payloads obviously cannot be embedded. | ||
| 51 | Debug.Assert(PackagingType.Embedded != payload.Packaging); | ||
| 52 | } | ||
| 53 | else // not a remote payload so we have a lot more to update. | ||
| 54 | { | ||
| 55 | this.UpdatePayloadFileInformation(payload); | ||
| 56 | |||
| 57 | this.UpdatePayloadVersionInformation(payload); | ||
| 58 | |||
| 59 | // External payloads need to be transfered. | ||
| 60 | if (PackagingType.External == payload.Packaging) | ||
| 61 | { | ||
| 62 | FileTransfer transfer; | ||
| 63 | if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer)) | ||
| 64 | { | ||
| 65 | fileTransfers.Add(transfer); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | this.FileTransfers = fileTransfers; | ||
| 72 | } | ||
| 73 | |||
| 74 | private void UpdatePayloadPackagingType(WixBundlePayloadRow payload) | ||
| 75 | { | ||
| 76 | if (PackagingType.Unknown == payload.Packaging) | ||
| 77 | { | ||
| 78 | if (YesNoDefaultType.Yes == payload.Compressed) | ||
| 79 | { | ||
| 80 | payload.Packaging = PackagingType.Embedded; | ||
| 81 | } | ||
| 82 | else if (YesNoDefaultType.No == payload.Compressed) | ||
| 83 | { | ||
| 84 | payload.Packaging = PackagingType.External; | ||
| 85 | } | ||
| 86 | else | ||
| 87 | { | ||
| 88 | payload.Packaging = this.DefaultPackaging; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | // Embedded payloads that are not assigned a container already are placed in the default attached | ||
| 93 | // container. | ||
| 94 | if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container)) | ||
| 95 | { | ||
| 96 | payload.Container = Compiler.BurnDefaultAttachedContainerId; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | private void UpdatePayloadFileInformation(WixBundlePayloadRow payload) | ||
| 101 | { | ||
| 102 | FileInfo fileInfo = new FileInfo(payload.SourceFile); | ||
| 103 | |||
| 104 | if (null != fileInfo) | ||
| 105 | { | ||
| 106 | payload.FileSize = (int)fileInfo.Length; | ||
| 107 | |||
| 108 | payload.Hash = Common.GetFileHash(fileInfo.FullName); | ||
| 109 | |||
| 110 | // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation. | ||
| 111 | if (payload.EnableSignatureValidation) | ||
| 112 | { | ||
| 113 | X509Certificate2 certificate = null; | ||
| 114 | try | ||
| 115 | { | ||
| 116 | certificate = new X509Certificate2(fileInfo.FullName); | ||
| 117 | } | ||
| 118 | catch (CryptographicException) // we don't care about non-signed files. | ||
| 119 | { | ||
| 120 | } | ||
| 121 | |||
| 122 | // If there is a certificate, remember its hashed public key identifier and thumbprint. | ||
| 123 | if (null != certificate) | ||
| 124 | { | ||
| 125 | byte[] publicKeyIdentifierHash = new byte[128]; | ||
| 126 | uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length; | ||
| 127 | |||
| 128 | WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize); | ||
| 129 | StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2); | ||
| 130 | for (int i = 0; i < publicKeyIdentifierHashSize; ++i) | ||
| 131 | { | ||
| 132 | sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]); | ||
| 133 | } | ||
| 134 | |||
| 135 | payload.PublicKey = sb.ToString(); | ||
| 136 | payload.Thumbprint = certificate.Thumbprint; | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload) | ||
| 143 | { | ||
| 144 | FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile); | ||
| 145 | |||
| 146 | if (null != versionInfo) | ||
| 147 | { | ||
| 148 | // Use the fixed version info block for the file since the resource text may not be a dotted quad. | ||
| 149 | Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart); | ||
| 150 | |||
| 151 | if (ProcessPayloadsCommand.EmptyVersion != version) | ||
| 152 | { | ||
| 153 | payload.Version = version.ToString(); | ||
| 154 | } | ||
| 155 | |||
| 156 | payload.Description = versionInfo.FileDescription; | ||
| 157 | payload.DisplayName = versionInfo.ProductName; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
diff --git a/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs new file mode 100644 index 00000000..9919f777 --- /dev/null +++ b/src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs | |||
| @@ -0,0 +1,148 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Bundles | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections.Generic; | ||
| 7 | using System.IO; | ||
| 8 | using System.Linq; | ||
| 9 | using System.Runtime.InteropServices; | ||
| 10 | using System.Text; | ||
| 11 | using WixToolset.Data; | ||
| 12 | using WixToolset.Data.Rows; | ||
| 13 | |||
| 14 | internal class VerifyPayloadsWithCatalogCommand | ||
| 15 | { | ||
| 16 | public IEnumerable<WixBundleCatalogRow> Catalogs { private get; set; } | ||
| 17 | |||
| 18 | public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; } | ||
| 19 | |||
| 20 | public void Execute() | ||
| 21 | { | ||
| 22 | List<CatalogIdWithPath> catalogIdsWithPaths = this.Catalogs | ||
| 23 | .Join(this.Payloads, | ||
| 24 | catalog => catalog.Payload, | ||
| 25 | payload => payload.Id, | ||
| 26 | (catalog, payload) => new CatalogIdWithPath() { Id = catalog.Id, FullPath = Path.GetFullPath(payload.SourceFile) }) | ||
| 27 | .ToList(); | ||
| 28 | |||
| 29 | foreach (WixBundlePayloadRow payloadInfo in this.Payloads) | ||
| 30 | { | ||
| 31 | // Payloads that are not embedded should be verfied. | ||
| 32 | if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) | ||
| 33 | { | ||
| 34 | bool validated = false; | ||
| 35 | |||
| 36 | foreach (CatalogIdWithPath catalog in catalogIdsWithPaths) | ||
| 37 | { | ||
| 38 | if (!validated) | ||
| 39 | { | ||
| 40 | // Get the file hash | ||
| 41 | uint cryptHashSize = 20; | ||
| 42 | byte[] cryptHashBytes = new byte[cryptHashSize]; | ||
| 43 | int error; | ||
| 44 | IntPtr fileHandle = IntPtr.Zero; | ||
| 45 | using (FileStream payloadStream = File.OpenRead(payloadInfo.FullFileName)) | ||
| 46 | { | ||
| 47 | // Get the file handle | ||
| 48 | fileHandle = payloadStream.SafeFileHandle.DangerousGetHandle(); | ||
| 49 | |||
| 50 | // 20 bytes is usually the hash size. Future hashes may be bigger | ||
| 51 | if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) | ||
| 52 | { | ||
| 53 | error = Marshal.GetLastWin32Error(); | ||
| 54 | |||
| 55 | if (VerifyInterop.ErrorInsufficientBuffer == error) | ||
| 56 | { | ||
| 57 | error = 0; | ||
| 58 | cryptHashBytes = new byte[cryptHashSize]; | ||
| 59 | if (!VerifyInterop.CryptCATAdminCalcHashFromFileHandle(fileHandle, ref cryptHashSize, cryptHashBytes, 0)) | ||
| 60 | { | ||
| 61 | error = Marshal.GetLastWin32Error(); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | if (0 != error) | ||
| 66 | { | ||
| 67 | Messaging.Instance.OnMessage(WixErrors.CatalogFileHashFailed(payloadInfo.FullFileName, error)); | ||
| 68 | } | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | VerifyInterop.WinTrustCatalogInfo catalogData = new VerifyInterop.WinTrustCatalogInfo(); | ||
| 73 | VerifyInterop.WinTrustData trustData = new VerifyInterop.WinTrustData(); | ||
| 74 | try | ||
| 75 | { | ||
| 76 | // Create WINTRUST_CATALOG_INFO structure | ||
| 77 | catalogData.cbStruct = (uint)Marshal.SizeOf(catalogData); | ||
| 78 | catalogData.cbCalculatedFileHash = cryptHashSize; | ||
| 79 | catalogData.pbCalculatedFileHash = Marshal.AllocCoTaskMem((int)cryptHashSize); | ||
| 80 | Marshal.Copy(cryptHashBytes, 0, catalogData.pbCalculatedFileHash, (int)cryptHashSize); | ||
| 81 | |||
| 82 | StringBuilder hashString = new StringBuilder(); | ||
| 83 | foreach (byte hashByte in cryptHashBytes) | ||
| 84 | { | ||
| 85 | hashString.Append(hashByte.ToString("X2")); | ||
| 86 | } | ||
| 87 | catalogData.pcwszMemberTag = hashString.ToString(); | ||
| 88 | |||
| 89 | // The file names need to be lower case for older OSes | ||
| 90 | catalogData.pcwszMemberFilePath = payloadInfo.FullFileName.ToLowerInvariant(); | ||
| 91 | catalogData.pcwszCatalogFilePath = catalog.FullPath.ToLowerInvariant(); | ||
| 92 | |||
| 93 | // Create WINTRUST_DATA structure | ||
| 94 | trustData.cbStruct = (uint)Marshal.SizeOf(trustData); | ||
| 95 | trustData.dwUIChoice = VerifyInterop.WTD_UI_NONE; | ||
| 96 | trustData.fdwRevocationChecks = VerifyInterop.WTD_REVOKE_NONE; | ||
| 97 | trustData.dwUnionChoice = VerifyInterop.WTD_CHOICE_CATALOG; | ||
| 98 | trustData.dwStateAction = VerifyInterop.WTD_STATEACTION_VERIFY; | ||
| 99 | trustData.dwProvFlags = VerifyInterop.WTD_REVOCATION_CHECK_NONE; | ||
| 100 | |||
| 101 | // Create the structure pointers for unmanaged | ||
| 102 | trustData.pCatalog = Marshal.AllocCoTaskMem(Marshal.SizeOf(catalogData)); | ||
| 103 | Marshal.StructureToPtr(catalogData, trustData.pCatalog, false); | ||
| 104 | |||
| 105 | // Call WinTrustVerify to validate the file with the catalog | ||
| 106 | IntPtr noWindow = new IntPtr(-1); | ||
| 107 | Guid verifyGuid = new Guid(VerifyInterop.GenericVerify2); | ||
| 108 | long verifyResult = VerifyInterop.WinVerifyTrust(noWindow, ref verifyGuid, ref trustData); | ||
| 109 | if (0 == verifyResult) | ||
| 110 | { | ||
| 111 | payloadInfo.Catalog = catalog.Id; | ||
| 112 | validated = true; | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | finally | ||
| 117 | { | ||
| 118 | // Free the structure memory | ||
| 119 | if (IntPtr.Zero != trustData.pCatalog) | ||
| 120 | { | ||
| 121 | Marshal.FreeCoTaskMem(trustData.pCatalog); | ||
| 122 | } | ||
| 123 | |||
| 124 | if (IntPtr.Zero != catalogData.pbCalculatedFileHash) | ||
| 125 | { | ||
| 126 | Marshal.FreeCoTaskMem(catalogData.pbCalculatedFileHash); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | // Error message if the file was not validated by one of the catalogs | ||
| 133 | if (!validated) | ||
| 134 | { | ||
| 135 | Messaging.Instance.OnMessage(WixErrors.CatalogVerificationFailed(payloadInfo.FullFileName)); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | private class CatalogIdWithPath | ||
| 142 | { | ||
| 143 | public string Id { get; set; } | ||
| 144 | |||
| 145 | public string FullPath { get; set; } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs new file mode 100644 index 00000000..5eb76479 --- /dev/null +++ b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Inscribe | ||
| 4 | { | ||
| 5 | using System.IO; | ||
| 6 | using WixToolset.Core.Burn.Bundles; | ||
| 7 | using WixToolset.Extensibility; | ||
| 8 | |||
| 9 | internal class InscribeBundleCommand | ||
| 10 | { | ||
| 11 | public InscribeBundleCommand(IInscribeContext context) | ||
| 12 | { | ||
| 13 | this.Context = context; | ||
| 14 | } | ||
| 15 | |||
| 16 | private IInscribeContext Context { get; } | ||
| 17 | |||
| 18 | public bool Execute() | ||
| 19 | { | ||
| 20 | bool inscribed = false; | ||
| 21 | string tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); | ||
| 22 | |||
| 23 | using (BurnReader reader = BurnReader.Open(this.Context.InputFilePath)) | ||
| 24 | { | ||
| 25 | File.Copy(this.Context.SignedEngineFile, tempFile, true); | ||
| 26 | |||
| 27 | // If there was an attached container on the original (unsigned) bundle, put it back. | ||
| 28 | if (reader.AttachedContainerSize > 0) | ||
| 29 | { | ||
| 30 | reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); | ||
| 31 | |||
| 32 | using (BurnWriter writer = BurnWriter.Open(tempFile)) | ||
| 33 | { | ||
| 34 | writer.RememberThenResetSignature(); | ||
| 35 | writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); | ||
| 36 | inscribed = true; | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); | ||
| 42 | if (File.Exists(this.Context.OutputFile)) | ||
| 43 | { | ||
| 44 | File.Delete(this.Context.OutputFile); | ||
| 45 | } | ||
| 46 | |||
| 47 | File.Move(tempFile, this.Context.OutputFile); | ||
| 48 | WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { this.Context.OutputFile }, 1); | ||
| 49 | |||
| 50 | return inscribed; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
diff --git a/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs new file mode 100644 index 00000000..26af056b --- /dev/null +++ b/src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs | |||
| @@ -0,0 +1,61 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset.Core.Burn.Inscribe | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.IO; | ||
| 7 | using WixToolset.Core.Burn.Bundles; | ||
| 8 | using WixToolset.Extensibility; | ||
| 9 | |||
| 10 | internal class InscribeBundleEngineCommand | ||
| 11 | { | ||
| 12 | public InscribeBundleEngineCommand(IInscribeContext context) | ||
| 13 | { | ||
| 14 | this.Context = context; | ||
| 15 | } | ||
| 16 | |||
| 17 | private IInscribeContext Context { get; } | ||
| 18 | |||
| 19 | public bool Execute() | ||
| 20 | { | ||
| 21 | string tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_unsigned.exe"); | ||
| 22 | |||
| 23 | using (BurnReader reader = BurnReader.Open(this.Context.InputFilePath)) | ||
| 24 | using (FileStream writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) | ||
| 25 | { | ||
| 26 | reader.Stream.Seek(0, SeekOrigin.Begin); | ||
| 27 | |||
| 28 | byte[] buffer = new byte[4 * 1024]; | ||
| 29 | int total = 0; | ||
| 30 | int read = 0; | ||
| 31 | do | ||
| 32 | { | ||
| 33 | read = Math.Min(buffer.Length, (int)reader.EngineSize - total); | ||
| 34 | |||
| 35 | read = reader.Stream.Read(buffer, 0, read); | ||
| 36 | writer.Write(buffer, 0, read); | ||
| 37 | |||
| 38 | total += read; | ||
| 39 | } while (total < reader.EngineSize && 0 < read); | ||
| 40 | |||
| 41 | if (total != reader.EngineSize) | ||
| 42 | { | ||
| 43 | throw new InvalidOperationException("Failed to copy engine out of bundle."); | ||
| 44 | } | ||
| 45 | |||
| 46 | // TODO: update writer with detached container signatures. | ||
| 47 | } | ||
| 48 | |||
| 49 | Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); | ||
| 50 | if (File.Exists(this.Context.OutputFile)) | ||
| 51 | { | ||
| 52 | File.Delete(this.Context.OutputFile); | ||
| 53 | } | ||
| 54 | |||
| 55 | File.Move(tempFile, this.Context.OutputFile); | ||
| 56 | WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { this.Context.OutputFile }, 1); | ||
| 57 | |||
| 58 | return true; | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
diff --git a/src/WixToolset.Core.Burn/VerifyInterop.cs b/src/WixToolset.Core.Burn/VerifyInterop.cs new file mode 100644 index 00000000..81fbec65 --- /dev/null +++ b/src/WixToolset.Core.Burn/VerifyInterop.cs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
| 2 | |||
| 3 | namespace WixToolset | ||
| 4 | { | ||
| 5 | using System; | ||
| 6 | using System.Collections; | ||
| 7 | using System.Runtime.CompilerServices; | ||
| 8 | using System.Runtime.InteropServices; | ||
| 9 | |||
| 10 | internal class VerifyInterop | ||
| 11 | { | ||
| 12 | internal const string GenericVerify2 = "00AAC56B-CD44-11d0-8CC2-00C04FC295EE"; | ||
| 13 | internal const uint WTD_UI_NONE = 2; | ||
| 14 | internal const uint WTD_REVOKE_NONE = 0; | ||
| 15 | internal const uint WTD_CHOICE_CATALOG = 2; | ||
| 16 | internal const uint WTD_STATEACTION_VERIFY = 1; | ||
| 17 | internal const uint WTD_REVOCATION_CHECK_NONE = 0x10; | ||
| 18 | internal const int ErrorInsufficientBuffer = 122; | ||
| 19 | |||
| 20 | [StructLayout(LayoutKind.Sequential)] | ||
| 21 | internal struct WinTrustData | ||
| 22 | { | ||
| 23 | internal uint cbStruct; | ||
| 24 | internal IntPtr pPolicyCallbackData; | ||
| 25 | internal IntPtr pSIPClientData; | ||
| 26 | internal uint dwUIChoice; | ||
| 27 | internal uint fdwRevocationChecks; | ||
| 28 | internal uint dwUnionChoice; | ||
| 29 | internal IntPtr pCatalog; | ||
| 30 | internal uint dwStateAction; | ||
| 31 | internal IntPtr hWVTStateData; | ||
| 32 | [MarshalAs(UnmanagedType.LPWStr)] | ||
| 33 | internal string pwszURLReference; | ||
| 34 | internal uint dwProvFlags; | ||
| 35 | internal uint dwUIContext; | ||
| 36 | } | ||
| 37 | |||
| 38 | [StructLayout(LayoutKind.Sequential)] | ||
| 39 | internal struct WinTrustCatalogInfo | ||
| 40 | { | ||
| 41 | internal uint cbStruct; | ||
| 42 | internal uint dwCatalogVersion; | ||
| 43 | [MarshalAs(UnmanagedType.LPWStr)] | ||
| 44 | internal string pcwszCatalogFilePath; | ||
| 45 | [MarshalAs(UnmanagedType.LPWStr)] | ||
| 46 | internal string pcwszMemberTag; | ||
| 47 | [MarshalAs(UnmanagedType.LPWStr)] | ||
| 48 | internal string pcwszMemberFilePath; | ||
| 49 | internal IntPtr hMemberFile; | ||
| 50 | internal IntPtr pbCalculatedFileHash; | ||
| 51 | internal uint cbCalculatedFileHash; | ||
| 52 | internal IntPtr pcCatalogContext; | ||
| 53 | } | ||
| 54 | |||
| 55 | [DllImport("wintrust.dll", SetLastError = true)] | ||
| 56 | internal static extern long WinVerifyTrust(IntPtr windowHandle, ref Guid actionGuid, ref WinTrustData trustData); | ||
| 57 | |||
| 58 | [DllImport("wintrust.dll", SetLastError = true)] | ||
| 59 | [return: MarshalAs(UnmanagedType.Bool)] | ||
| 60 | internal static extern bool CryptCATAdminCalcHashFromFileHandle( | ||
| 61 | IntPtr fileHandle, | ||
| 62 | [In, Out] | ||
| 63 | ref uint hashSize, | ||
| 64 | [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] | ||
| 65 | byte[] hashBytes, | ||
| 66 | uint flags); | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj new file mode 100644 index 00000000..878ac200 --- /dev/null +++ b/src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | <?xml version="1.0" encoding="utf-8"?> | ||
| 2 | <!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. --> | ||
| 3 | |||
| 4 | <Project Sdk="Microsoft.NET.Sdk"> | ||
| 5 | <PropertyGroup> | ||
| 6 | <TargetFramework>netstandard2.0</TargetFramework> | ||
| 7 | <Description>Core Burn</Description> | ||
| 8 | <Title>WiX Toolset Core Burn</Title> | ||
| 9 | </PropertyGroup> | ||
| 10 | |||
| 11 | <PropertyGroup> | ||
| 12 | <NoWarn>NU1701</NoWarn> | ||
| 13 | </PropertyGroup> | ||
| 14 | |||
| 15 | <ItemGroup> | ||
| 16 | <ProjectReference Include="$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " /> | ||
| 17 | <PackageReference Include="WixToolset.Data" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Data\src\WixToolset.Data\WixToolset.Data.csproj') " /> | ||
| 18 | |||
| 19 | <ProjectReference Include="$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " /> | ||
| 20 | <PackageReference Include="WixToolset.Extensibility" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Extensibility\src\WixToolset.Extensibility\WixToolset.Extensibility.csproj') " /> | ||
| 21 | |||
| 22 | <ProjectReference Include="$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj" Condition=" '$(Configuration)' == 'Debug' And Exists('$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj') " /> | ||
| 23 | |||
| 24 | <ProjectReference Include="..\WixToolset.Core\WixToolset.Core.csproj" /> | ||
| 25 | <PackageReference Include="WixToolset.Core.Native" Version="4.0.*" Condition=" '$(Configuration)' == 'Release' Or !Exists('$(WixToolsetRootFolder)\Core.Native\src\WixToolset.Core.Native\WixToolset.Core.Native.csproj') " /> | ||
| 26 | </ItemGroup> | ||
| 27 | |||
| 28 | <ItemGroup> | ||
| 29 | <PackageReference Include="WixToolset.Dtf.Resources" Version="4.0.*" /> | ||
| 30 | <PackageReference Include="WixToolset.Dtf.WindowsInstaller" Version="4.0.*" /> | ||
| 31 | </ItemGroup> | ||
| 32 | |||
| 33 | <ItemGroup> | ||
| 34 | <PackageReference Include="Nerdbank.GitVersioning" Version="2.0.41" PrivateAssets="all" /> | ||
| 35 | </ItemGroup> | ||
| 36 | </Project> | ||
