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