aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.Burn
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.Burn')
-rw-r--r--src/WixToolset.Core.Burn/BackendFactory.cs30
-rw-r--r--src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs942
-rw-r--r--src/WixToolset.Core.Burn/Bind/ProvidesDependency.cs108
-rw-r--r--src/WixToolset.Core.Burn/Bind/ProvidesDependencyCollection.cs64
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixComponentSearchInfo.cs64
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixFileSearchInfo.cs54
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixProductSearchInfo.cs67
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixRegistrySearchInfo.cs92
-rw-r--r--src/WixToolset.Core.Burn/Bind/WixSearchInfo.cs53
-rw-r--r--src/WixToolset.Core.Burn/BundleBackend.cs58
-rw-r--r--src/WixToolset.Core.Burn/Bundles/AutomaticallySlipstreamPatchesCommand.cs112
-rw-r--r--src/WixToolset.Core.Burn/Bundles/BurnCommon.cs378
-rw-r--r--src/WixToolset.Core.Burn/Bundles/BurnReader.cs220
-rw-r--r--src/WixToolset.Core.Burn/Bundles/BurnWriter.cs239
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBootstrapperApplicationManifestCommand.cs241
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs686
-rw-r--r--src/WixToolset.Core.Burn/Bundles/CreateContainerCommand.cs68
-rw-r--r--src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs62
-rw-r--r--src/WixToolset.Core.Burn/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs145
-rw-r--r--src/WixToolset.Core.Burn/Bundles/PackageFacade.cs58
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessExePackageCommand.cs33
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMsiPackageCommand.cs576
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMspPackageCommand.cs189
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessMsuPackageCommand.cs30
-rw-r--r--src/WixToolset.Core.Burn/Bundles/ProcessPayloadsCommand.cs161
-rw-r--r--src/WixToolset.Core.Burn/Bundles/VerifyPayloadsWithCatalogCommand.cs148
-rw-r--r--src/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs53
-rw-r--r--src/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs61
-rw-r--r--src/WixToolset.Core.Burn/VerifyInterop.cs68
-rw-r--r--src/WixToolset.Core.Burn/WixToolset.Core.Burn.csproj36
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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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
3namespace 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>