aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Bind
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
committerRob Mensching <rob@firegiant.com>2017-10-14 16:12:07 -0700
commitdbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch)
tree0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core/Bind
parentfbf986eb97f68396797a89fc7d40dec07b775440 (diff)
downloadwix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2
wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core/Bind')
-rw-r--r--src/WixToolset.Core/Bind/BindBundleCommand.cs905
-rw-r--r--src/WixToolset.Core/Bind/BindDatabaseCommand.cs1311
-rw-r--r--src/WixToolset.Core/Bind/BindTransformCommand.cs473
-rw-r--r--src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs112
-rw-r--r--src/WixToolset.Core/Bind/Bundles/BurnCommon.cs378
-rw-r--r--src/WixToolset.Core/Bind/Bundles/BurnReader.cs210
-rw-r--r--src/WixToolset.Core/Bind/Bundles/BurnWriter.cs239
-rw-r--r--src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs241
-rw-r--r--src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs686
-rw-r--r--src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs68
-rw-r--r--src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs62
-rw-r--r--src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs145
-rw-r--r--src/WixToolset.Core/Bind/Bundles/PackageFacade.cs58
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs33
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs560
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs189
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs30
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs159
-rw-r--r--src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs148
-rw-r--r--src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs314
-rw-r--r--src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs135
-rw-r--r--src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs176
-rw-r--r--src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs78
-rw-r--r--src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs91
-rw-r--r--src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs606
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs489
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs86
-rw-r--r--src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs68
-rw-r--r--src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs225
-rw-r--r--src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs148
-rw-r--r--src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs350
-rw-r--r--src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs115
-rw-r--r--src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs80
-rw-r--r--src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs532
-rw-r--r--src/WixToolset.Core/Bind/DelayedField.cs13
-rw-r--r--src/WixToolset.Core/Bind/ExpectedExtractFile.cs16
-rw-r--r--src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs36
-rw-r--r--src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs29
-rw-r--r--src/WixToolset.Core/Bind/FileFacade.cs (renamed from src/WixToolset.Core/Bind/Databases/FileFacade.cs)2
-rw-r--r--src/WixToolset.Core/Bind/FileResolver.cs231
-rw-r--r--src/WixToolset.Core/Bind/FileTransfer.cs113
-rw-r--r--src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs335
-rw-r--r--src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs15
-rw-r--r--src/WixToolset.Core/Bind/ResolveFieldsCommand.cs71
-rw-r--r--src/WixToolset.Core/Bind/ResolveResult.cs14
-rw-r--r--src/WixToolset.Core/Bind/ResolvedDirectory.cs2
-rw-r--r--src/WixToolset.Core/Bind/TransferFilesCommand.cs61
47 files changed, 377 insertions, 10061 deletions
diff --git a/src/WixToolset.Core/Bind/BindBundleCommand.cs b/src/WixToolset.Core/Bind/BindBundleCommand.cs
deleted file mode 100644
index 7ea0c830..00000000
--- a/src/WixToolset.Core/Bind/BindBundleCommand.cs
+++ /dev/null
@@ -1,905 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind
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.Bundles;
13 using WixToolset.Data;
14 using WixToolset.Data.Rows;
15 using WixToolset.Extensibility;
16
17 /// <summary>
18 /// Binds a this.bundle.
19 /// </summary>
20 internal class BindBundleCommand : ICommand
21 {
22 public CompressionLevel DefaultCompressionLevel { private get; set; }
23
24 public IEnumerable<IBinderExtension> Extensions { private get; set; }
25
26 public BinderFileManagerCore FileManagerCore { private get; set; }
27
28 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
29
30 public Output Output { private get; set; }
31
32 public string OutputPath { private get; set; }
33
34 public string PdbFile { private get; set; }
35
36 public TableDefinitionCollection TableDefinitions { private get; set; }
37
38 public string TempFilesLocation { private get; set; }
39
40 public WixVariableResolver WixVariableResolver { private get; set; }
41
42 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
43
44 public IEnumerable<string> ContentFilePaths { get; private set; }
45
46 public void Execute()
47 {
48 this.FileTransfers = Enumerable.Empty<FileTransfer>();
49 this.ContentFilePaths = Enumerable.Empty<string>();
50
51 // First look for data we expect to find... Chain, WixGroups, etc.
52
53 // We shouldn't really get past the linker phase if there are
54 // no group items... that means that there's no UX, no Chain,
55 // *and* no Containers!
56 Table chainPackageTable = this.GetRequiredTable("WixBundlePackage");
57
58 Table wixGroupTable = this.GetRequiredTable("WixGroup");
59
60 // Ensure there is one and only one row in the WixBundle table.
61 // The compiler and linker behavior should have colluded to get
62 // this behavior.
63 WixBundleRow bundleRow = (WixBundleRow)this.GetSingleRowTable("WixBundle");
64
65 bundleRow.PerMachine = true; // default to per-machine but the first-per user package wil flip the bundle per-user.
66
67 // Ensure there is one and only one row in the WixBootstrapperApplication table.
68 // The compiler and linker behavior should have colluded to get
69 // this behavior.
70 Row baRow = this.GetSingleRowTable("WixBootstrapperApplication");
71
72 // Ensure there is one and only one row in the WixChain table.
73 // The compiler and linker behavior should have colluded to get
74 // this behavior.
75 WixChainRow chainRow = (WixChainRow)this.GetSingleRowTable("WixChain");
76
77 if (Messaging.Instance.EncounteredError)
78 {
79 return;
80 }
81
82 // Localize fields, resolve wix variables, and resolve file paths.
83 ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
84
85 IEnumerable<DelayedField> delayedFields;
86 {
87 ResolveFieldsCommand command = new ResolveFieldsCommand();
88 command.Tables = this.Output.Tables;
89 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
90 command.FileManagerCore = this.FileManagerCore;
91 command.FileManagers = this.FileManagers;
92 command.SupportDelayedResolution = true;
93 command.TempFilesLocation = this.TempFilesLocation;
94 command.WixVariableResolver = this.WixVariableResolver;
95 command.Execute();
96
97 delayedFields = command.DelayedFields;
98 }
99
100 if (Messaging.Instance.EncounteredError)
101 {
102 return;
103 }
104
105 // If there are any fields to resolve later, create the cache to populate during bind.
106 IDictionary<string, string> variableCache = null;
107 if (delayedFields.Any())
108 {
109 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
110 }
111
112 // TODO: Although the WixSearch tables are defined in the Util extension,
113 // the Bundle Binder has to know all about them. We hope to revisit all
114 // of this in the 4.0 timeframe.
115 IEnumerable<WixSearchInfo> orderedSearches = this.OrderSearches();
116
117 // Extract files that come from cabinet files (this does not extract files from merge modules).
118 {
119 ExtractEmbeddedFilesCommand extractEmbeddedFilesCommand = new ExtractEmbeddedFilesCommand();
120 extractEmbeddedFilesCommand.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
121 extractEmbeddedFilesCommand.Execute();
122 }
123
124 // Get the explicit payloads.
125 RowDictionary<WixBundlePayloadRow> payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]);
126
127 // Update explicitly authored payloads with their parent package and container (as appropriate)
128 // to make it easier to gather the payloads later.
129 foreach (WixGroupRow row in wixGroupTable.RowsAs<WixGroupRow>())
130 {
131 if (ComplexReferenceChildType.Payload == row.ChildType)
132 {
133 WixBundlePayloadRow payload = payloads.Get(row.ChildId);
134
135 if (ComplexReferenceParentType.Package == row.ParentType)
136 {
137 Debug.Assert(String.IsNullOrEmpty(payload.Package));
138 payload.Package = row.ParentId;
139 }
140 else if (ComplexReferenceParentType.Container == row.ParentType)
141 {
142 Debug.Assert(String.IsNullOrEmpty(payload.Container));
143 payload.Container = row.ParentId;
144 }
145 else if (ComplexReferenceParentType.Layout == row.ParentType)
146 {
147 payload.LayoutOnly = true;
148 }
149 }
150 }
151
152 List<FileTransfer> fileTransfers = new List<FileTransfer>();
153 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
154
155 // Process the explicitly authored payloads.
156 ISet<string> processedPayloads;
157 {
158 ProcessPayloadsCommand command = new ProcessPayloadsCommand();
159 command.Payloads = payloads.Values;
160 command.DefaultPackaging = bundleRow.DefaultPackagingType;
161 command.LayoutDirectory = layoutDirectory;
162 command.Execute();
163
164 fileTransfers.AddRange(command.FileTransfers);
165
166 processedPayloads = new HashSet<string>(payloads.Keys);
167 }
168
169 IDictionary<string, PackageFacade> facades;
170 {
171 GetPackageFacadesCommand command = new GetPackageFacadesCommand();
172 command.PackageTable = chainPackageTable;
173 command.ExePackageTable = this.Output.Tables["WixBundleExePackage"];
174 command.MsiPackageTable = this.Output.Tables["WixBundleMsiPackage"];
175 command.MspPackageTable = this.Output.Tables["WixBundleMspPackage"];
176 command.MsuPackageTable = this.Output.Tables["WixBundleMsuPackage"];
177 command.Execute();
178
179 facades = command.PackageFacades;
180 }
181
182 // Process each package facade. Note this is likely to add payloads and other rows to tables so
183 // note that any indexes created above may be out of date now.
184 foreach (PackageFacade facade in facades.Values)
185 {
186 switch (facade.Package.Type)
187 {
188 case WixBundlePackageType.Exe:
189 {
190 ProcessExePackageCommand command = new ProcessExePackageCommand();
191 command.AuthoredPayloads = payloads;
192 command.Facade = facade;
193 command.Execute();
194
195 // ? variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.ExePackage.Manufacturer);
196 }
197 break;
198
199 case WixBundlePackageType.Msi:
200 {
201 ProcessMsiPackageCommand command = new ProcessMsiPackageCommand();
202 command.AuthoredPayloads = payloads;
203 command.Facade = facade;
204 command.FileManager = this.FileManagers.First();
205 command.MsiFeatureTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiFeature"]);
206 command.MsiPropertyTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleMsiProperty"]);
207 command.PayloadTable = this.Output.Tables["WixBundlePayload"];
208 command.RelatedPackageTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleRelatedPackage"]);
209 command.Execute();
210
211 if (null != variableCache)
212 {
213 variableCache.Add(String.Concat("packageLanguage.", facade.Package.WixChainItemId), facade.MsiPackage.ProductLanguage.ToString());
214
215 if (null != facade.MsiPackage.Manufacturer)
216 {
217 variableCache.Add(String.Concat("packageManufacturer.", facade.Package.WixChainItemId), facade.MsiPackage.Manufacturer);
218 }
219 }
220
221 }
222 break;
223
224 case WixBundlePackageType.Msp:
225 {
226 ProcessMspPackageCommand command = new ProcessMspPackageCommand();
227 command.AuthoredPayloads = payloads;
228 command.Facade = facade;
229 command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]);
230 command.Execute();
231 }
232 break;
233
234 case WixBundlePackageType.Msu:
235 {
236 ProcessMsuPackageCommand command = new ProcessMsuPackageCommand();
237 command.Facade = facade;
238 command.Execute();
239 }
240 break;
241 }
242
243 if (null != variableCache)
244 {
245 BindBundleCommand.PopulatePackageVariableCache(facade.Package, variableCache);
246 }
247 }
248
249 // Reindex the payloads now that all the payloads (minus the manifest payloads that will be created later)
250 // are present.
251 payloads = new RowDictionary<WixBundlePayloadRow>(this.Output.Tables["WixBundlePayload"]);
252
253 // Process the payloads that were added by processing the packages.
254 {
255 ProcessPayloadsCommand command = new ProcessPayloadsCommand();
256 command.Payloads = payloads.Values.Where(r => !processedPayloads.Contains(r.Id)).ToList();
257 command.DefaultPackaging = bundleRow.DefaultPackagingType;
258 command.LayoutDirectory = layoutDirectory;
259 command.Execute();
260
261 fileTransfers.AddRange(command.FileTransfers);
262
263 processedPayloads = null;
264 }
265
266 // Set the package metadata from the payloads now that we have the complete payload information.
267 ILookup<string, WixBundlePayloadRow> payloadsByPackage = payloads.Values.ToLookup(p => p.Package);
268
269 {
270 foreach (PackageFacade facade in facades.Values)
271 {
272 facade.Package.Size = 0;
273
274 IEnumerable<WixBundlePayloadRow> packagePayloads = payloadsByPackage[facade.Package.WixChainItemId];
275
276 foreach (WixBundlePayloadRow payload in packagePayloads)
277 {
278 facade.Package.Size += payload.FileSize;
279 }
280
281 if (!facade.Package.InstallSize.HasValue)
282 {
283 facade.Package.InstallSize = facade.Package.Size;
284
285 }
286
287 WixBundlePayloadRow packagePayload = payloads[facade.Package.PackagePayload];
288
289 if (String.IsNullOrEmpty(facade.Package.Description))
290 {
291 facade.Package.Description = packagePayload.Description;
292 }
293
294 if (String.IsNullOrEmpty(facade.Package.DisplayName))
295 {
296 facade.Package.DisplayName = packagePayload.DisplayName;
297 }
298 }
299 }
300
301
302 // Give the UX payloads their embedded IDs...
303 int uxPayloadIndex = 0;
304 {
305 foreach (WixBundlePayloadRow payload in payloads.Values.Where(p => Compiler.BurnUXContainerId == p.Container))
306 {
307 // In theory, UX payloads could be embedded in the UX CAB, external to the bundle EXE, or even
308 // downloaded. The current engine requires the UX to be fully present before any downloading starts,
309 // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads
310 // into the temporary UX directory correctly, so we don't allow external either.
311 if (PackagingType.Embedded != payload.Packaging)
312 {
313 Messaging.Instance.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName));
314 payload.Packaging = PackagingType.Embedded;
315 }
316
317 payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex);
318 ++uxPayloadIndex;
319 }
320
321 if (0 == uxPayloadIndex)
322 {
323 // If we didn't get any UX payloads, it's an error!
324 throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication"));
325 }
326
327 // Give the embedded payloads without an embedded id yet an embedded id.
328 int payloadIndex = 0;
329 foreach (WixBundlePayloadRow payload in payloads.Values)
330 {
331 Debug.Assert(PackagingType.Unknown != payload.Packaging);
332
333 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId))
334 {
335 payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex);
336 ++payloadIndex;
337 }
338 }
339 }
340
341 // Determine patches to automatically slipstream.
342 {
343 AutomaticallySlipstreamPatchesCommand command = new AutomaticallySlipstreamPatchesCommand();
344 command.PackageFacades = facades.Values;
345 command.SlipstreamMspTable = this.Output.EnsureTable(this.TableDefinitions["WixBundleSlipstreamMsp"]);
346 command.WixBundlePatchTargetCodeTable = this.Output.EnsureTable(this.TableDefinitions["WixBundlePatchTargetCode"]);
347 command.Execute();
348 }
349
350 // If catalog files exist, non-embedded payloads should validate with the catalogs.
351 IEnumerable<WixBundleCatalogRow> catalogs = this.Output.Tables["WixBundleCatalog"].RowsAs<WixBundleCatalogRow>();
352
353 if (catalogs.Any())
354 {
355 VerifyPayloadsWithCatalogCommand command = new VerifyPayloadsWithCatalogCommand();
356 command.Catalogs = catalogs;
357 command.Payloads = payloads.Values;
358 command.Execute();
359 }
360
361 if (Messaging.Instance.EncounteredError)
362 {
363 return;
364 }
365
366 IEnumerable<PackageFacade> orderedFacades;
367 IEnumerable<WixBundleRollbackBoundaryRow> boundaries;
368 {
369 OrderPackagesAndRollbackBoundariesCommand command = new OrderPackagesAndRollbackBoundariesCommand();
370 command.Boundaries = new RowDictionary<WixBundleRollbackBoundaryRow>(this.Output.Tables["WixBundleRollbackBoundary"]);
371 command.PackageFacades = facades;
372 command.WixGroupTable = wixGroupTable;
373 command.Execute();
374
375 orderedFacades = command.OrderedPackageFacades;
376 boundaries = command.UsedRollbackBoundaries;
377 }
378
379 // Resolve any delayed fields before generating the manifest.
380 if (delayedFields.Any())
381 {
382 ResolveDelayedFieldsCommand resolveDelayedFieldsCommand = new ResolveDelayedFieldsCommand();
383 resolveDelayedFieldsCommand.OutputType = this.Output.Type;
384 resolveDelayedFieldsCommand.DelayedFields = delayedFields;
385 resolveDelayedFieldsCommand.ModularizationGuid = null;
386 resolveDelayedFieldsCommand.VariableCache = variableCache;
387 resolveDelayedFieldsCommand.Execute();
388 }
389
390 // Set the overridable bundle provider key.
391 this.SetBundleProviderKey(this.Output, bundleRow);
392
393 // Import or generate dependency providers for packages in the manifest.
394 this.ProcessDependencyProviders(this.Output, facades);
395
396 // Update the bundle per-machine/per-user scope based on the chained packages.
397 this.ResolveBundleInstallScope(bundleRow, orderedFacades);
398
399 // Generate the core-defined BA manifest tables...
400 {
401 CreateBootstrapperApplicationManifestCommand command = new CreateBootstrapperApplicationManifestCommand();
402 command.BundleRow = bundleRow;
403 command.ChainPackages = orderedFacades;
404 command.LastUXPayloadIndex = uxPayloadIndex;
405 command.MsiFeatures = this.Output.Tables["WixBundleMsiFeature"].RowsAs<WixBundleMsiFeatureRow>();
406 command.Output = this.Output;
407 command.Payloads = payloads;
408 command.TableDefinitions = this.TableDefinitions;
409 command.TempFilesLocation = this.TempFilesLocation;
410 command.Execute();
411
412 WixBundlePayloadRow baManifestPayload = command.BootstrapperApplicationManifestPayloadRow;
413 payloads.Add(baManifestPayload);
414 }
415
416 foreach (BinderExtension extension in this.Extensions)
417 {
418 extension.Finish(Output);
419 }
420
421 // Create all the containers except the UX container first so the manifest (that goes in the UX container)
422 // can contain all size and hash information about the non-UX containers.
423 RowDictionary<WixBundleContainerRow> containers = new RowDictionary<WixBundleContainerRow>(this.Output.Tables["WixBundleContainer"]);
424
425 ILookup<string, WixBundlePayloadRow> payloadsByContainer = payloads.Values.ToLookup(p => p.Container);
426
427 int attachedContainerIndex = 1; // count starts at one because UX container is "0".
428
429 IEnumerable<WixBundlePayloadRow> uxContainerPayloads = Enumerable.Empty<WixBundlePayloadRow>();
430
431 foreach (WixBundleContainerRow container in containers.Values)
432 {
433 IEnumerable<WixBundlePayloadRow> containerPayloads = payloadsByContainer[container.Id];
434
435 if (!containerPayloads.Any())
436 {
437 if (container.Id != Compiler.BurnDefaultAttachedContainerId)
438 {
439 // TODO: display warning that we're ignoring container that ended up with no paylods in it.
440 }
441 }
442 else if (Compiler.BurnUXContainerId == container.Id)
443 {
444 container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name);
445 container.AttachedContainerIndex = 0;
446
447 // Gather the list of UX payloads but ensure the BootstrapperApplication Payload is the first
448 // in the list since that is the Payload that Burn attempts to load.
449 List<WixBundlePayloadRow> uxPayloads = new List<WixBundlePayloadRow>();
450
451 string baPayloadId = baRow.FieldAsString(0);
452
453 foreach (WixBundlePayloadRow uxPayload in containerPayloads)
454 {
455 if (uxPayload.Id == baPayloadId)
456 {
457 uxPayloads.Insert(0, uxPayload);
458 }
459 else
460 {
461 uxPayloads.Add(uxPayload);
462 }
463 }
464
465 uxContainerPayloads = uxPayloads;
466 }
467 else
468 {
469 container.WorkingPath = Path.Combine(this.TempFilesLocation, container.Name);
470
471 // Add detached containers to the list of file transfers.
472 if (ContainerType.Detached == container.Type)
473 {
474 FileTransfer transfer;
475 if (FileTransfer.TryCreate(container.WorkingPath, Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer))
476 {
477 transfer.Built = true;
478 fileTransfers.Add(transfer);
479 }
480 }
481 else // update the attached container index.
482 {
483 Debug.Assert(ContainerType.Attached == container.Type);
484
485 container.AttachedContainerIndex = attachedContainerIndex;
486 ++attachedContainerIndex;
487 }
488
489 this.CreateContainer(container, containerPayloads, null);
490 }
491 }
492
493 // Create the bundle manifest then UX container.
494 string manifestPath = Path.Combine(this.TempFilesLocation, "bundle-manifest.xml");
495 {
496 CreateBurnManifestCommand command = new CreateBurnManifestCommand();
497 command.FileManagers = this.FileManagers;
498 command.Output = this.Output;
499
500 command.BundleInfo = bundleRow;
501 command.Chain = chainRow;
502 command.Containers = containers;
503 command.Catalogs = catalogs;
504 command.ExecutableName = Path.GetFileName(this.OutputPath);
505 command.OrderedPackages = orderedFacades;
506 command.OutputPath = manifestPath;
507 command.RollbackBoundaries = boundaries;
508 command.OrderedSearches = orderedSearches;
509 command.Payloads = payloads;
510 command.UXContainerPayloads = uxContainerPayloads;
511 command.Execute();
512 }
513
514 WixBundleContainerRow uxContainer = containers[Compiler.BurnUXContainerId];
515 this.CreateContainer(uxContainer, uxContainerPayloads, manifestPath);
516
517 // Copy the burn.exe to a writable location then mark it to be moved to its final build location. Note
518 // that today, the x64 Burn uses the x86 stub.
519 string stubPlatform = (Platform.X64 == bundleRow.Platform) ? "x86" : bundleRow.Platform.ToString();
520
521 string stubFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform, "burn.exe");
522 string bundleTempPath = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath));
523
524 Messaging.Instance.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile));
525
526 string bundleFilename = Path.GetFileName(this.OutputPath);
527 if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase))
528 {
529 Messaging.Instance.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename));
530 }
531
532 FileTransfer bundleTransfer;
533 if (FileTransfer.TryCreate(bundleTempPath, this.OutputPath, true, "Bundle", bundleRow.SourceLineNumbers, out bundleTransfer))
534 {
535 bundleTransfer.Built = true;
536 fileTransfers.Add(bundleTransfer);
537 }
538
539 File.Copy(stubFile, bundleTempPath, true);
540 File.SetAttributes(bundleTempPath, FileAttributes.Normal);
541
542 this.UpdateBurnResources(bundleTempPath, this.OutputPath, bundleRow);
543
544 // Update the .wixburn section to point to at the UX and attached container(s) then attach the containers
545 // if they should be attached.
546 using (BurnWriter writer = BurnWriter.Open(bundleTempPath))
547 {
548 FileInfo burnStubFile = new FileInfo(bundleTempPath);
549 writer.InitializeBundleSectionData(burnStubFile.Length, bundleRow.BundleId);
550
551 // Always attach the UX container first
552 writer.AppendContainer(uxContainer.WorkingPath, BurnWriter.Container.UX);
553
554 // Now append all other attached containers
555 foreach (WixBundleContainerRow container in containers.Values)
556 {
557 if (ContainerType.Attached == container.Type)
558 {
559 // The container was only created if it had payloads.
560 if (!String.IsNullOrEmpty(container.WorkingPath) && Compiler.BurnUXContainerId != container.Id)
561 {
562 writer.AppendContainer(container.WorkingPath, BurnWriter.Container.Attached);
563 }
564 }
565 }
566 }
567
568 if (null != this.PdbFile)
569 {
570 Pdb pdb = new Pdb();
571 pdb.Output = Output;
572 pdb.Save(this.PdbFile);
573 }
574
575 this.FileTransfers = fileTransfers;
576 this.ContentFilePaths = payloads.Values.Where(p => p.ContentFile).Select(p => p.FullFileName).ToList();
577 }
578
579 private Table GetRequiredTable(string tableName)
580 {
581 Table table = this.Output.Tables[tableName];
582 if (null == table || 0 == table.Rows.Count)
583 {
584 throw new WixException(WixErrors.MissingBundleInformation(tableName));
585 }
586
587 return table;
588 }
589
590 private Row GetSingleRowTable(string tableName)
591 {
592 Table table = this.Output.Tables[tableName];
593 if (null == table || 1 != table.Rows.Count)
594 {
595 throw new WixException(WixErrors.MissingBundleInformation(tableName));
596 }
597
598 return table.Rows[0];
599 }
600
601 private List<WixSearchInfo> OrderSearches()
602 {
603 Dictionary<string, WixSearchInfo> allSearches = new Dictionary<string, WixSearchInfo>();
604 Table wixFileSearchTable = this.Output.Tables["WixFileSearch"];
605 if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count)
606 {
607 foreach (Row row in wixFileSearchTable.Rows)
608 {
609 WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row);
610 allSearches.Add(fileSearchInfo.Id, fileSearchInfo);
611 }
612 }
613
614 Table wixRegistrySearchTable = this.Output.Tables["WixRegistrySearch"];
615 if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count)
616 {
617 foreach (Row row in wixRegistrySearchTable.Rows)
618 {
619 WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row);
620 allSearches.Add(registrySearchInfo.Id, registrySearchInfo);
621 }
622 }
623
624 Table wixComponentSearchTable = this.Output.Tables["WixComponentSearch"];
625 if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count)
626 {
627 foreach (Row row in wixComponentSearchTable.Rows)
628 {
629 WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row);
630 allSearches.Add(componentSearchInfo.Id, componentSearchInfo);
631 }
632 }
633
634 Table wixProductSearchTable = this.Output.Tables["WixProductSearch"];
635 if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count)
636 {
637 foreach (Row row in wixProductSearchTable.Rows)
638 {
639 WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row);
640 allSearches.Add(productSearchInfo.Id, productSearchInfo);
641 }
642 }
643
644 // Merge in the variable/condition info and get the canonical ordering for
645 // the searches.
646 List<WixSearchInfo> orderedSearches = new List<WixSearchInfo>();
647 Table wixSearchTable = this.Output.Tables["WixSearch"];
648 if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count)
649 {
650 orderedSearches.Capacity = wixSearchTable.Rows.Count;
651 foreach (Row row in wixSearchTable.Rows)
652 {
653 WixSearchInfo searchInfo = allSearches[(string)row[0]];
654 searchInfo.AddWixSearchRowInfo(row);
655 orderedSearches.Add(searchInfo);
656 }
657 }
658
659 return orderedSearches;
660 }
661
662 /// <summary>
663 /// Populates the variable cache with specific package properties.
664 /// </summary>
665 /// <param name="package">The package with properties to cache.</param>
666 /// <param name="variableCache">The property cache.</param>
667 private static void PopulatePackageVariableCache(WixBundlePackageRow package, IDictionary<string, string> variableCache)
668 {
669 string id = package.WixChainItemId;
670
671 variableCache.Add(String.Concat("packageDescription.", id), package.Description);
672 //variableCache.Add(String.Concat("packageLanguage.", id), package.Language);
673 //variableCache.Add(String.Concat("packageManufacturer.", id), package.Manufacturer);
674 variableCache.Add(String.Concat("packageName.", id), package.DisplayName);
675 variableCache.Add(String.Concat("packageVersion.", id), package.Version);
676 }
677
678 private void CreateContainer(WixBundleContainerRow container, IEnumerable<WixBundlePayloadRow> containerPayloads, string manifestFile)
679 {
680 CreateContainerCommand command = new CreateContainerCommand();
681 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
682 command.Payloads = containerPayloads;
683 command.ManifestFile = manifestFile;
684 command.OutputPath = container.WorkingPath;
685 command.Execute();
686
687 container.Hash = command.Hash;
688 container.Size = command.Size;
689 }
690
691 private void ResolveBundleInstallScope(WixBundleRow bundleInfo, IEnumerable<PackageFacade> facades)
692 {
693 foreach (PackageFacade facade in facades)
694 {
695 if (bundleInfo.PerMachine && YesNoDefaultType.No == facade.Package.PerMachine)
696 {
697 Messaging.Instance.OnMessage(WixVerboses.SwitchingToPerUserPackage(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId));
698
699 bundleInfo.PerMachine = false;
700 break;
701 }
702 }
703
704 foreach (PackageFacade facade in facades)
705 {
706 // Update package scope from bundle scope if default.
707 if (YesNoDefaultType.Default == facade.Package.PerMachine)
708 {
709 facade.Package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No;
710 }
711
712 // We will only register packages in the same scope as the bundle. Warn if any packages with providers
713 // are in a different scope and not permanent (permanents typically don't need a ref-count).
714 if (!bundleInfo.PerMachine && YesNoDefaultType.Yes == facade.Package.PerMachine && !facade.Package.Permanent && 0 < facade.Provides.Count)
715 {
716 Messaging.Instance.OnMessage(WixWarnings.NoPerMachineDependencies(facade.Package.SourceLineNumbers, facade.Package.WixChainItemId));
717 }
718 }
719 }
720
721 private void UpdateBurnResources(string bundleTempPath, string outputPath, WixBundleRow bundleInfo)
722 {
723 WixToolset.Dtf.Resources.ResourceCollection resources = new WixToolset.Dtf.Resources.ResourceCollection();
724 WixToolset.Dtf.Resources.VersionResource version = new WixToolset.Dtf.Resources.VersionResource("#1", 1033);
725
726 version.Load(bundleTempPath);
727 resources.Add(version);
728
729 // Ensure the bundle info provides a full four part version.
730 Version fourPartVersion = new Version(bundleInfo.Version);
731 int major = (fourPartVersion.Major < 0) ? 0 : fourPartVersion.Major;
732 int minor = (fourPartVersion.Minor < 0) ? 0 : fourPartVersion.Minor;
733 int build = (fourPartVersion.Build < 0) ? 0 : fourPartVersion.Build;
734 int revision = (fourPartVersion.Revision < 0) ? 0 : fourPartVersion.Revision;
735
736 if (UInt16.MaxValue < major || UInt16.MaxValue < minor || UInt16.MaxValue < build || UInt16.MaxValue < revision)
737 {
738 throw new WixException(WixErrors.InvalidModuleOrBundleVersion(bundleInfo.SourceLineNumbers, "Bundle", bundleInfo.Version));
739 }
740
741 fourPartVersion = new Version(major, minor, build, revision);
742 version.FileVersion = fourPartVersion;
743 version.ProductVersion = fourPartVersion;
744
745 WixToolset.Dtf.Resources.VersionStringTable strings = version[1033];
746 strings["LegalCopyright"] = bundleInfo.Copyright;
747 strings["OriginalFilename"] = Path.GetFileName(outputPath);
748 strings["FileVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
749 strings["ProductVersion"] = bundleInfo.Version; // string versions do not have to be four parts.
750
751 if (!String.IsNullOrEmpty(bundleInfo.Name))
752 {
753 strings["ProductName"] = bundleInfo.Name;
754 strings["FileDescription"] = bundleInfo.Name;
755 }
756
757 if (!String.IsNullOrEmpty(bundleInfo.Publisher))
758 {
759 strings["CompanyName"] = bundleInfo.Publisher;
760 }
761 else
762 {
763 strings["CompanyName"] = String.Empty;
764 }
765
766 if (!String.IsNullOrEmpty(bundleInfo.IconPath))
767 {
768 Dtf.Resources.GroupIconResource iconGroup = new Dtf.Resources.GroupIconResource("#1", 1033);
769 iconGroup.ReadFromFile(bundleInfo.IconPath);
770 resources.Add(iconGroup);
771
772 foreach (Dtf.Resources.Resource icon in iconGroup.Icons)
773 {
774 resources.Add(icon);
775 }
776 }
777
778 if (!String.IsNullOrEmpty(bundleInfo.SplashScreenBitmapPath))
779 {
780 Dtf.Resources.BitmapResource bitmap = new Dtf.Resources.BitmapResource("#1", 1033);
781 bitmap.ReadFromFile(bundleInfo.SplashScreenBitmapPath);
782 resources.Add(bitmap);
783 }
784
785 resources.Save(bundleTempPath);
786 }
787
788 #region DependencyExtension
789 /// <summary>
790 /// Imports authored dependency providers for each package in the manifest,
791 /// and generates dependency providers for certain package types that do not
792 /// have a provider defined.
793 /// </summary>
794 /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param>
795 /// <param name="facades">An indexed collection of chained packages.</param>
796 private void ProcessDependencyProviders(Output bundle, IDictionary<string, PackageFacade> facades)
797 {
798 // First import any authored dependencies. These may merge with imported provides from MSI packages.
799 Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"];
800 if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count)
801 {
802 // Add package information for each dependency provider authored into the manifest.
803 foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows)
804 {
805 string packageId = (string)wixDependencyProviderRow[1];
806
807 PackageFacade facade = null;
808 if (facades.TryGetValue(packageId, out facade))
809 {
810 ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow);
811
812 if (String.IsNullOrEmpty(dependency.Key))
813 {
814 switch (facade.Package.Type)
815 {
816 // The WixDependencyExtension allows an empty Key for MSIs and MSPs.
817 case WixBundlePackageType.Msi:
818 dependency.Key = facade.MsiPackage.ProductCode;
819 break;
820 case WixBundlePackageType.Msp:
821 dependency.Key = facade.MspPackage.PatchCode;
822 break;
823 }
824 }
825
826 if (String.IsNullOrEmpty(dependency.Version))
827 {
828 dependency.Version = facade.Package.Version;
829 }
830
831 // If the version is still missing, a version could not be harvested from the package and was not authored.
832 if (String.IsNullOrEmpty(dependency.Version))
833 {
834 Messaging.Instance.OnMessage(WixErrors.MissingDependencyVersion(facade.Package.WixChainItemId));
835 }
836
837 if (String.IsNullOrEmpty(dependency.DisplayName))
838 {
839 dependency.DisplayName = facade.Package.DisplayName;
840 }
841
842 if (!facade.Provides.Merge(dependency))
843 {
844 Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId));
845 }
846 }
847 }
848 }
849
850 // Generate providers for MSI packages that still do not have providers.
851 foreach (PackageFacade facade in facades.Values)
852 {
853 string key = null;
854
855 if (WixBundlePackageType.Msi == facade.Package.Type && 0 == facade.Provides.Count)
856 {
857 key = facade.MsiPackage.ProductCode;
858 }
859 else if (WixBundlePackageType.Msp == facade.Package.Type && 0 == facade.Provides.Count)
860 {
861 key = facade.MspPackage.PatchCode;
862 }
863
864 if (!String.IsNullOrEmpty(key))
865 {
866 ProvidesDependency dependency = new ProvidesDependency(key, facade.Package.Version, facade.Package.DisplayName, 0);
867
868 if (!facade.Provides.Merge(dependency))
869 {
870 Messaging.Instance.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, facade.Package.WixChainItemId));
871 }
872 }
873 }
874 }
875
876 /// <summary>
877 /// Sets the provider key for the bundle.
878 /// </summary>
879 /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param>
880 /// <param name="bundleInfo">The <see cref="BundleInfo"/> containing the provider key and other information for the bundle.</param>
881 private void SetBundleProviderKey(Output bundle, WixBundleRow bundleInfo)
882 {
883 // From DependencyCommon.cs in the WixDependencyExtension.
884 const int ProvidesAttributesBundle = 0x10000;
885
886 Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"];
887 if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count)
888 {
889 // Search the WixDependencyProvider table for the single bundle provider key.
890 foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows)
891 {
892 object attributes = wixDependencyProviderRow[5];
893 if (null != attributes && 0 != (ProvidesAttributesBundle & (int)attributes))
894 {
895 bundleInfo.ProviderKey = (string)wixDependencyProviderRow[2];
896 break;
897 }
898 }
899 }
900
901 // Defaults to the bundle ID as the provider key.
902 }
903 #endregion
904 }
905}
diff --git a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core/Bind/BindDatabaseCommand.cs
deleted file mode 100644
index 93af2e9a..00000000
--- a/src/WixToolset.Core/Bind/BindDatabaseCommand.cs
+++ /dev/null
@@ -1,1311 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind
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.Bind.Databases;
13 using WixToolset.Data;
14 using WixToolset.Data.Rows;
15 using WixToolset.Extensibility;
16 using WixToolset.Msi;
17
18 /// <summary>
19 /// Binds a databse.
20 /// </summary>
21 internal class BindDatabaseCommand : ICommand
22 {
23 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
24 private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
25
26 public int Codepage { private get; set; }
27
28 public int CabbingThreadCount { private get; set; }
29
30 public CompressionLevel DefaultCompressionLevel { private get; set; }
31
32 public bool DeltaBinaryPatch { get; set; }
33
34 public IEnumerable<IBinderExtension> Extensions { private get; set; }
35
36 public BinderFileManagerCore FileManagerCore { private get; set; }
37
38 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
39
40 public IEnumerable<InspectorExtension> InspectorExtensions { private get; set; }
41
42 public Localizer Localizer { private get; set; }
43
44 public string PdbFile { private get; set; }
45
46 public Output Output { private get; set; }
47
48 public string OutputPath { private get; set; }
49
50 public bool SuppressAddingValidationRows { private get; set; }
51
52 public bool SuppressLayout { private get; set; }
53
54 public TableDefinitionCollection TableDefinitions { private get; set; }
55
56 public string TempFilesLocation { private get; set; }
57
58 public Validator Validator { private get; set; }
59
60 public WixVariableResolver WixVariableResolver { private get; set; }
61
62 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
63
64 public IEnumerable<string> ContentFilePaths { get; private set; }
65
66 public void Execute()
67 {
68 List<FileTransfer> fileTransfers = new List<FileTransfer>();
69
70 HashSet<string> suppressedTableNames = new HashSet<string>();
71
72 // Localize fields, resolve wix variables, and resolve file paths.
73 ExtractEmbeddedFiles filesWithEmbeddedFiles = new ExtractEmbeddedFiles();
74
75 IEnumerable<DelayedField> delayedFields;
76 {
77 ResolveFieldsCommand command = new ResolveFieldsCommand();
78 command.Tables = this.Output.Tables;
79 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
80 command.FileManagerCore = this.FileManagerCore;
81 command.FileManagers = this.FileManagers;
82 command.SupportDelayedResolution = true;
83 command.TempFilesLocation = this.TempFilesLocation;
84 command.WixVariableResolver = this.WixVariableResolver;
85 command.Execute();
86
87 delayedFields = command.DelayedFields;
88 }
89
90 if (OutputType.Patch == this.Output.Type)
91 {
92 foreach (SubStorage transform in this.Output.SubStorages)
93 {
94 ResolveFieldsCommand command = new ResolveFieldsCommand();
95 command.Tables = transform.Data.Tables;
96 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
97 command.FileManagerCore = this.FileManagerCore;
98 command.FileManagers = this.FileManagers;
99 command.SupportDelayedResolution = false;
100 command.TempFilesLocation = this.TempFilesLocation;
101 command.WixVariableResolver = this.WixVariableResolver;
102 command.Execute();
103 }
104 }
105
106 // If there are any fields to resolve later, create the cache to populate during bind.
107 IDictionary<string, string> variableCache = null;
108 if (delayedFields.Any())
109 {
110 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
111 }
112
113 this.LocalizeUI(this.Output.Tables);
114
115 // Process the summary information table before the other tables.
116 bool compressed;
117 bool longNames;
118 int installerVersion;
119 string modularizationGuid;
120 {
121 BindSummaryInfoCommand command = new BindSummaryInfoCommand();
122 command.Output = this.Output;
123 command.Execute();
124
125 compressed = command.Compressed;
126 longNames = command.LongNames;
127 installerVersion = command.InstallerVersion;
128 modularizationGuid = command.ModularizationGuid;
129 }
130
131 // Stop processing if an error previously occurred.
132 if (Messaging.Instance.EncounteredError)
133 {
134 return;
135 }
136
137 // Modularize identifiers and add tables with real streams to the import tables.
138 if (OutputType.Module == this.Output.Type)
139 {
140 // Gather all the suppress modularization identifiers
141 HashSet<string> suppressModularizationIdentifiers = null;
142 Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"];
143 if (null != wixSuppressModularizationTable)
144 {
145 suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0]));
146 }
147
148 foreach (Table table in this.Output.Tables)
149 {
150 table.Modularize(modularizationGuid, suppressModularizationIdentifiers);
151 }
152 }
153
154 // This must occur after all variables and source paths have been resolved and after modularization.
155 List<FileFacade> fileFacades;
156 {
157 GetFileFacadesCommand command = new GetFileFacadesCommand();
158 command.FileTable = this.Output.Tables["File"];
159 command.WixFileTable = this.Output.Tables["WixFile"];
160 command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"];
161 command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"];
162 command.Execute();
163
164 fileFacades = command.FileFacades;
165 }
166
167 ////if (OutputType.Patch == this.Output.Type)
168 ////{
169 //// foreach (SubStorage substorage in this.Output.SubStorages)
170 //// {
171 //// Output transform = substorage.Data;
172
173 //// ResolveFieldsCommand command = new ResolveFieldsCommand();
174 //// command.Tables = transform.Tables;
175 //// command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
176 //// command.FileManagerCore = this.FileManagerCore;
177 //// command.FileManagers = this.FileManagers;
178 //// command.SupportDelayedResolution = false;
179 //// command.TempFilesLocation = this.TempFilesLocation;
180 //// command.WixVariableResolver = this.WixVariableResolver;
181 //// command.Execute();
182
183 //// this.MergeUnrealTables(transform.Tables);
184 //// }
185 ////}
186
187 {
188 CreateSpecialPropertiesCommand command = new CreateSpecialPropertiesCommand();
189 command.PropertyTable = this.Output.Tables["Property"];
190 command.WixPropertyTable = this.Output.Tables["WixProperty"];
191 command.Execute();
192 }
193
194 if (Messaging.Instance.EncounteredError)
195 {
196 return;
197 }
198
199 // Add binder variables for all properties.
200 Table propertyTable = this.Output.Tables["Property"];
201 if (null != propertyTable)
202 {
203 foreach (PropertyRow propertyRow in propertyTable.Rows)
204 {
205 // Set the ProductCode if it is to be generated.
206 if (OutputType.Product == this.Output.Type && "ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal))
207 {
208 propertyRow.Value = Common.GenerateGuid();
209
210 // Update the target ProductCode in any instance transforms.
211 foreach (SubStorage subStorage in this.Output.SubStorages)
212 {
213 Output subStorageOutput = subStorage.Data;
214 if (OutputType.Transform != subStorageOutput.Type)
215 {
216 continue;
217 }
218
219 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"];
220 foreach (Row row in instanceSummaryInformationTable.Rows)
221 {
222 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0))
223 {
224 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
225 break;
226 }
227 }
228 }
229 }
230
231 // Add the property name and value to the variableCache.
232 if (null != variableCache)
233 {
234 string key = String.Concat("property.", Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property));
235 variableCache[key] = propertyRow.Value;
236 }
237 }
238 }
239
240 // Extract files that come from cabinet files (this does not extract files from merge modules).
241 {
242 ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand();
243 command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles;
244 command.Execute();
245 }
246
247 if (OutputType.Product == this.Output.Type)
248 {
249 // Retrieve files and their information from merge modules.
250 Table wixMergeTable = this.Output.Tables["WixMerge"];
251
252 if (null != wixMergeTable)
253 {
254 ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand();
255 command.FileFacades = fileFacades;
256 command.FileTable = this.Output.Tables["File"];
257 command.WixFileTable = this.Output.Tables["WixFile"];
258 command.WixMergeTable = wixMergeTable;
259 command.OutputInstallerVersion = installerVersion;
260 command.SuppressLayout = this.SuppressLayout;
261 command.TempFilesLocation = this.TempFilesLocation;
262 command.Execute();
263
264 fileFacades.AddRange(command.MergeModulesFileFacades);
265 }
266 }
267 else if (OutputType.Patch == this.Output.Type)
268 {
269 // Merge transform data into the output object.
270 IEnumerable<FileFacade> filesFromTransform = this.CopyFromTransformData(this.Output);
271
272 fileFacades.AddRange(filesFromTransform);
273 }
274
275 // stop processing if an error previously occurred
276 if (Messaging.Instance.EncounteredError)
277 {
278 return;
279 }
280
281 Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation());
282
283 // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table).
284 {
285 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
286 command.FileFacades = fileFacades;
287 command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule);
288 command.ModularizationGuid = modularizationGuid;
289 command.Output = this.Output;
290 command.OverwriteHash = true;
291 command.TableDefinitions = this.TableDefinitions;
292 command.VariableCache = variableCache;
293 command.Execute();
294 }
295
296 // Set generated component guids.
297 this.SetComponentGuids(this.Output);
298
299 // With the Component Guids set now we can create instance transforms.
300 this.CreateInstanceTransforms(this.Output);
301
302 this.ValidateComponentGuids(this.Output);
303
304 this.UpdateControlText(this.Output);
305
306 if (delayedFields.Any())
307 {
308 ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand();
309 command.OutputType = this.Output.Type;
310 command.DelayedFields = delayedFields;
311 command.ModularizationGuid = null;
312 command.VariableCache = variableCache;
313 command.Execute();
314 }
315
316 // Assign files to media.
317 RowDictionary<MediaRow> assignedMediaRows;
318 Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia;
319 IEnumerable<FileFacade> uncompressedFiles;
320 {
321 AssignMediaCommand command = new AssignMediaCommand();
322 command.FilesCompressed = compressed;
323 command.FileFacades = fileFacades;
324 command.Output = this.Output;
325 command.TableDefinitions = this.TableDefinitions;
326 command.Execute();
327
328 assignedMediaRows = command.MediaRows;
329 filesByCabinetMedia = command.FileFacadesByCabinetMedia;
330 uncompressedFiles = command.UncompressedFileFacades;
331 }
332
333 // Update file sequence.
334 this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows);
335
336 // stop processing if an error previously occurred
337 if (Messaging.Instance.EncounteredError)
338 {
339 return;
340 }
341
342 // Extended binder extensions can be called now that fields are resolved.
343 {
344 Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]);
345
346 foreach (BinderExtension extension in this.Extensions)
347 {
348 extension.AfterResolvedFields(this.Output);
349 }
350
351 List<FileFacade> updatedFileFacades = new List<FileFacade>();
352
353 foreach (Row updatedFile in updatedFiles.Rows)
354 {
355 string updatedId = updatedFile.FieldAsString(0);
356
357 FileFacade updatedFacade = fileFacades.First(f => f.File.File.Equals(updatedId));
358
359 updatedFileFacades.Add(updatedFacade);
360 }
361
362 if (updatedFileFacades.Any())
363 {
364 UpdateFileFacadesCommand command = new UpdateFileFacadesCommand();
365 command.FileFacades = fileFacades;
366 command.UpdateFileFacades = updatedFileFacades;
367 command.ModularizationGuid = modularizationGuid;
368 command.Output = this.Output;
369 command.OverwriteHash = true;
370 command.TableDefinitions = this.TableDefinitions;
371 command.VariableCache = variableCache;
372 command.Execute();
373 }
374 }
375
376 // stop processing if an error previously occurred
377 if (Messaging.Instance.EncounteredError)
378 {
379 return;
380 }
381
382 Directory.CreateDirectory(this.TempFilesLocation);
383
384 if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch)
385 {
386 CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand();
387 command.FileFacades = fileFacades;
388 command.WixPatchIdTable = this.Output.Tables["WixPatchId"];
389 command.TempFilesLocation = this.TempFilesLocation;
390 command.Execute();
391 }
392
393 // create cabinet files and process uncompressed files
394 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
395 if (!this.SuppressLayout || OutputType.Module == this.Output.Type)
396 {
397 Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles());
398
399 CreateCabinetsCommand command = new CreateCabinetsCommand();
400 command.CabbingThreadCount = this.CabbingThreadCount;
401 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
402 command.Output = this.Output;
403 command.FileManagers = this.FileManagers;
404 command.LayoutDirectory = layoutDirectory;
405 command.Compressed = compressed;
406 command.FileRowsByCabinet = filesByCabinetMedia;
407 command.ResolveMedia = this.ResolveMedia;
408 command.TableDefinitions = this.TableDefinitions;
409 command.TempFilesLocation = this.TempFilesLocation;
410 command.WixMediaTable = this.Output.Tables["WixMedia"];
411 command.Execute();
412
413 fileTransfers.AddRange(command.FileTransfers);
414 }
415
416 if (OutputType.Patch == this.Output.Type)
417 {
418 // copy output data back into the transforms
419 this.CopyToTransformData(this.Output);
420 }
421
422 // stop processing if an error previously occurred
423 if (Messaging.Instance.EncounteredError)
424 {
425 return;
426 }
427
428 // add back suppressed tables which must be present prior to merging in modules
429 if (OutputType.Product == this.Output.Type)
430 {
431 Table wixMergeTable = this.Output.Tables["WixMerge"];
432
433 if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count)
434 {
435 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
436 {
437 string sequenceTableName = sequence.ToString();
438 Table sequenceTable = this.Output.Tables[sequenceTableName];
439
440 if (null == sequenceTable)
441 {
442 sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]);
443 }
444
445 if (0 == sequenceTable.Rows.Count)
446 {
447 suppressedTableNames.Add(sequenceTableName);
448 }
449 }
450 }
451 }
452
453 foreach (BinderExtension extension in this.Extensions)
454 {
455 extension.Finish(this.Output);
456 }
457
458 // generate database file
459 Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase());
460 string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(this.OutputPath));
461 this.GenerateDatabase(this.Output, tempDatabaseFile, false, false);
462
463 FileTransfer transfer;
464 if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future
465 {
466 transfer.Built = true;
467 fileTransfers.Add(transfer);
468 }
469
470 // stop processing if an error previously occurred
471 if (Messaging.Instance.EncounteredError)
472 {
473 return;
474 }
475
476 // Output the output to a file
477 Pdb pdb = new Pdb();
478 pdb.Output = this.Output;
479 if (!String.IsNullOrEmpty(this.PdbFile))
480 {
481 pdb.Save(this.PdbFile);
482 }
483
484 // Merge modules.
485 if (OutputType.Product == this.Output.Type)
486 {
487 Messaging.Instance.OnMessage(WixVerboses.MergingModules());
488
489 MergeModulesCommand command = new MergeModulesCommand();
490 command.FileFacades = fileFacades;
491 command.Output = this.Output;
492 command.OutputPath = tempDatabaseFile;
493 command.SuppressedTableNames = suppressedTableNames;
494 command.Execute();
495
496 // stop processing if an error previously occurred
497 if (Messaging.Instance.EncounteredError)
498 {
499 return;
500 }
501 }
502
503 // inspect the MSI prior to running ICEs
504 InspectorCore inspectorCore = new InspectorCore();
505 foreach (InspectorExtension inspectorExtension in this.InspectorExtensions)
506 {
507 inspectorExtension.Core = inspectorCore;
508 inspectorExtension.InspectDatabase(tempDatabaseFile, pdb);
509
510 inspectorExtension.Core = null; // reset.
511 }
512
513 if (Messaging.Instance.EncounteredError)
514 {
515 return;
516 }
517
518 // validate the output if there is an MSI validator
519 if (null != this.Validator)
520 {
521 Stopwatch stopwatch = Stopwatch.StartNew();
522
523 // set the output file for source line information
524 this.Validator.Output = this.Output;
525
526 Messaging.Instance.OnMessage(WixVerboses.ValidatingDatabase());
527
528 this.Validator.Validate(tempDatabaseFile);
529
530 stopwatch.Stop();
531 Messaging.Instance.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds));
532
533 // Stop processing if an error occurred.
534 if (Messaging.Instance.EncounteredError)
535 {
536 return;
537 }
538 }
539
540 // Process uncompressed files.
541 if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
542 {
543 ProcessUncompressedFilesCommand command = new ProcessUncompressedFilesCommand();
544 command.Compressed = compressed;
545 command.FileFacades = uncompressedFiles;
546 command.LayoutDirectory = layoutDirectory;
547 command.LongNamesInImage = longNames;
548 command.MediaRows = assignedMediaRows;
549 command.ResolveMedia = this.ResolveMedia;
550 command.DatabasePath = tempDatabaseFile;
551 command.WixMediaTable = this.Output.Tables["WixMedia"];
552 command.Execute();
553
554 fileTransfers.AddRange(command.FileTransfers);
555 }
556
557 this.FileTransfers = fileTransfers;
558 this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList();
559 }
560
561 /// <summary>
562 /// Localize dialogs and controls.
563 /// </summary>
564 /// <param name="tables">The tables to localize.</param>
565 private void LocalizeUI(TableIndexedCollection tables)
566 {
567 Table dialogTable = tables["Dialog"];
568 if (null != dialogTable)
569 {
570 foreach (Row row in dialogTable.Rows)
571 {
572 string dialog = (string)row[0];
573 LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, null);
574 if (null != localizedControl)
575 {
576 if (CompilerConstants.IntegerNotSet != localizedControl.X)
577 {
578 row[1] = localizedControl.X;
579 }
580
581 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
582 {
583 row[2] = localizedControl.Y;
584 }
585
586 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
587 {
588 row[3] = localizedControl.Width;
589 }
590
591 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
592 {
593 row[4] = localizedControl.Height;
594 }
595
596 row[5] = (int)row[5] | localizedControl.Attributes;
597
598 if (!String.IsNullOrEmpty(localizedControl.Text))
599 {
600 row[6] = localizedControl.Text;
601 }
602 }
603 }
604 }
605
606 Table controlTable = tables["Control"];
607 if (null != controlTable)
608 {
609 foreach (Row row in controlTable.Rows)
610 {
611 string dialog = (string)row[0];
612 string control = (string)row[1];
613 LocalizedControl localizedControl = this.Localizer.GetLocalizedControl(dialog, control);
614 if (null != localizedControl)
615 {
616 if (CompilerConstants.IntegerNotSet != localizedControl.X)
617 {
618 row[3] = localizedControl.X.ToString();
619 }
620
621 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
622 {
623 row[4] = localizedControl.Y.ToString();
624 }
625
626 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
627 {
628 row[5] = localizedControl.Width.ToString();
629 }
630
631 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
632 {
633 row[6] = localizedControl.Height.ToString();
634 }
635
636 row[7] = (int)row[7] | localizedControl.Attributes;
637
638 if (!String.IsNullOrEmpty(localizedControl.Text))
639 {
640 row[9] = localizedControl.Text;
641 }
642 }
643 }
644 }
645 }
646
647 /// <summary>
648 /// Copy file data between transform substorages and the patch output object
649 /// </summary>
650 /// <param name="output">The output to bind.</param>
651 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
652 private IEnumerable<FileFacade> CopyFromTransformData(Output output)
653 {
654 CopyTransformDataCommand command = new CopyTransformDataCommand();
655 command.CopyOutFileRows = true;
656 command.FileManagerCore = this.FileManagerCore;
657 command.FileManagers = this.FileManagers;
658 command.Output = output;
659 command.TableDefinitions = this.TableDefinitions;
660 command.Execute();
661
662 return command.FileFacades;
663 }
664
665 /// <summary>
666 /// Copy file data between transform substorages and the patch output object
667 /// </summary>
668 /// <param name="output">The output to bind.</param>
669 /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param>
670 private void CopyToTransformData(Output output)
671 {
672 CopyTransformDataCommand command = new CopyTransformDataCommand();
673 command.CopyOutFileRows = false;
674 command.FileManagerCore = this.FileManagerCore;
675 command.FileManagers = this.FileManagers;
676 command.Output = output;
677 command.TableDefinitions = this.TableDefinitions;
678 command.Execute();
679 }
680
681 /// <summary>
682 /// Takes an id, and demodularizes it (if possible).
683 /// </summary>
684 /// <remarks>
685 /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id.
686 /// </remarks>
687 /// <param name="outputType">The type of the output to bind.</param>
688 /// <param name="modularizationGuid">The modularization GUID.</param>
689 /// <param name="id">The id to demodularize.</param>
690 /// <returns>The demodularized id.</returns>
691 internal static string Demodularize(OutputType outputType, string modularizationGuid, string id)
692 {
693 if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal))
694 {
695 id = id.Substring(0, id.Length - 37);
696 }
697
698 return id;
699 }
700
701 private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows)
702 {
703 // Calculate sequence numbers and media disk id layout for all file media information objects.
704 if (OutputType.Module == outputType)
705 {
706 int lastSequence = 0;
707 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
708 {
709 facade.File.Sequence = ++lastSequence;
710 }
711 }
712 else
713 {
714 int lastSequence = 0;
715 MediaRow mediaRow = null;
716 Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>();
717
718 // sequence the non-patch-added files
719 foreach (FileFacade facade in fileFacades) // TODO: Sort these rows directory path and component id and maybe file size or file extension and other creative ideas to get optimal install speed out of MSI.
720 {
721 if (null == mediaRow)
722 {
723 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
724 if (OutputType.Patch == outputType)
725 {
726 // patch Media cannot start at zero
727 lastSequence = mediaRow.LastSequence;
728 }
729 }
730 else if (mediaRow.DiskId != facade.WixFile.DiskId)
731 {
732 mediaRow.LastSequence = lastSequence;
733 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
734 }
735
736 if (0 < facade.WixFile.PatchGroup)
737 {
738 List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup];
739
740 if (null == patchGroup)
741 {
742 patchGroup = new List<FileFacade>();
743 patchGroups.Add(facade.WixFile.PatchGroup, patchGroup);
744 }
745
746 patchGroup.Add(facade);
747 }
748 else
749 {
750 facade.File.Sequence = ++lastSequence;
751 }
752 }
753
754 if (null != mediaRow)
755 {
756 mediaRow.LastSequence = lastSequence;
757 mediaRow = null;
758 }
759
760 // sequence the patch-added files
761 foreach (List<FileFacade> patchGroup in patchGroups.Values)
762 {
763 foreach (FileFacade facade in patchGroup)
764 {
765 if (null == mediaRow)
766 {
767 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
768 }
769 else if (mediaRow.DiskId != facade.WixFile.DiskId)
770 {
771 mediaRow.LastSequence = lastSequence;
772 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
773 }
774
775 facade.File.Sequence = ++lastSequence;
776 }
777 }
778
779 if (null != mediaRow)
780 {
781 mediaRow.LastSequence = lastSequence;
782 }
783 }
784 }
785
786 /// <summary>
787 /// Set the guids for components with generatable guids.
788 /// </summary>
789 /// <param name="output">Internal representation of the database to operate on.</param>
790 private void SetComponentGuids(Output output)
791 {
792 Table componentTable = output.Tables["Component"];
793 if (null != componentTable)
794 {
795 Hashtable registryKeyRows = null;
796 Hashtable directories = null;
797 Hashtable componentIdGenSeeds = null;
798 Dictionary<string, List<FileRow>> fileRows = null;
799
800 // find components with generatable guids
801 foreach (ComponentRow componentRow in componentTable.Rows)
802 {
803 // component guid will be generated
804 if ("*" == componentRow.Guid)
805 {
806 if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath)
807 {
808 Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers));
809 }
810 else if (componentRow.IsRegistryKeyPath)
811 {
812 if (null == registryKeyRows)
813 {
814 Table registryTable = output.Tables["Registry"];
815
816 registryKeyRows = new Hashtable(registryTable.Rows.Count);
817
818 foreach (Row registryRow in registryTable.Rows)
819 {
820 registryKeyRows.Add((string)registryRow[0], registryRow);
821 }
822 }
823
824 Row foundRow = registryKeyRows[componentRow.KeyPath] as Row;
825
826 string bitness = componentRow.Is64Bit ? "64" : String.Empty;
827 if (null != foundRow)
828 {
829 string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]);
830 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant();
831 }
832 }
833 else // must be a File KeyPath
834 {
835 // if the directory table hasn't been loaded into an indexed hash
836 // of directory ids to target names do that now.
837 if (null == directories)
838 {
839 Table directoryTable = output.Tables["Directory"];
840
841 int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0;
842
843 directories = new Hashtable(numDirectoryTableRows);
844
845 // get the target paths for all directories
846 if (null != directoryTable)
847 {
848 foreach (Row row in directoryTable.Rows)
849 {
850 // if the directory Id already exists, we will skip it here since
851 // checking for duplicate primary keys is done later when importing tables
852 // into database
853 if (directories.ContainsKey(row[0]))
854 {
855 continue;
856 }
857
858 string targetName = Installer.GetName((string)row[2], false, true);
859 directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName));
860 }
861 }
862 }
863
864 // if the component id generation seeds have not been indexed
865 // from the WixDirectory table do that now.
866 if (null == componentIdGenSeeds)
867 {
868 Table wixDirectoryTable = output.Tables["WixDirectory"];
869
870 int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0;
871
872 componentIdGenSeeds = new Hashtable(numWixDirectoryRows);
873
874 // if there are any WixDirectory rows, build up the Component Guid
875 // generation seeds indexed by Directory/@Id.
876 if (null != wixDirectoryTable)
877 {
878 foreach (Row row in wixDirectoryTable.Rows)
879 {
880 componentIdGenSeeds.Add(row[0], (string)row[1]);
881 }
882 }
883 }
884
885 // if the file rows have not been indexed by File.Component yet
886 // then do that now
887 if (null == fileRows)
888 {
889 Table fileTable = output.Tables["File"];
890
891 int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0;
892
893 fileRows = new Dictionary<string, List<FileRow>>(numFileRows);
894
895 if (null != fileTable)
896 {
897 foreach (FileRow file in fileTable.Rows)
898 {
899 List<FileRow> files;
900 if (!fileRows.TryGetValue(file.Component, out files))
901 {
902 files = new List<FileRow>();
903 fileRows.Add(file.Component, files);
904 }
905
906 files.Add(file);
907 }
908 }
909 }
910
911 // validate component meets all the conditions to have a generated guid
912 List<FileRow> currentComponentFiles = fileRows[componentRow.Component];
913 int numFilesInComponent = currentComponentFiles.Count;
914 string path = null;
915
916 foreach (FileRow fileRow in currentComponentFiles)
917 {
918 if (fileRow.File == componentRow.KeyPath)
919 {
920 // calculate the key file's canonical target path
921 string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true);
922 string fileName = Installer.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture);
923 path = Path.Combine(directoryPath, fileName);
924
925 // find paths that are not canonicalized
926 if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
927 path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
928 path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
929 path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
930 path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
931 path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
932 {
933 Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path));
934 }
935
936 // if component has more than one file, the key path must be versioned
937 if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version))
938 {
939 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers));
940 }
941 }
942 else
943 {
944 // not a key path, so it must be an unversioned file if component has more than one file
945 if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version))
946 {
947 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers));
948 }
949 }
950 }
951
952 // if the rules were followed, reward with a generated guid
953 if (!Messaging.Instance.EncounteredError)
954 {
955 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant();
956 }
957 }
958 }
959 }
960 }
961 }
962
963 /// <summary>
964 /// Creates instance transform substorages in the output.
965 /// </summary>
966 /// <param name="output">Output containing instance transform definitions.</param>
967 private void CreateInstanceTransforms(Output output)
968 {
969 // Create and add substorages for instance transforms.
970 Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"];
971 if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count)
972 {
973 string targetProductCode = null;
974 string targetUpgradeCode = null;
975 string targetProductVersion = null;
976
977 Table targetSummaryInformationTable = output.Tables["_SummaryInformation"];
978 Table targetPropertyTable = output.Tables["Property"];
979
980 // Get the data from target database
981 foreach (Row propertyRow in targetPropertyTable.Rows)
982 {
983 if ("ProductCode" == (string)propertyRow[0])
984 {
985 targetProductCode = (string)propertyRow[1];
986 }
987 else if ("ProductVersion" == (string)propertyRow[0])
988 {
989 targetProductVersion = (string)propertyRow[1];
990 }
991 else if ("UpgradeCode" == (string)propertyRow[0])
992 {
993 targetUpgradeCode = (string)propertyRow[1];
994 }
995 }
996
997 // Index the Instance Component Rows.
998 Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>();
999 Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"];
1000 if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count)
1001 {
1002 foreach (Row row in targetInstanceComponentTable.Rows)
1003 {
1004 // Build up all the instances, we'll get the Components rows from the real Component table.
1005 instanceComponentGuids.Add((string)row[0], null);
1006 }
1007
1008 Table targetComponentTable = output.Tables["Component"];
1009 foreach (ComponentRow componentRow in targetComponentTable.Rows)
1010 {
1011 string component = (string)componentRow[0];
1012 if (instanceComponentGuids.ContainsKey(component))
1013 {
1014 instanceComponentGuids[component] = componentRow;
1015 }
1016 }
1017 }
1018
1019 // Generate the instance transforms
1020 foreach (Row instanceRow in wixInstanceTransformsTable.Rows)
1021 {
1022 string instanceId = (string)instanceRow[0];
1023
1024 Output instanceTransform = new Output(instanceRow.SourceLineNumbers);
1025 instanceTransform.Type = OutputType.Transform;
1026 instanceTransform.Codepage = output.Codepage;
1027
1028 Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
1029 string targetPlatformAndLanguage = null;
1030
1031 foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows)
1032 {
1033 if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE
1034 {
1035 targetPlatformAndLanguage = (string)summaryInformationRow[1];
1036 }
1037
1038 // Copy the row's data to the transform.
1039 Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null);
1040 copyOfSummaryRow[0] = summaryInformationRow[0];
1041 copyOfSummaryRow[1] = summaryInformationRow[1];
1042 }
1043
1044 // Modify the appropriate properties.
1045 Table propertyTable = instanceTransform.EnsureTable(this.TableDefinitions["Property"]);
1046
1047 // Change the ProductCode property
1048 string productCode = (string)instanceRow[2];
1049 if ("*" == productCode)
1050 {
1051 productCode = Common.GenerateGuid();
1052 }
1053
1054 Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1055 productCodeRow.Operation = RowOperation.Modify;
1056 productCodeRow.Fields[1].Modified = true;
1057 productCodeRow[0] = "ProductCode";
1058 productCodeRow[1] = productCode;
1059
1060 // Change the instance property
1061 Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1062 instanceIdRow.Operation = RowOperation.Modify;
1063 instanceIdRow.Fields[1].Modified = true;
1064 instanceIdRow[0] = (string)instanceRow[1];
1065 instanceIdRow[1] = instanceId;
1066
1067 if (null != instanceRow[3])
1068 {
1069 // Change the ProductName property
1070 Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1071 productNameRow.Operation = RowOperation.Modify;
1072 productNameRow.Fields[1].Modified = true;
1073 productNameRow[0] = "ProductName";
1074 productNameRow[1] = (string)instanceRow[3];
1075 }
1076
1077 if (null != instanceRow[4])
1078 {
1079 // Change the UpgradeCode property
1080 Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers);
1081 upgradeCodeRow.Operation = RowOperation.Modify;
1082 upgradeCodeRow.Fields[1].Modified = true;
1083 upgradeCodeRow[0] = "UpgradeCode";
1084 upgradeCodeRow[1] = instanceRow[4];
1085
1086 // Change the Upgrade table
1087 Table targetUpgradeTable = output.Tables["Upgrade"];
1088 if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count)
1089 {
1090 string upgradeId = (string)instanceRow[4];
1091 Table upgradeTable = instanceTransform.EnsureTable(this.TableDefinitions["Upgrade"]);
1092 foreach (Row row in targetUpgradeTable.Rows)
1093 {
1094 // In case they are upgrading other codes to this new product, leave the ones that don't match the
1095 // Product.UpgradeCode intact.
1096 if (targetUpgradeCode == (string)row[0])
1097 {
1098 Row upgradeRow = upgradeTable.CreateRow(null);
1099 upgradeRow.Operation = RowOperation.Add;
1100 upgradeRow.Fields[0].Modified = true;
1101 // I was hoping to be able to RowOperation.Modify, but that didn't appear to function.
1102 // upgradeRow.Fields[0].PreviousData = (string)row[0];
1103
1104 // Inserting a new Upgrade record with the updated UpgradeCode
1105 upgradeRow[0] = upgradeId;
1106 upgradeRow[1] = row[1];
1107 upgradeRow[2] = row[2];
1108 upgradeRow[3] = row[3];
1109 upgradeRow[4] = row[4];
1110 upgradeRow[5] = row[5];
1111 upgradeRow[6] = row[6];
1112
1113 // Delete the old row
1114 Row upgradeRemoveRow = upgradeTable.CreateRow(null);
1115 upgradeRemoveRow.Operation = RowOperation.Delete;
1116 upgradeRemoveRow[0] = row[0];
1117 upgradeRemoveRow[1] = row[1];
1118 upgradeRemoveRow[2] = row[2];
1119 upgradeRemoveRow[3] = row[3];
1120 upgradeRemoveRow[4] = row[4];
1121 upgradeRemoveRow[5] = row[5];
1122 upgradeRemoveRow[6] = row[6];
1123 }
1124 }
1125 }
1126 }
1127
1128 // If there are instance Components generate new GUIDs for them.
1129 if (0 < instanceComponentGuids.Count)
1130 {
1131 Table componentTable = instanceTransform.EnsureTable(this.TableDefinitions["Component"]);
1132 foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values)
1133 {
1134 string guid = targetComponentRow.Guid;
1135 if (!String.IsNullOrEmpty(guid))
1136 {
1137 Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers);
1138 instanceComponentRow.Operation = RowOperation.Modify;
1139 instanceComponentRow.Fields[1].Modified = true;
1140 instanceComponentRow[0] = targetComponentRow[0];
1141 instanceComponentRow[1] = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, String.Concat(guid, instanceId)).ToString("B").ToUpper(CultureInfo.InvariantCulture);
1142 instanceComponentRow[2] = targetComponentRow[2];
1143 instanceComponentRow[3] = targetComponentRow[3];
1144 instanceComponentRow[4] = targetComponentRow[4];
1145 instanceComponentRow[5] = targetComponentRow[5];
1146 }
1147 }
1148 }
1149
1150 // Update the summary information
1151 Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count);
1152 foreach (Row row in instanceSummaryInformationTable.Rows)
1153 {
1154 summaryRows[row[0]] = row;
1155
1156 if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
1157 {
1158 row[1] = targetPlatformAndLanguage;
1159 }
1160 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
1161 {
1162 row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode);
1163 }
1164 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
1165 {
1166 row[1] = 0;
1167 }
1168 else if ((int)SummaryInformation.Transform.Security == (int)row[0])
1169 {
1170 row[1] = "4";
1171 }
1172 }
1173
1174 if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage))
1175 {
1176 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1177 summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage;
1178 summaryRow[1] = targetPlatformAndLanguage;
1179 }
1180 else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags))
1181 {
1182 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1183 summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags;
1184 summaryRow[1] = "0";
1185 }
1186 else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security))
1187 {
1188 Row summaryRow = instanceSummaryInformationTable.CreateRow(null);
1189 summaryRow[0] = (int)SummaryInformation.Transform.Security;
1190 summaryRow[1] = "4";
1191 }
1192
1193 output.SubStorages.Add(new SubStorage(instanceId, instanceTransform));
1194 }
1195 }
1196 }
1197
1198 /// <summary>
1199 /// Validate that there are no duplicate GUIDs in the output.
1200 /// </summary>
1201 /// <remarks>
1202 /// Duplicate GUIDs without conditions are an error condition; with conditions, it's a
1203 /// warning, as the conditions might be mutually exclusive.
1204 /// </remarks>
1205 private void ValidateComponentGuids(Output output)
1206 {
1207 Table componentTable = output.Tables["Component"];
1208 if (null != componentTable)
1209 {
1210 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
1211
1212 foreach (ComponentRow row in componentTable.Rows)
1213 {
1214 // we don't care about unmanaged components and if there's a * GUID remaining,
1215 // there's already an error that prevented it from being replaced with a real GUID.
1216 if (!String.IsNullOrEmpty(row.Guid) && "*" != row.Guid)
1217 {
1218 bool thisComponentHasCondition = !String.IsNullOrEmpty(row.Condition);
1219 bool allComponentsHaveConditions = thisComponentHasCondition;
1220
1221 if (componentGuidConditions.ContainsKey(row.Guid))
1222 {
1223 allComponentsHaveConditions = componentGuidConditions[row.Guid] && thisComponentHasCondition;
1224
1225 if (allComponentsHaveConditions)
1226 {
1227 Messaging.Instance.OnMessage(WixWarnings.DuplicateComponentGuidsMustHaveMutuallyExclusiveConditions(row.SourceLineNumbers, row.Component, row.Guid));
1228 }
1229 else
1230 {
1231 Messaging.Instance.OnMessage(WixErrors.DuplicateComponentGuids(row.SourceLineNumbers, row.Component, row.Guid));
1232 }
1233 }
1234
1235 componentGuidConditions[row.Guid] = allComponentsHaveConditions;
1236 }
1237 }
1238 }
1239 }
1240
1241 /// <summary>
1242 /// Update Control and BBControl text by reading from files when necessary.
1243 /// </summary>
1244 /// <param name="output">Internal representation of the msi database to operate upon.</param>
1245 private void UpdateControlText(Output output)
1246 {
1247 UpdateControlTextCommand command = new UpdateControlTextCommand();
1248 command.BBControlTable = output.Tables["BBControl"];
1249 command.WixBBControlTable = output.Tables["WixBBControl"];
1250 command.ControlTable = output.Tables["Control"];
1251 command.WixControlTable = output.Tables["WixControl"];
1252 command.Execute();
1253 }
1254
1255 private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory)
1256 {
1257 string layout = null;
1258
1259 foreach (IBinderFileManager fileManager in this.FileManagers)
1260 {
1261 layout = fileManager.ResolveMedia(mediaRow, mediaLayoutDirectory, layoutDirectory);
1262 if (!String.IsNullOrEmpty(layout))
1263 {
1264 break;
1265 }
1266 }
1267
1268 // If no binder file manager resolved the layout, do the default behavior.
1269 if (String.IsNullOrEmpty(layout))
1270 {
1271 if (String.IsNullOrEmpty(mediaLayoutDirectory))
1272 {
1273 layout = layoutDirectory;
1274 }
1275 else if (Path.IsPathRooted(mediaLayoutDirectory))
1276 {
1277 layout = mediaLayoutDirectory;
1278 }
1279 else
1280 {
1281 layout = Path.Combine(layoutDirectory, mediaLayoutDirectory);
1282 }
1283 }
1284
1285 return layout;
1286 }
1287
1288 /// <summary>
1289 /// Creates the MSI/MSM/PCP database.
1290 /// </summary>
1291 /// <param name="output">Output to create database for.</param>
1292 /// <param name="databaseFile">The database file to create.</param>
1293 /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param>
1294 /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param>
1295 private void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory)
1296 {
1297 GenerateDatabaseCommand command = new GenerateDatabaseCommand();
1298 command.Extensions = this.Extensions;
1299 command.FileManagers = this.FileManagers;
1300 command.Output = output;
1301 command.OutputPath = databaseFile;
1302 command.KeepAddedColumns = keepAddedColumns;
1303 command.UseSubDirectory = useSubdirectory;
1304 command.SuppressAddingValidationRows = this.SuppressAddingValidationRows;
1305 command.TableDefinitions = this.TableDefinitions;
1306 command.TempFilesLocation = this.TempFilesLocation;
1307 command.Codepage = this.Codepage;
1308 command.Execute();
1309 }
1310 }
1311}
diff --git a/src/WixToolset.Core/Bind/BindTransformCommand.cs b/src/WixToolset.Core/Bind/BindTransformCommand.cs
deleted file mode 100644
index e909f191..00000000
--- a/src/WixToolset.Core/Bind/BindTransformCommand.cs
+++ /dev/null
@@ -1,473 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Extensibility;
11 using WixToolset.Msi;
12 using WixToolset.Core.Native;
13
14 internal class BindTransformCommand : ICommand
15 {
16 public IEnumerable<IBinderExtension> Extensions { private get; set; }
17
18 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
19
20 public TableDefinitionCollection TableDefinitions { private get; set; }
21
22 public string TempFilesLocation { private get; set; }
23
24 public Output Transform { private get; set; }
25
26 public string OutputPath { private get; set; }
27
28 public void Execute()
29 {
30 int transformFlags = 0;
31
32 Output targetOutput = new Output(null);
33 Output updatedOutput = new Output(null);
34
35 // TODO: handle added columns
36
37 // to generate a localized transform, both the target and updated
38 // databases need to have the same code page. the only reason to
39 // set different code pages is to support localized primary key
40 // columns, but that would only support deleting rows. if this
41 // becomes necessary, define a PreviousCodepage property on the
42 // Output class and persist this throughout transform generation.
43 targetOutput.Codepage = this.Transform.Codepage;
44 updatedOutput.Codepage = this.Transform.Codepage;
45
46 // remove certain Property rows which will be populated from summary information values
47 string targetUpgradeCode = null;
48 string updatedUpgradeCode = null;
49
50 Table propertyTable = this.Transform.Tables["Property"];
51 if (null != propertyTable)
52 {
53 for (int i = propertyTable.Rows.Count - 1; i >= 0; i--)
54 {
55 Row row = propertyTable.Rows[i];
56
57 if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0])
58 {
59 propertyTable.Rows.RemoveAt(i);
60
61 if ("UpgradeCode" == (string)row[0])
62 {
63 updatedUpgradeCode = (string)row[1];
64 }
65 }
66 }
67 }
68
69 Table targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
70 Table updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]);
71 Table targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]);
72 Table updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]);
73
74 // process special summary information values
75 foreach (Row row in this.Transform.Tables["_SummaryInformation"].Rows)
76 {
77 if ((int)SummaryInformation.Transform.CodePage == (int)row[0])
78 {
79 // convert from a web name if provided
80 string codePage = (string)row.Fields[1].Data;
81 if (null == codePage)
82 {
83 codePage = "0";
84 }
85 else
86 {
87 codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture);
88 }
89
90 string previousCodePage = (string)row.Fields[1].PreviousData;
91 if (null == previousCodePage)
92 {
93 previousCodePage = "0";
94 }
95 else
96 {
97 previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture);
98 }
99
100 Row targetCodePageRow = targetSummaryInfo.CreateRow(null);
101 targetCodePageRow[0] = 1; // PID_CODEPAGE
102 targetCodePageRow[1] = previousCodePage;
103
104 Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null);
105 updatedCodePageRow[0] = 1; // PID_CODEPAGE
106 updatedCodePageRow[1] = codePage;
107 }
108 else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ||
109 (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0])
110 {
111 // the target language
112 string[] propertyData = ((string)row[1]).Split(';');
113 string lang = 2 == propertyData.Length ? propertyData[1] : "0";
114
115 Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo;
116 Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable;
117
118 Row productLanguageRow = tempPropertyTable.CreateRow(null);
119 productLanguageRow[0] = "ProductLanguage";
120 productLanguageRow[1] = lang;
121
122 // set the platform;language on the MSI to be generated
123 Row templateRow = tempSummaryInfo.CreateRow(null);
124 templateRow[0] = 7; // PID_TEMPLATE
125 templateRow[1] = (string)row[1];
126 }
127 else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0])
128 {
129 string[] propertyData = ((string)row[1]).Split(';');
130
131 Row targetProductCodeRow = targetPropertyTable.CreateRow(null);
132 targetProductCodeRow[0] = "ProductCode";
133 targetProductCodeRow[1] = propertyData[0].Substring(0, 38);
134
135 Row targetProductVersionRow = targetPropertyTable.CreateRow(null);
136 targetProductVersionRow[0] = "ProductVersion";
137 targetProductVersionRow[1] = propertyData[0].Substring(38);
138
139 Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null);
140 updatedProductCodeRow[0] = "ProductCode";
141 updatedProductCodeRow[1] = propertyData[1].Substring(0, 38);
142
143 Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null);
144 updatedProductVersionRow[0] = "ProductVersion";
145 updatedProductVersionRow[1] = propertyData[1].Substring(38);
146
147 // UpgradeCode is optional and may not exists in the target
148 // or upgraded databases, so do not include a null-valued
149 // UpgradeCode property.
150
151 targetUpgradeCode = propertyData[2];
152 if (!String.IsNullOrEmpty(targetUpgradeCode))
153 {
154 Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null);
155 targetUpgradeCodeRow[0] = "UpgradeCode";
156 targetUpgradeCodeRow[1] = targetUpgradeCode;
157
158 // If the target UpgradeCode is specified, an updated
159 // UpgradeCode is required.
160 if (String.IsNullOrEmpty(updatedUpgradeCode))
161 {
162 updatedUpgradeCode = targetUpgradeCode;
163 }
164 }
165
166 if (!String.IsNullOrEmpty(updatedUpgradeCode))
167 {
168 Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null);
169 updatedUpgradeCodeRow[0] = "UpgradeCode";
170 updatedUpgradeCodeRow[1] = updatedUpgradeCode;
171 }
172 }
173 else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0])
174 {
175 transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture);
176 }
177 else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0])
178 {
179 // PID_LASTPRINTED should be null for transforms
180 row.Operation = RowOperation.None;
181 }
182 else
183 {
184 // add everything else as is
185 Row targetRow = targetSummaryInfo.CreateRow(null);
186 targetRow[0] = row[0];
187 targetRow[1] = row[1];
188
189 Row updatedRow = updatedSummaryInfo.CreateRow(null);
190 updatedRow[0] = row[0];
191 updatedRow[1] = row[1];
192 }
193 }
194
195 // Validate that both databases have an UpgradeCode if the
196 // authoring transform will validate the UpgradeCode; otherwise,
197 // MsiCreateTransformSummaryinfo() will fail with 1620.
198 if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 &&
199 (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode)))
200 {
201 Messaging.Instance.OnMessage(WixErrors.BothUpgradeCodesRequired());
202 }
203
204 string emptyFile = null;
205
206 foreach (Table table in this.Transform.Tables)
207 {
208 // Ignore unreal tables when building transforms except the _Stream table.
209 // These tables are ignored when generating the database so there is no reason
210 // to process them here.
211 if (table.Definition.Unreal && "_Streams" != table.Name)
212 {
213 continue;
214 }
215
216 // process table operations
217 switch (table.Operation)
218 {
219 case TableOperation.Add:
220 updatedOutput.EnsureTable(table.Definition);
221 break;
222 case TableOperation.Drop:
223 targetOutput.EnsureTable(table.Definition);
224 continue;
225 default:
226 targetOutput.EnsureTable(table.Definition);
227 updatedOutput.EnsureTable(table.Definition);
228 break;
229 }
230
231 // process row operations
232 foreach (Row row in table.Rows)
233 {
234 switch (row.Operation)
235 {
236 case RowOperation.Add:
237 Table updatedTable = updatedOutput.EnsureTable(table.Definition);
238 updatedTable.Rows.Add(row);
239 continue;
240 case RowOperation.Delete:
241 Table targetTable = targetOutput.EnsureTable(table.Definition);
242 targetTable.Rows.Add(row);
243
244 // fill-in non-primary key values
245 foreach (Field field in row.Fields)
246 {
247 if (!field.Column.PrimaryKey)
248 {
249 if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable)
250 {
251 field.Data = field.Column.MinValue;
252 }
253 else if (ColumnType.Object == field.Column.Type)
254 {
255 if (null == emptyFile)
256 {
257 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
258 }
259
260 field.Data = emptyFile;
261 }
262 else
263 {
264 field.Data = "0";
265 }
266 }
267 }
268 continue;
269 }
270
271 // Assure that the file table's sequence is populated
272 if ("File" == table.Name)
273 {
274 foreach (Row fileRow in table.Rows)
275 {
276 if (null == fileRow[7])
277 {
278 if (RowOperation.Add == fileRow.Operation)
279 {
280 Messaging.Instance.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0]));
281 break;
282 }
283
284 // Set to 1 to prevent invalid IDT file from being generated
285 fileRow[7] = 1;
286 }
287 }
288 }
289
290 // process modified and unmodified rows
291 bool modifiedRow = false;
292 Row targetRow = new Row(null, table.Definition);
293 Row updatedRow = row;
294 for (int i = 0; i < row.Fields.Length; i++)
295 {
296 Field updatedField = row.Fields[i];
297
298 if (updatedField.Modified)
299 {
300 // set a different value in the target row to ensure this value will be modified during transform generation
301 if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable)
302 {
303 if (null == updatedField.Data || 1 != (int)updatedField.Data)
304 {
305 targetRow[i] = 1;
306 }
307 else
308 {
309 targetRow[i] = 2;
310 }
311 }
312 else if (ColumnType.Object == updatedField.Column.Type)
313 {
314 if (null == emptyFile)
315 {
316 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
317 }
318
319 targetRow[i] = emptyFile;
320 }
321 else
322 {
323 if ("0" != (string)updatedField.Data)
324 {
325 targetRow[i] = "0";
326 }
327 else
328 {
329 targetRow[i] = "1";
330 }
331 }
332
333 modifiedRow = true;
334 }
335 else if (ColumnType.Object == updatedField.Column.Type)
336 {
337 ObjectField objectField = (ObjectField)updatedField;
338
339 // create an empty file for comparing against
340 if (null == objectField.PreviousData)
341 {
342 if (null == emptyFile)
343 {
344 emptyFile = Path.Combine(this.TempFilesLocation, "empty");
345 }
346
347 targetRow[i] = emptyFile;
348 modifiedRow = true;
349 }
350 else if (!this.CompareFiles(objectField.PreviousData, (string)objectField.Data))
351 {
352 targetRow[i] = objectField.PreviousData;
353 modifiedRow = true;
354 }
355 }
356 else // unmodified
357 {
358 if (null != updatedField.Data)
359 {
360 targetRow[i] = updatedField.Data;
361 }
362 }
363 }
364
365 // modified rows and certain special rows go in the target and updated msi databases
366 if (modifiedRow ||
367 ("Property" == table.Name &&
368 ("ProductCode" == (string)row[0] ||
369 "ProductLanguage" == (string)row[0] ||
370 "ProductVersion" == (string)row[0] ||
371 "UpgradeCode" == (string)row[0])))
372 {
373 Table targetTable = targetOutput.EnsureTable(table.Definition);
374 targetTable.Rows.Add(targetRow);
375
376 Table updatedTable = updatedOutput.EnsureTable(table.Definition);
377 updatedTable.Rows.Add(updatedRow);
378 }
379 }
380 }
381
382 foreach (BinderExtension extension in this.Extensions)
383 {
384 extension.Finish(this.Transform);
385 }
386
387 // Any errors encountered up to this point can cause errors during generation.
388 if (Messaging.Instance.EncounteredError)
389 {
390 return;
391 }
392
393 string transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath);
394 string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi"));
395 string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi"));
396
397 try
398 {
399 if (!String.IsNullOrEmpty(emptyFile))
400 {
401 using (FileStream fileStream = File.Create(emptyFile))
402 {
403 }
404 }
405
406 this.GenerateDatabase(targetOutput, targetDatabaseFile, false);
407 this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true);
408
409 // make sure the directory exists
410 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath));
411
412 // create the transform file
413 using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly))
414 {
415 using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly))
416 {
417 if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath))
418 {
419 updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF));
420 }
421 else
422 {
423 Messaging.Instance.OnMessage(WixErrors.NoDifferencesInTransform(this.Transform.SourceLineNumbers));
424 }
425 }
426 }
427 }
428 finally
429 {
430 if (!String.IsNullOrEmpty(emptyFile))
431 {
432 File.Delete(emptyFile);
433 }
434 }
435 }
436
437 private bool CompareFiles(string targetFile, string updatedFile)
438 {
439 bool? compared = null;
440 foreach (IBinderFileManager fileManager in this.FileManagers)
441 {
442 compared = fileManager.CompareFiles(targetFile, updatedFile);
443 if (compared.HasValue)
444 {
445 break;
446 }
447 }
448
449 if (!compared.HasValue)
450 {
451 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
452 }
453
454 return compared.Value;
455 }
456
457 private void GenerateDatabase(Output output, string outputPath, bool keepAddedColumns)
458 {
459 GenerateDatabaseCommand command = new GenerateDatabaseCommand();
460 command.Codepage = output.Codepage;
461 command.Extensions = this.Extensions;
462 command.FileManagers = this.FileManagers;
463 command.KeepAddedColumns = keepAddedColumns;
464 command.Output = output;
465 command.OutputPath = outputPath;
466 command.TableDefinitions = this.TableDefinitions;
467 command.TempFilesLocation = this.TempFilesLocation;
468 command.SuppressAddingValidationRows = true;
469 command.UseSubDirectory = true;
470 command.Execute();
471 }
472 }
473}
diff --git a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs b/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs
deleted file mode 100644
index eb02a983..00000000
--- a/src/WixToolset.Core/Bind/Bundles/AutomaticallySlipstreamPatchesCommand.cs
+++ /dev/null
@@ -1,112 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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 : ICommand
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/Bind/Bundles/BurnCommon.cs b/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs
deleted file mode 100644
index 8cb07791..00000000
--- a/src/WixToolset.Core/Bind/Bundles/BurnCommon.cs
+++ /dev/null
@@ -1,378 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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/Bind/Bundles/BurnReader.cs b/src/WixToolset.Core/Bind/Bundles/BurnReader.cs
deleted file mode 100644
index f6d7a197..00000000
--- a/src/WixToolset.Core/Bind/Bundles/BurnReader.cs
+++ /dev/null
@@ -1,210 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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.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 /// <summary>
53 /// Opens a Burn reader.
54 /// </summary>
55 /// <param name="fileExe">Path to file.</param>
56 /// <returns>Burn reader.</returns>
57 public static BurnReader Open(string fileExe)
58 {
59 BurnReader reader = new BurnReader(fileExe);
60
61 reader.binaryReader = new BinaryReader(File.Open(fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete));
62 if (!reader.Initialize(reader.binaryReader))
63 {
64 reader.invalidBundle = true;
65 }
66
67 return reader;
68 }
69
70 /// <summary>
71 /// Gets the UX container from the exe and extracts its contents to the output directory.
72 /// </summary>
73 /// <param name="outputDirectory">Directory to write extracted files to.</param>
74 /// <returns>True if successful, false otherwise</returns>
75 public bool ExtractUXContainer(string outputDirectory, string tempDirectory)
76 {
77 // No UX container to extract
78 if (this.UXAddress == 0 || this.UXSize == 0)
79 {
80 return false;
81 }
82
83 if (this.invalidBundle)
84 {
85 return false;
86 }
87
88 Directory.CreateDirectory(outputDirectory);
89 string tempCabPath = Path.Combine(tempDirectory, "ux.cab");
90 string manifestOriginalPath = Path.Combine(outputDirectory, "0");
91 string manifestPath = Path.Combine(outputDirectory, "manifest.xml");
92
93 this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin);
94 using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write))
95 {
96 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.UXSize);
97 }
98
99 using (WixExtractCab extract = new WixExtractCab())
100 {
101 extract.Extract(tempCabPath, outputDirectory);
102 }
103
104 Directory.CreateDirectory(Path.GetDirectoryName(manifestPath));
105 File.Delete(manifestPath);
106 File.Move(manifestOriginalPath, manifestPath);
107
108 XmlDocument document = new XmlDocument();
109 document.Load(manifestPath);
110 XmlNamespaceManager namespaceManager = new XmlNamespaceManager(document.NameTable);
111 namespaceManager.AddNamespace("burn", BurnCommon.BurnNamespace);
112 XmlNodeList uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager);
113 XmlNodeList payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager);
114
115 foreach (XmlNode uxPayload in uxPayloads)
116 {
117 XmlNode sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath");
118 XmlNode filePathNode = uxPayload.Attributes.GetNamedItem("FilePath");
119
120 string sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value);
121 string destinationPath = Path.Combine(outputDirectory, filePathNode.Value);
122
123 Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
124 File.Delete(destinationPath);
125 File.Move(sourcePath, destinationPath);
126 }
127
128 foreach (XmlNode payload in payloads)
129 {
130 XmlNode sourcePathNode = payload.Attributes.GetNamedItem("SourcePath");
131 XmlNode filePathNode = payload.Attributes.GetNamedItem("FilePath");
132 XmlNode packagingNode = payload.Attributes.GetNamedItem("Packaging");
133
134 string sourcePath = sourcePathNode.Value;
135 string destinationPath = filePathNode.Value;
136 string packaging = packagingNode.Value;
137
138 if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase))
139 {
140 this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath));
141 }
142 }
143
144 return true;
145 }
146
147 /// <summary>
148 /// Gets the attached container from the exe and extracts its contents to the output directory.
149 /// </summary>
150 /// <param name="outputDirectory">Directory to write extracted files to.</param>
151 /// <returns>True if successful, false otherwise</returns>
152 public bool ExtractAttachedContainer(string outputDirectory, string tempDirectory)
153 {
154 // No attached container to extract
155 if (this.AttachedContainerAddress == 0 || this.AttachedContainerSize == 0)
156 {
157 return false;
158 }
159
160 if (this.invalidBundle)
161 {
162 return false;
163 }
164
165 Directory.CreateDirectory(outputDirectory);
166 string tempCabPath = Path.Combine(tempDirectory, "attached.cab");
167
168 this.binaryReader.BaseStream.Seek(this.AttachedContainerAddress, SeekOrigin.Begin);
169 using (Stream tempCab = File.Open(tempCabPath, FileMode.Create, FileAccess.Write))
170 {
171 BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)this.AttachedContainerSize);
172 }
173
174 using (WixExtractCab extract = new WixExtractCab())
175 {
176 extract.Extract(tempCabPath, outputDirectory);
177 }
178
179 foreach (DictionaryEntry entry in this.attachedContainerPayloadNames)
180 {
181 string sourcePath = Path.Combine(outputDirectory, (string)entry.Key);
182 string destinationPath = Path.Combine(outputDirectory, (string)entry.Value);
183
184 Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
185 File.Delete(destinationPath);
186 File.Move(sourcePath, destinationPath);
187 }
188
189 return true;
190 }
191
192 /// <summary>
193 /// Dispose object.
194 /// </summary>
195 /// <param name="disposing">True when releasing managed objects.</param>
196 protected override void Dispose(bool disposing)
197 {
198 if (!this.disposed)
199 {
200 if (disposing && this.binaryReader != null)
201 {
202 this.binaryReader.Close();
203 this.binaryReader = null;
204 }
205
206 this.disposed = true;
207 }
208 }
209 }
210}
diff --git a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs b/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs
deleted file mode 100644
index bc0baf46..00000000
--- a/src/WixToolset.Core/Bind/Bundles/BurnWriter.cs
+++ /dev/null
@@ -1,239 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs
deleted file mode 100644
index 1040b394..00000000
--- a/src/WixToolset.Core/Bind/Bundles/CreateBootstrapperApplicationManifestCommand.cs
+++ /dev/null
@@ -1,241 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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 : ICommand
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/Bind/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs
deleted file mode 100644
index 7bc708a3..00000000
--- a/src/WixToolset.Core/Bind/Bundles/CreateBurnManifestCommand.cs
+++ /dev/null
@@ -1,686 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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 : ICommand
17 {
18 public IEnumerable<IBinderFileManager> FileManagers { 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 (IBinderFileManager fileManager in this.FileManagers)
675 {
676 resolved = fileManager.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/Bind/Bundles/CreateContainerCommand.cs b/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs
deleted file mode 100644
index 1bf987e3..00000000
--- a/src/WixToolset.Core/Bind/Bundles/CreateContainerCommand.cs
+++ /dev/null
@@ -1,68 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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.Cab;
11 using WixToolset.Data;
12 using WixToolset.Data.Rows;
13
14 /// <summary>
15 /// Creates cabinet files.
16 /// </summary>
17 internal class CreateContainerCommand : ICommand
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 (WixCreateCab 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/Bind/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs
deleted file mode 100644
index dc19e380..00000000
--- a/src/WixToolset.Core/Bind/Bundles/GetPackageFacadesCommand.cs
+++ /dev/null
@@ -1,62 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Bundles
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 internal class GetPackageFacadesCommand : ICommand
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/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs b/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs
deleted file mode 100644
index ac3a301d..00000000
--- a/src/WixToolset.Core/Bind/Bundles/OrderPackagesAndRollbackBoundariesCommand.cs
+++ /dev/null
@@ -1,145 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Bundles
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class OrderPackagesAndRollbackBoundariesCommand : ICommand
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/Bind/Bundles/PackageFacade.cs b/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs
deleted file mode 100644
index f7e6410f..00000000
--- a/src/WixToolset.Core/Bind/Bundles/PackageFacade.cs
+++ /dev/null
@@ -1,58 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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/Bind/Bundles/ProcessExePackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs
deleted file mode 100644
index a1e7c271..00000000
--- a/src/WixToolset.Core/Bind/Bundles/ProcessExePackageCommand.cs
+++ /dev/null
@@ -1,33 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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 : ICommand
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/Bind/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs
deleted file mode 100644
index f73776c0..00000000
--- a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs
+++ /dev/null
@@ -1,560 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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.Msi;
16 using WixToolset.Core.Native;
17 using Dtf = WixToolset.Dtf.WindowsInstaller;
18
19 /// <summary>
20 /// Initializes package state from the MSI contents.
21 /// </summary>
22 internal class ProcessMsiPackageCommand : ICommand
23 {
24 private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'";
25
26 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; }
27
28 public PackageFacade Facade { private get; set; }
29
30 public IBinderFileManager FileManager { private get; set; }
31
32 public Table MsiFeatureTable { private get; set; }
33
34 public Table MsiPropertyTable { private get; set; }
35
36 public Table PayloadTable { private get; set; }
37
38 public Table RelatedPackageTable { private get; set; }
39
40 /// <summary>
41 /// Processes the MSI packages to add properties and payloads from the MSI packages.
42 /// </summary>
43 public void Execute()
44 {
45 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload);
46
47 string sourcePath = packagePayload.FullFileName;
48 bool longNamesInImage = false;
49 bool compressed = false;
50 bool x64 = false;
51 try
52 {
53 // Read data out of the msi database...
54 using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false))
55 {
56 // 1 is the Word Count summary information stream bit that means
57 // the MSI uses short file names when set. We care about long file
58 // names so check when the bit is not set.
59 longNamesInImage = 0 == (sumInfo.WordCount & 1);
60
61 // 2 is the Word Count summary information stream bit that means
62 // files are compressed in the MSI by default when the bit is set.
63 compressed = 2 == (sumInfo.WordCount & 2);
64
65 x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64"));
66
67 // 8 is the Word Count summary information stream bit that means
68 // "Elevated privileges are not required to install this package."
69 // in MSI 4.5 and below, if this bit is 0, elevation is required.
70 this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No;
71 this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No;
72 }
73
74 using (Dtf.Database db = new Dtf.Database(sourcePath))
75 {
76 this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode");
77 this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode");
78 this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer");
79 this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture);
80 this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion");
81
82 if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion))
83 {
84 // not a proper .NET version (e.g., five fields); can we get a valid four-part version number?
85 string version = null;
86 string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.');
87 int count = versionParts.Length;
88 if (0 < count)
89 {
90 version = versionParts[0];
91 for (int i = 1; i < 4 && i < count; ++i)
92 {
93 version = String.Concat(version, ".", versionParts[i]);
94 }
95 }
96
97 if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version))
98 {
99 Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version));
100 this.Facade.MsiPackage.ProductVersion = version;
101 }
102 else
103 {
104 Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath));
105 }
106 }
107
108 if (String.IsNullOrEmpty(this.Facade.Package.CacheId))
109 {
110 this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion);
111 }
112
113 if (String.IsNullOrEmpty(this.Facade.Package.DisplayName))
114 {
115 this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName");
116 }
117
118 if (String.IsNullOrEmpty(this.Facade.Package.Description))
119 {
120 this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS");
121 }
122
123 ISet<string> payloadNames = this.GetPayloadTargetNames();
124
125 ISet<string> msiPropertyNames = this.GetMsiPropertyNames();
126
127 this.SetPerMachineAppropriately(db, sourcePath);
128
129 // Ensure the MSI package is appropriately marked visible or not.
130 this.SetPackageVisibility(db, msiPropertyNames);
131
132 // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance.
133 if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL"))
134 {
135 this.AddMsiProperty("MSIFASTINSTALL", "7");
136 }
137
138 this.CreateRelatedPackages(db);
139
140 // If feature selection is enabled, represent the Feature table in the manifest.
141 if (this.Facade.MsiPackage.EnableFeatureSelection)
142 {
143 this.CreateMsiFeatures(db);
144 }
145
146 // Add all external cabinets as package payloads.
147 this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames);
148
149 // Add all external files as package payloads and calculate the total install size as the rollup of
150 // File table's sizes.
151 this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames);
152
153 // Add all dependency providers from the MSI.
154 this.ImportDependencyProviders(db);
155 }
156 }
157 catch (Dtf.InstallerException e)
158 {
159 Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message));
160 }
161 }
162
163 private ISet<string> GetPayloadTargetNames()
164 {
165 IEnumerable<string> payloadNames = this.PayloadTable.RowsAs<WixBundlePayloadRow>()
166 .Where(r => r.Package == this.Facade.Package.WixChainItemId)
167 .Select(r => r.Name);
168
169 return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase);
170 }
171
172 private ISet<string> GetMsiPropertyNames()
173 {
174 IEnumerable<string> properties = this.MsiPropertyTable.RowsAs<WixBundleMsiPropertyRow>()
175 .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId)
176 .Select(r => r.Name);
177
178 return new HashSet<string>(properties, StringComparer.Ordinal);
179 }
180
181 private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath)
182 {
183 if (this.Facade.MsiPackage.ForcePerMachine)
184 {
185 if (YesNoDefaultType.No == this.Facade.Package.PerMachine)
186 {
187 Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath));
188 this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine.
189 }
190
191 // Force ALLUSERS=1 via the MSI command-line.
192 this.AddMsiProperty("ALLUSERS", "1");
193 }
194 else
195 {
196 string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS");
197
198 if (String.IsNullOrEmpty(allusers))
199 {
200 // Not forced per-machine and no ALLUSERS property, flip back to per-user.
201 if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine)
202 {
203 Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath));
204 this.Facade.Package.PerMachine = YesNoDefaultType.No;
205 }
206 }
207 else if (allusers.Equals("1", StringComparison.Ordinal))
208 {
209 if (YesNoDefaultType.No == this.Facade.Package.PerMachine)
210 {
211 Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath));
212 }
213 }
214 else if (allusers.Equals("2", StringComparison.Ordinal))
215 {
216 Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user"));
217 }
218 else
219 {
220 Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers));
221 }
222 }
223 }
224
225 private void SetPackageVisibility(Dtf.Database db, ISet<string> msiPropertyNames)
226 {
227 bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT");
228
229 if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility.
230 {
231 // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again.
232 if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT"))
233 {
234 this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1");
235 }
236 }
237 }
238
239 private void CreateRelatedPackages(Dtf.Database db)
240 {
241 // Represent the Upgrade table as related packages.
242 if (db.Tables.Contains("Upgrade"))
243 {
244 using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`"))
245 {
246 view.Execute();
247 while (true)
248 {
249 using (Dtf.Record record = view.Fetch())
250 {
251 if (null == record)
252 {
253 break;
254 }
255
256 WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers);
257 related.ChainPackageId = this.Facade.Package.WixChainItemId;
258 related.Id = record.GetString(1);
259 related.MinVersion = record.GetString(2);
260 related.MaxVersion = record.GetString(3);
261 related.Languages = record.GetString(4);
262
263 int attributes = record.GetInteger(5);
264 related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect;
265 related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive;
266 related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive;
267 related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0;
268 }
269 }
270 }
271 }
272 }
273
274 private void CreateMsiFeatures(Dtf.Database db)
275 {
276 if (db.Tables.Contains("Feature"))
277 {
278 using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?"))
279 using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?"))
280 {
281 using (Dtf.Record featureRecord = new Dtf.Record(1))
282 using (Dtf.Record componentRecord = new Dtf.Record(1))
283 {
284 using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`"))
285 {
286 allFeaturesView.Execute();
287
288 while (true)
289 {
290 using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch())
291 {
292 if (null == allFeaturesResultRecord)
293 {
294 break;
295 }
296
297 string featureName = allFeaturesResultRecord.GetString(1);
298
299 // Calculate the Feature size.
300 featureRecord.SetString(1, featureName);
301 featureView.Execute(featureRecord);
302
303 // Loop over all the components for the feature to calculate the size of the feature.
304 long size = 0;
305 while (true)
306 {
307 using (Dtf.Record componentResultRecord = featureView.Fetch())
308 {
309 if (null == componentResultRecord)
310 {
311 break;
312 }
313 string component = componentResultRecord.GetString(1);
314 componentRecord.SetString(1, component);
315 componentView.Execute(componentRecord);
316
317 while (true)
318 {
319 using (Dtf.Record fileResultRecord = componentView.Fetch())
320 {
321 if (null == fileResultRecord)
322 {
323 break;
324 }
325
326 string fileSize = fileResultRecord.GetString(1);
327 size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat);
328 }
329 }
330 }
331 }
332
333 WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers);
334 feature.ChainPackageId = this.Facade.Package.WixChainItemId;
335 feature.Name = featureName;
336 feature.Parent = allFeaturesResultRecord.GetString(2);
337 feature.Title = allFeaturesResultRecord.GetString(3);
338 feature.Description = allFeaturesResultRecord.GetString(4);
339 feature.Display = allFeaturesResultRecord.GetInteger(5);
340 feature.Level = allFeaturesResultRecord.GetInteger(6);
341 feature.Directory = allFeaturesResultRecord.GetString(7);
342 feature.Attributes = allFeaturesResultRecord.GetInteger(8);
343 feature.Size = size;
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351
352 private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet<string> payloadNames)
353 {
354 if (db.Tables.Contains("Media"))
355 {
356 foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`"))
357 {
358 if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal))
359 {
360 // If we didn't find the Payload as an existing child of the package, we need to
361 // add it. We expect the file to exist on-disk in the same relative location as
362 // the MSI expects to find it...
363 string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet);
364
365 if (!payloadNames.Contains(cabinetName))
366 {
367 string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet);
368 string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal);
369
370 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers);
371 payload.Id = generatedId;
372 payload.Name = cabinetName;
373 payload.SourceFile = payloadSourceFile;
374 payload.Compressed = packagePayload.Compressed;
375 payload.UnresolvedSourceFile = cabinetName;
376 payload.Package = packagePayload.Package;
377 payload.Container = packagePayload.Container;
378 payload.ContentFile = true;
379 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation;
380 payload.Packaging = packagePayload.Packaging;
381 payload.ParentPackagePayload = packagePayload.Id;
382 }
383 }
384 }
385 }
386 }
387
388 private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames)
389 {
390 long size = 0;
391
392 if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File"))
393 {
394 Hashtable directories = new Hashtable();
395
396 // Load up the directory hash table so we will be able to resolve source paths
397 // for files in the MSI database.
398 using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
399 {
400 view.Execute();
401 while (true)
402 {
403 using (Dtf.Record record = view.Fetch())
404 {
405 if (null == record)
406 {
407 break;
408 }
409
410 string sourceName = Installer.GetName(record.GetString(3), true, longNamesInImage);
411 directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName));
412 }
413 }
414 }
415
416 // Resolve the source paths to external files and add each file size to the total
417 // install size of the package.
418 using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`"))
419 {
420 view.Execute();
421 while (true)
422 {
423 using (Dtf.Record record = view.Fetch())
424 {
425 if (null == record)
426 {
427 break;
428 }
429
430 // Skip adding the loose files as payloads if it was suppressed.
431 if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration)
432 {
433 // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not
434 // explicitly marked compressed then this is an external file.
435 if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) ||
436 (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed)))
437 {
438 string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage);
439 string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath);
440
441 if (!payloadNames.Contains(name))
442 {
443 string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2));
444 string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal);
445
446 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers);
447 payload.Id = generatedId;
448 payload.Name = name;
449 payload.SourceFile = payloadSourceFile;
450 payload.Compressed = packagePayload.Compressed;
451 payload.UnresolvedSourceFile = name;
452 payload.Package = packagePayload.Package;
453 payload.Container = packagePayload.Container;
454 payload.ContentFile = true;
455 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation;
456 payload.Packaging = packagePayload.Packaging;
457 payload.ParentPackagePayload = packagePayload.Id;
458 }
459 }
460 }
461
462 size += record.GetInteger(5);
463 }
464 }
465 }
466 }
467
468 return size;
469 }
470
471 private void AddMsiProperty(string name, string value)
472 {
473 WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers);
474 row.ChainPackageId = this.Facade.Package.WixChainItemId;
475 row.Name = name;
476 row.Value = value;
477 }
478
479 private void ImportDependencyProviders(Dtf.Database db)
480 {
481 if (db.Tables.Contains("WixDependencyProvider"))
482 {
483 string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`";
484
485 using (Dtf.View view = db.OpenView(query))
486 {
487 view.Execute();
488 while (true)
489 {
490 using (Dtf.Record record = view.Fetch())
491 {
492 if (null == record)
493 {
494 break;
495 }
496
497 // Import the provider key and attributes.
498 string providerKey = record.GetString(1);
499 string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion;
500 string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName;
501 int attributes = record.GetInteger(4);
502
503 ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes);
504 dependency.Imported = true;
505
506 this.Facade.Provides.Add(dependency);
507 }
508 }
509 }
510 }
511 }
512
513 /// <summary>
514 /// Queries a Windows Installer database for a Property value.
515 /// </summary>
516 /// <param name="db">Database to query.</param>
517 /// <param name="property">Property to examine.</param>
518 /// <returns>String value for result or null if query doesn't match a single result.</returns>
519 private static string GetProperty(Dtf.Database db, string property)
520 {
521 try
522 {
523 return db.ExecuteScalar(PropertyQuery(property)).ToString();
524 }
525 catch (Dtf.InstallerException)
526 {
527 }
528
529 return null;
530 }
531
532 /// <summary>
533 /// Queries a Windows Installer database to determine if one or more rows exist in the Property table.
534 /// </summary>
535 /// <param name="db">Database to query.</param>
536 /// <param name="property">Property to examine.</param>
537 /// <returns>True if query matches at least one result.</returns>
538 private static bool HasProperty(Dtf.Database db, string property)
539 {
540 try
541 {
542 return 0 < db.ExecuteQuery(PropertyQuery(property)).Count;
543 }
544 catch (Dtf.InstallerException)
545 {
546 }
547
548 return false;
549 }
550
551 private static string PropertyQuery(string property)
552 {
553 // quick sanity check that we'll be creating a valid query...
554 // TODO: Are there any other special characters we should be looking for?
555 Debug.Assert(!property.Contains("'"));
556
557 return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property);
558 }
559 }
560}
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs
deleted file mode 100644
index 24063221..00000000
--- a/src/WixToolset.Core/Bind/Bundles/ProcessMspPackageCommand.cs
+++ /dev/null
@@ -1,189 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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 : ICommand
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/Bind/Bundles/ProcessMsuPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs
deleted file mode 100644
index ba59f5f5..00000000
--- a/src/WixToolset.Core/Bind/Bundles/ProcessMsuPackageCommand.cs
+++ /dev/null
@@ -1,30 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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 : ICommand
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/Bind/Bundles/ProcessPayloadsCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs
deleted file mode 100644
index a83a7a4a..00000000
--- a/src/WixToolset.Core/Bind/Bundles/ProcessPayloadsCommand.cs
+++ /dev/null
@@ -1,159 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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.Data;
13 using WixToolset.Data.Rows;
14
15 internal class ProcessPayloadsCommand : ICommand
16 {
17 private static readonly Version EmptyVersion = new Version(0, 0, 0, 0);
18
19 public IEnumerable<WixBundlePayloadRow> Payloads { private get; set; }
20
21 public PackagingType DefaultPackaging { private get; set; }
22
23 public string LayoutDirectory { private get; set; }
24
25 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
26
27 public void Execute()
28 {
29 List<FileTransfer> fileTransfers = new List<FileTransfer>();
30
31 foreach (WixBundlePayloadRow payload in this.Payloads)
32 {
33 string normalizedPath = payload.Name.Replace('\\', '/');
34 if (normalizedPath.StartsWith("../", StringComparison.Ordinal) || normalizedPath.Contains("/../"))
35 {
36 Messaging.Instance.OnMessage(WixErrors.PayloadMustBeRelativeToCache(payload.SourceLineNumbers, "Payload", "Name", payload.Name));
37 }
38
39 // Embedded files (aka: files from binary .wixlibs) are not content files (because they are hidden
40 // in the .wixlib).
41 ObjectField field = (ObjectField)payload.Fields[2];
42 payload.ContentFile = !field.EmbeddedFileIndex.HasValue;
43
44 this.UpdatePayloadPackagingType(payload);
45
46 if (String.IsNullOrEmpty(payload.SourceFile))
47 {
48 // Remote payloads obviously cannot be embedded.
49 Debug.Assert(PackagingType.Embedded != payload.Packaging);
50 }
51 else // not a remote payload so we have a lot more to update.
52 {
53 this.UpdatePayloadFileInformation(payload);
54
55 this.UpdatePayloadVersionInformation(payload);
56
57 // External payloads need to be transfered.
58 if (PackagingType.External == payload.Packaging)
59 {
60 FileTransfer transfer;
61 if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(this.LayoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer))
62 {
63 fileTransfers.Add(transfer);
64 }
65 }
66 }
67 }
68
69 this.FileTransfers = fileTransfers;
70 }
71
72 private void UpdatePayloadPackagingType(WixBundlePayloadRow payload)
73 {
74 if (PackagingType.Unknown == payload.Packaging)
75 {
76 if (YesNoDefaultType.Yes == payload.Compressed)
77 {
78 payload.Packaging = PackagingType.Embedded;
79 }
80 else if (YesNoDefaultType.No == payload.Compressed)
81 {
82 payload.Packaging = PackagingType.External;
83 }
84 else
85 {
86 payload.Packaging = this.DefaultPackaging;
87 }
88 }
89
90 // Embedded payloads that are not assigned a container already are placed in the default attached
91 // container.
92 if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.Container))
93 {
94 payload.Container = Compiler.BurnDefaultAttachedContainerId;
95 }
96 }
97
98 private void UpdatePayloadFileInformation(WixBundlePayloadRow payload)
99 {
100 FileInfo fileInfo = new FileInfo(payload.SourceFile);
101
102 if (null != fileInfo)
103 {
104 payload.FileSize = (int)fileInfo.Length;
105
106 payload.Hash = Common.GetFileHash(fileInfo.FullName);
107
108 // Try to get the certificate if the payload is a signed file and we're not suppressing signature validation.
109 if (payload.EnableSignatureValidation)
110 {
111 X509Certificate2 certificate = null;
112 try
113 {
114 certificate = new X509Certificate2(fileInfo.FullName);
115 }
116 catch (CryptographicException) // we don't care about non-signed files.
117 {
118 }
119
120 // If there is a certificate, remember its hashed public key identifier and thumbprint.
121 if (null != certificate)
122 {
123 byte[] publicKeyIdentifierHash = new byte[128];
124 uint publicKeyIdentifierHashSize = (uint)publicKeyIdentifierHash.Length;
125
126 WixToolset.Core.Native.NativeMethods.HashPublicKeyInfo(certificate.Handle, publicKeyIdentifierHash, ref publicKeyIdentifierHashSize);
127 StringBuilder sb = new StringBuilder(((int)publicKeyIdentifierHashSize + 1) * 2);
128 for (int i = 0; i < publicKeyIdentifierHashSize; ++i)
129 {
130 sb.AppendFormat("{0:X2}", publicKeyIdentifierHash[i]);
131 }
132
133 payload.PublicKey = sb.ToString();
134 payload.Thumbprint = certificate.Thumbprint;
135 }
136 }
137 }
138 }
139
140 private void UpdatePayloadVersionInformation(WixBundlePayloadRow payload)
141 {
142 FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(payload.SourceFile);
143
144 if (null != versionInfo)
145 {
146 // Use the fixed version info block for the file since the resource text may not be a dotted quad.
147 Version version = new Version(versionInfo.ProductMajorPart, versionInfo.ProductMinorPart, versionInfo.ProductBuildPart, versionInfo.ProductPrivatePart);
148
149 if (ProcessPayloadsCommand.EmptyVersion != version)
150 {
151 payload.Version = version.ToString();
152 }
153
154 payload.Description = versionInfo.FileDescription;
155 payload.DisplayName = versionInfo.ProductName;
156 }
157 }
158 }
159}
diff --git a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs b/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs
deleted file mode 100644
index 9c614c26..00000000
--- a/src/WixToolset.Core/Bind/Bundles/VerifyPayloadsWithCatalogCommand.cs
+++ /dev/null
@@ -1,148 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.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 : ICommand
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/Bind/Databases/AssignMediaCommand.cs b/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs
deleted file mode 100644
index 5e2650e9..00000000
--- a/src/WixToolset.Core/Bind/Databases/AssignMediaCommand.cs
+++ /dev/null
@@ -1,314 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 /// <summary>
13 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
14 /// </summary>
15 public class AssignMediaCommand : ICommand
16 {
17 public AssignMediaCommand()
18 {
19 this.CabinetNameTemplate = "Cab{0}.cab";
20 }
21
22 public Output Output { private get; set; }
23
24 public bool FilesCompressed { private get; set; }
25
26 public string CabinetNameTemplate { private get; set; }
27
28 public IEnumerable<FileFacade> FileFacades { private get; set; }
29
30 public TableDefinitionCollection TableDefinitions { private get; set; }
31
32 /// <summary>
33 /// Gets cabinets with their file rows.
34 /// </summary>
35 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
36
37 /// <summary>
38 /// Get media rows.
39 /// </summary>
40 public RowDictionary<MediaRow> MediaRows { get; private set; }
41
42 /// <summary>
43 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
44 /// This contains all the files when Package element is marked with compression=no
45 /// </summary>
46 public IEnumerable<FileFacade> UncompressedFileFacades { get; private set; }
47
48 public void Execute()
49 {
50 Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia = new Dictionary<MediaRow, List<FileFacade>>();
51
52 RowDictionary<MediaRow> mediaRows = new RowDictionary<MediaRow>();
53
54 List<FileFacade> uncompressedFiles = new List<FileFacade>();
55
56 MediaRow mergeModuleMediaRow = null;
57 Table mediaTable = this.Output.Tables["Media"];
58 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
59
60 // If both tables are authored, it is an error.
61 if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1))
62 {
63 throw new WixException(WixErrors.MediaTableCollision(null));
64 }
65
66 // When building merge module, all the files go to "#MergeModule.CABinet".
67 if (OutputType.Module == this.Output.Type)
68 {
69 Table mergeModuleMediaTable = new Table(null, this.TableDefinitions["Media"]);
70 mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null);
71 mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet";
72
73 filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>());
74 }
75
76 if (OutputType.Module == this.Output.Type || null == mediaTemplateTable)
77 {
78 this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
79 }
80 else
81 {
82 this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
83 }
84
85 this.FileFacadesByCabinetMedia = new Dictionary<MediaRow, IEnumerable<FileFacade>>();
86
87 foreach (var mediaRowWithFiles in filesByCabinetMedia)
88 {
89 this.FileFacadesByCabinetMedia.Add(mediaRowWithFiles.Key, mediaRowWithFiles.Value);
90 }
91
92 this.MediaRows = mediaRows;
93
94 this.UncompressedFileFacades = uncompressedFiles;
95 }
96
97 /// <summary>
98 /// Assign files to cabinets based on MediaTemplate authoring.
99 /// </summary>
100 /// <param name="fileFacades">FileRowCollection</param>
101 private void AutoAssignFiles(Table mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
102 {
103 const int MaxCabIndex = 999;
104
105 ulong currentPreCabSize = 0;
106 ulong maxPreCabSizeInBytes;
107 int maxPreCabSizeInMB = 0;
108 int currentCabIndex = 0;
109
110 MediaRow currentMediaRow = null;
111
112 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
113
114 // Auto assign files to cabinets based on maximum uncompressed media size
115 mediaTable.Rows.Clear();
116 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
117
118 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
119 {
120 this.CabinetNameTemplate = mediaTemplateRow.CabinetTemplate;
121 }
122
123 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
124
125 try
126 {
127 // Override authored mums value if environment variable is authored.
128 if (!String.IsNullOrEmpty(mumsString))
129 {
130 maxPreCabSizeInMB = Int32.Parse(mumsString);
131 }
132 else
133 {
134 maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
135 }
136
137 maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024;
138 }
139 catch (FormatException)
140 {
141 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
142 }
143 catch (OverflowException)
144 {
145 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB));
146 }
147
148 foreach (FileFacade facade in this.FileFacades)
149 {
150 // When building a product, if the current file is not to be compressed or if
151 // the package set not to be compressed, don't cab it.
152 if (OutputType.Product == this.Output.Type &&
153 (YesNoType.No == facade.File.Compressed ||
154 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
155 {
156 uncompressedFiles.Add(facade);
157 continue;
158 }
159
160 if (currentCabIndex == MaxCabIndex)
161 {
162 // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore.
163 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
164 facade.WixFile.DiskId = currentCabIndex;
165 cabinetFiles.Add(facade);
166 continue;
167 }
168
169 // Update current cab size.
170 currentPreCabSize += (ulong)facade.File.FileSize;
171
172 if (currentPreCabSize > maxPreCabSizeInBytes)
173 {
174 // Overflow due to current file
175 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
176 mediaRows.Add(currentMediaRow);
177 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
178
179 List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow];
180 facade.WixFile.DiskId = currentCabIndex;
181 cabinetFileRows.Add(facade);
182 // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize
183 currentPreCabSize = (ulong)facade.File.FileSize;
184 }
185 else
186 {
187 // File fits in the current cab.
188 if (currentMediaRow == null)
189 {
190 // Create new cab and MediaRow
191 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex);
192 mediaRows.Add(currentMediaRow);
193 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
194 }
195
196 // Associate current file with current cab.
197 List<FileFacade> cabinetFiles = filesByCabinetMedia[currentMediaRow];
198 facade.WixFile.DiskId = currentCabIndex;
199 cabinetFiles.Add(facade);
200 }
201 }
202
203 // If there are uncompressed files and no MediaRow, create a default one.
204 if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0)
205 {
206 MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null);
207 defaultMediaRow.DiskId = 1;
208 mediaRows.Add(defaultMediaRow);
209 }
210 }
211
212 /// <summary>
213 /// Assign files to cabinets based on Media authoring.
214 /// </summary>
215 /// <param name="mediaTable"></param>
216 /// <param name="mergeModuleMediaRow"></param>
217 /// <param name="fileFacades"></param>
218 private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, IEnumerable<FileFacade> fileFacades, Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia, RowDictionary<MediaRow> mediaRows, List<FileFacade> uncompressedFiles)
219 {
220 if (OutputType.Module != this.Output.Type)
221 {
222 if (null != mediaTable)
223 {
224 Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase);
225 foreach (MediaRow mediaRow in mediaTable.Rows)
226 {
227 // If the Media row has a cabinet, make sure it is unique across all Media rows.
228 if (!String.IsNullOrEmpty(mediaRow.Cabinet))
229 {
230 MediaRow existingRow;
231 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow))
232 {
233 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet));
234 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
235 }
236 else
237 {
238 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
239 }
240 }
241
242 mediaRows.Add(mediaRow);
243 }
244 }
245
246 foreach (MediaRow mediaRow in mediaRows.Values)
247 {
248 if (null != mediaRow.Cabinet)
249 {
250 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
251 }
252 }
253 }
254
255 foreach (FileFacade facade in fileFacades)
256 {
257 if (OutputType.Module == this.Output.Type)
258 {
259 filesByCabinetMedia[mergeModuleMediaRow].Add(facade);
260 }
261 else
262 {
263 MediaRow mediaRow;
264 if (!mediaRows.TryGetValue(facade.WixFile.DiskId.ToString(CultureInfo.InvariantCulture), out mediaRow))
265 {
266 Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId));
267 continue;
268 }
269
270 // When building a product, if the current file is not to be compressed or if
271 // the package set not to be compressed, don't cab it.
272 if (OutputType.Product == this.Output.Type &&
273 (YesNoType.No == facade.File.Compressed ||
274 (YesNoType.NotSet == facade.File.Compressed && !this.FilesCompressed)))
275 {
276 uncompressedFiles.Add(facade);
277 }
278 else // file is marked compressed.
279 {
280 List<FileFacade> cabinetFiles;
281 if (filesByCabinetMedia.TryGetValue(mediaRow, out cabinetFiles))
282 {
283 cabinetFiles.Add(facade);
284 }
285 else
286 {
287 Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId));
288 }
289 }
290 }
291 }
292 }
293
294 /// <summary>
295 /// Adds a row to the media table with cab name template filled in.
296 /// </summary>
297 /// <param name="mediaTable"></param>
298 /// <param name="cabIndex"></param>
299 /// <returns></returns>
300 private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex)
301 {
302 MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
303 currentMediaRow.DiskId = cabIndex;
304 currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex);
305
306 Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]);
307 WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers);
308 row.DiskId = cabIndex;
309 row.CompressionLevel = mediaTemplateRow.CompressionLevel;
310
311 return currentMediaRow;
312 }
313 }
314}
diff --git a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs b/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs
deleted file mode 100644
index 95bd4cf0..00000000
--- a/src/WixToolset.Core/Bind/Databases/BindSummaryInfoCommand.cs
+++ /dev/null
@@ -1,135 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Globalization;
7 using WixToolset.Data;
8
9 /// <summary>
10 /// Binds the summary information table of a database.
11 /// </summary>
12 internal class BindSummaryInfoCommand : ICommand
13 {
14 /// <summary>
15 /// The output to bind.
16 /// </summary>
17 public Output Output { private get; set; }
18
19 /// <summary>
20 /// Returns a flag indicating if files are compressed by default.
21 /// </summary>
22 public bool Compressed { get; private set; }
23
24 /// <summary>
25 /// Returns a flag indicating if uncompressed files use long filenames.
26 /// </summary>
27 public bool LongNames { get; private set; }
28
29 public int InstallerVersion { get; private set; }
30
31 /// <summary>
32 /// Modularization guid, or null if the output is not a module.
33 /// </summary>
34 public string ModularizationGuid { get; private set; }
35
36 public void Execute()
37 {
38 this.Compressed = false;
39 this.LongNames = false;
40 this.InstallerVersion = 0;
41 this.ModularizationGuid = null;
42
43 Table summaryInformationTable = this.Output.Tables["_SummaryInformation"];
44
45 if (null != summaryInformationTable)
46 {
47 bool foundCreateDataTime = false;
48 bool foundLastSaveDataTime = false;
49 bool foundCreatingApplication = false;
50 string now = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture);
51
52 foreach (Row summaryInformationRow in summaryInformationTable.Rows)
53 {
54 switch (summaryInformationRow.FieldAsInteger(0))
55 {
56 case 1: // PID_CODEPAGE
57 // make sure the code page is an int and not a web name or null
58 string codepage = summaryInformationRow.FieldAsString(1);
59
60 if (null == codepage)
61 {
62 codepage = "0";
63 }
64 else
65 {
66 summaryInformationRow[1] = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture);
67 }
68 break;
69 case 9: // PID_REVNUMBER
70 string packageCode = (string)summaryInformationRow[1];
71
72 if (OutputType.Module == this.Output.Type)
73 {
74 this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_');
75 }
76 else if ("*" == packageCode)
77 {
78 // set the revision number (package/patch code) if it should be automatically generated
79 summaryInformationRow[1] = Common.GenerateGuid();
80 }
81 break;
82 case 12: // PID_CREATE_DTM
83 foundCreateDataTime = true;
84 break;
85 case 13: // PID_LASTSAVE_DTM
86 foundLastSaveDataTime = true;
87 break;
88 case 14:
89 this.InstallerVersion = summaryInformationRow.FieldAsInteger(1);
90 break;
91 case 15: // PID_WORDCOUNT
92 if (OutputType.Patch == this.Output.Type)
93 {
94 this.LongNames = true;
95 this.Compressed = true;
96 }
97 else
98 {
99 this.LongNames = (0 == (summaryInformationRow.FieldAsInteger(1) & 1));
100 this.Compressed = (2 == (summaryInformationRow.FieldAsInteger(1) & 2));
101 }
102 break;
103 case 18: // PID_APPNAME
104 foundCreatingApplication = true;
105 break;
106 }
107 }
108
109 // add a summary information row for the create time/date property if its not already set
110 if (!foundCreateDataTime)
111 {
112 Row createTimeDateRow = summaryInformationTable.CreateRow(null);
113 createTimeDateRow[0] = 12;
114 createTimeDateRow[1] = now;
115 }
116
117 // add a summary information row for the last save time/date property if its not already set
118 if (!foundLastSaveDataTime)
119 {
120 Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null);
121 lastSaveTimeDateRow[0] = 13;
122 lastSaveTimeDateRow[1] = now;
123 }
124
125 // add a summary information row for the creating application property if its not already set
126 if (!foundCreatingApplication)
127 {
128 Row creatingApplicationRow = summaryInformationTable.CreateRow(null);
129 creatingApplicationRow[0] = 18;
130 creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString());
131 }
132 }
133 }
134 }
135}
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs b/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs
deleted file mode 100644
index 2de6ec25..00000000
--- a/src/WixToolset.Core/Bind/Databases/CabinetBuilder.cs
+++ /dev/null
@@ -1,176 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using WixToolset.Cab;
11 using WixToolset.Data;
12 using WixToolset.Data.Rows;
13
14 /// <summary>
15 /// Builds cabinets using multiple threads. This implements a thread pool that generates cabinets with multiple
16 /// threads. Unlike System.Threading.ThreadPool, it waits until all threads are finished.
17 /// </summary>
18 internal sealed class CabinetBuilder
19 {
20 private Queue cabinetWorkItems;
21 private object lockObject;
22 private int threadCount;
23
24 // Address of Binder's callback function for Cabinet Splitting
25 private IntPtr newCabNamesCallBackAddress;
26
27 public int MaximumCabinetSizeForLargeFileSplitting { get; set; }
28 public int MaximumUncompressedMediaSize { get; set; }
29
30 /// <summary>
31 /// Instantiate a new CabinetBuilder.
32 /// </summary>
33 /// <param name="threadCount">number of threads to use</param>
34 /// <param name="newCabNamesCallBackAddress">Address of Binder's callback function for Cabinet Splitting</param>
35 public CabinetBuilder(int threadCount, IntPtr newCabNamesCallBackAddress)
36 {
37 if (0 >= threadCount)
38 {
39 throw new ArgumentOutOfRangeException("threadCount");
40 }
41
42 this.cabinetWorkItems = new Queue();
43 this.lockObject = new object();
44
45 this.threadCount = threadCount;
46
47 // Set Address of Binder's callback function for Cabinet Splitting
48 this.newCabNamesCallBackAddress = newCabNamesCallBackAddress;
49 }
50
51 /// <summary>
52 /// Enqueues a CabinetWorkItem to the queue.
53 /// </summary>
54 /// <param name="cabinetWorkItem">cabinet work item</param>
55 public void Enqueue(CabinetWorkItem cabinetWorkItem)
56 {
57 this.cabinetWorkItems.Enqueue(cabinetWorkItem);
58 }
59
60 /// <summary>
61 /// Create the queued cabinets.
62 /// </summary>
63 /// <returns>error message number (zero if no error)</returns>
64 public void CreateQueuedCabinets()
65 {
66 // don't create more threads than the number of cabinets to build
67 if (this.cabinetWorkItems.Count < this.threadCount)
68 {
69 this.threadCount = this.cabinetWorkItems.Count;
70 }
71
72 if (0 < this.threadCount)
73 {
74 Thread[] threads = new Thread[this.threadCount];
75
76 for (int i = 0; i < threads.Length; i++)
77 {
78 threads[i] = new Thread(new ThreadStart(this.ProcessWorkItems));
79 threads[i].Start();
80 }
81
82 // wait for all threads to finish
83 foreach (Thread thread in threads)
84 {
85 thread.Join();
86 }
87 }
88 }
89
90 /// <summary>
91 /// This function gets called by multiple threads to do actual work.
92 /// It takes one work item at a time and calls this.CreateCabinet().
93 /// It does not return until cabinetWorkItems queue is empty
94 /// </summary>
95 private void ProcessWorkItems()
96 {
97 try
98 {
99 while (true)
100 {
101 CabinetWorkItem cabinetWorkItem;
102
103 lock (this.cabinetWorkItems)
104 {
105 // check if there are any more cabinets to create
106 if (0 == this.cabinetWorkItems.Count)
107 {
108 break;
109 }
110
111 cabinetWorkItem = (CabinetWorkItem)this.cabinetWorkItems.Dequeue();
112 }
113
114 // create a cabinet
115 this.CreateCabinet(cabinetWorkItem);
116 }
117 }
118 catch (WixException we)
119 {
120 Messaging.Instance.OnMessage(we.Error);
121 }
122 catch (Exception e)
123 {
124 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(e.Message, e.GetType().ToString(), e.StackTrace));
125 }
126 }
127
128 /// <summary>
129 /// Creates a cabinet using the wixcab.dll interop layer.
130 /// </summary>
131 /// <param name="cabinetWorkItem">CabinetWorkItem containing information about the cabinet to create.</param>
132 private void CreateCabinet(CabinetWorkItem cabinetWorkItem)
133 {
134 Messaging.Instance.OnMessage(WixVerboses.CreateCabinet(cabinetWorkItem.CabinetFile));
135
136 int maxCabinetSize = 0; // The value of 0 corresponds to default of 2GB which means no cabinet splitting
137 ulong maxPreCompressedSizeInBytes = 0;
138
139 if (MaximumCabinetSizeForLargeFileSplitting != 0)
140 {
141 // User Specified Max Cab Size for File Splitting, So Check if this cabinet has a single file larger than MaximumUncompressedFileSize
142 // If a file is larger than MaximumUncompressedFileSize, then the cabinet containing it will have only this file
143 if (1 == cabinetWorkItem.FileFacades.Count())
144 {
145 // Cabinet has Single File, Check if this is Large File than needs Splitting into Multiple cabs
146 // Get the Value for Max Uncompressed Media Size
147 maxPreCompressedSizeInBytes = (ulong)MaximumUncompressedMediaSize * 1024 * 1024;
148
149 foreach (FileFacade facade in cabinetWorkItem.FileFacades) // No other easy way than looping to get the only row
150 {
151 if ((ulong)facade.File.FileSize >= maxPreCompressedSizeInBytes)
152 {
153 // If file is larger than MaximumUncompressedFileSize set Maximum Cabinet Size for Cabinet Splitting
154 maxCabinetSize = MaximumCabinetSizeForLargeFileSplitting;
155 }
156 }
157 }
158 }
159
160 // create the cabinet file
161 string cabinetFileName = Path.GetFileName(cabinetWorkItem.CabinetFile);
162 string cabinetDirectory = Path.GetDirectoryName(cabinetWorkItem.CabinetFile);
163
164 using (WixCreateCab cab = new WixCreateCab(cabinetFileName, cabinetDirectory, cabinetWorkItem.FileFacades.Count(), maxCabinetSize, cabinetWorkItem.MaxThreshold, cabinetWorkItem.CompressionLevel))
165 {
166 foreach (FileFacade facade in cabinetWorkItem.FileFacades)
167 {
168 cab.AddFile(facade);
169 }
170
171 cab.Complete(newCabNamesCallBackAddress);
172 }
173 }
174 }
175}
176
diff --git a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs b/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs
deleted file mode 100644
index 20241bc9..00000000
--- a/src/WixToolset.Core/Bind/Databases/CabinetWorkItem.cs
+++ /dev/null
@@ -1,78 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System.Collections.Generic;
6 using WixToolset.Data;
7 using WixToolset.Data.Rows;
8
9 /// <summary>
10 /// A cabinet builder work item.
11 /// </summary>
12 internal sealed class CabinetWorkItem
13 {
14 private string cabinetFile;
15 private CompressionLevel compressionLevel;
16 //private BinderFileManager binderFileManager;
17 private int maxThreshold;
18
19 /// <summary>
20 /// Instantiate a new CabinetWorkItem.
21 /// </summary>
22 /// <param name="fileFacades">The collection of files in this cabinet.</param>
23 /// <param name="cabinetFile">The cabinet file.</param>
24 /// <param name="maxThreshold">Maximum threshold for each cabinet.</param>
25 /// <param name="compressionLevel">The compression level of the cabinet.</param>
26 /// <param name="binderFileManager">The binder file manager.</param>
27 public CabinetWorkItem(IEnumerable<FileFacade> fileFacades, string cabinetFile, int maxThreshold, CompressionLevel compressionLevel /*, BinderFileManager binderFileManager*/)
28 {
29 this.cabinetFile = cabinetFile;
30 this.compressionLevel = compressionLevel;
31 this.FileFacades = fileFacades;
32 //this.binderFileManager = binderFileManager;
33 this.maxThreshold = maxThreshold;
34 }
35
36 /// <summary>
37 /// Gets the cabinet file.
38 /// </summary>
39 /// <value>The cabinet file.</value>
40 public string CabinetFile
41 {
42 get { return this.cabinetFile; }
43 }
44
45 /// <summary>
46 /// Gets the compression level of the cabinet.
47 /// </summary>
48 /// <value>The compression level of the cabinet.</value>
49 public CompressionLevel CompressionLevel
50 {
51 get { return this.compressionLevel; }
52 }
53
54 /// <summary>
55 /// Gets the collection of files in this cabinet.
56 /// </summary>
57 /// <value>The collection of files in this cabinet.</value>
58 public IEnumerable<FileFacade> FileFacades { get; private set; }
59
60 /// <summary>
61 /// Gets the binder file manager.
62 /// </summary>
63 /// <value>The binder file manager.</value>
64 //public BinderFileManager BinderFileManager
65 //{
66 // get { return this.binderFileManager; }
67 //}
68
69 /// <summary>
70 /// Gets the max threshold.
71 /// </summary>
72 /// <value>The maximum threshold for a folder in a cabinet.</value>
73 public int MaxThreshold
74 {
75 get { return this.maxThreshold; }
76 }
77 }
78}
diff --git a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs b/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs
deleted file mode 100644
index 7cb18e0f..00000000
--- a/src/WixToolset.Core/Bind/Databases/ConfigurationCallback.cs
+++ /dev/null
@@ -1,91 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using WixToolset.MergeMod;
9
10 /// <summary>
11 /// Callback object for configurable merge modules.
12 /// </summary>
13 internal sealed class ConfigurationCallback : IMsmConfigureModule
14 {
15 private const int SOk = 0x0;
16 private const int SFalse = 0x1;
17 private Hashtable configurationData;
18
19 /// <summary>
20 /// Creates a ConfigurationCallback object.
21 /// </summary>
22 /// <param name="configData">String to break up into name/value pairs.</param>
23 public ConfigurationCallback(string configData)
24 {
25 if (String.IsNullOrEmpty(configData))
26 {
27 throw new ArgumentNullException("configData");
28 }
29
30 string[] pairs = configData.Split(',');
31 this.configurationData = new Hashtable(pairs.Length);
32 for (int i = 0; i < pairs.Length; ++i)
33 {
34 string[] nameVal = pairs[i].Split('=');
35 string name = nameVal[0];
36 string value = nameVal[1];
37
38 name = name.Replace("%2C", ",");
39 name = name.Replace("%3D", "=");
40 name = name.Replace("%25", "%");
41
42 value = value.Replace("%2C", ",");
43 value = value.Replace("%3D", "=");
44 value = value.Replace("%25", "%");
45
46 this.configurationData[name] = value;
47 }
48 }
49
50 /// <summary>
51 /// Returns text data based on name.
52 /// </summary>
53 /// <param name="name">Name of value to return.</param>
54 /// <param name="configData">Out param to put configuration data into.</param>
55 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
56 public int ProvideTextData(string name, out string configData)
57 {
58 if (this.configurationData.Contains(name))
59 {
60 configData = (string)this.configurationData[name];
61 return SOk;
62 }
63 else
64 {
65 configData = null;
66 return SFalse;
67 }
68 }
69
70 /// <summary>
71 /// Returns integer data based on name.
72 /// </summary>
73 /// <param name="name">Name of value to return.</param>
74 /// <param name="configData">Out param to put configuration data into.</param>
75 /// <returns>S_OK if value provided, S_FALSE if not.</returns>
76 public int ProvideIntegerData(string name, out int configData)
77 {
78 if (this.configurationData.Contains(name))
79 {
80 string val = (string)this.configurationData[name];
81 configData = Convert.ToInt32(val, CultureInfo.InvariantCulture);
82 return SOk;
83 }
84 else
85 {
86 configData = 0;
87 return SFalse;
88 }
89 }
90 }
91}
diff --git a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs b/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs
deleted file mode 100644
index af1ab3b0..00000000
--- a/src/WixToolset.Core/Bind/Databases/CopyTransformDataCommand.cs
+++ /dev/null
@@ -1,606 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Extensibility;
11 using WixToolset.Core.Native;
12
13 internal class CopyTransformDataCommand : ICommand
14 {
15 public bool CopyOutFileRows { private get; set; }
16
17 public BinderFileManagerCore FileManagerCore { private get; set; }
18
19 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
20
21 public Output Output { private get; set; }
22
23 public TableDefinitionCollection TableDefinitions { private get; set; }
24
25 public IEnumerable<FileFacade> FileFacades { get; private set; }
26
27 public void Execute()
28 {
29 Debug.Assert(OutputType.Patch != this.Output.Type);
30
31 List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null;
32
33#if false // TODO: Fix this patching related code to work correctly with FileFacades.
34 bool copyToPatch = (allFileRows != null);
35 bool copyFromPatch = !copyToPatch;
36
37 RowDictionary<MediaRow> patchMediaRows = new RowDictionary<MediaRow>();
38
39 Dictionary<int, RowDictionary<WixFileRow>> patchMediaFileRows = new Dictionary<int, RowDictionary<WixFileRow>>();
40
41 Table patchActualFileTable = this.Output.EnsureTable(this.TableDefinitions["File"]);
42 Table patchFileTable = this.Output.EnsureTable(this.TableDefinitions["WixFile"]);
43
44 if (copyFromPatch)
45 {
46 // index patch files by diskId+fileId
47 foreach (WixFileRow patchFileRow in patchFileTable.Rows)
48 {
49 int diskId = patchFileRow.DiskId;
50 RowDictionary<WixFileRow> mediaFileRows;
51 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
52 {
53 mediaFileRows = new RowDictionary<WixFileRow>();
54 patchMediaFileRows.Add(diskId, mediaFileRows);
55 }
56
57 mediaFileRows.Add(patchFileRow);
58 }
59
60 Table patchMediaTable = this.Output.EnsureTable(this.TableDefinitions["Media"]);
61 patchMediaRows = new RowDictionary<MediaRow>(patchMediaTable);
62 }
63
64 // index paired transforms
65 Dictionary<string, Output> pairedTransforms = new Dictionary<string, Output>();
66 foreach (SubStorage substorage in this.Output.SubStorages)
67 {
68 if (substorage.Name.StartsWith("#"))
69 {
70 pairedTransforms.Add(substorage.Name.Substring(1), substorage.Data);
71 }
72 }
73
74 try
75 {
76 // copy File bind data into substorages
77 foreach (SubStorage substorage in this.Output.SubStorages)
78 {
79 if (substorage.Name.StartsWith("#"))
80 {
81 // no changes necessary for paired transforms
82 continue;
83 }
84
85 Output mainTransform = substorage.Data;
86 Table mainWixFileTable = mainTransform.Tables["WixFile"];
87 Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"];
88
89 this.FileManagerCore.ActiveSubStorage = substorage;
90
91 RowDictionary<WixFileRow> mainWixFiles = new RowDictionary<WixFileRow>(mainWixFileTable);
92 RowDictionary<Row> mainMsiFileHashIndex = new RowDictionary<Row>();
93
94 Table mainFileTable = mainTransform.Tables["File"];
95 Output pairedTransform = (Output)pairedTransforms[substorage.Name];
96
97 // copy Media.LastSequence and index the MsiFileHash table if it exists.
98 if (copyFromPatch)
99 {
100 Table pairedMediaTable = pairedTransform.Tables["Media"];
101 foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows)
102 {
103 MediaRow patchMediaRow = patchMediaRows.Get(pairedMediaRow.DiskId);
104 pairedMediaRow.Fields[1] = patchMediaRow.Fields[1];
105 }
106
107 if (null != mainMsiFileHashTable)
108 {
109 mainMsiFileHashIndex = new RowDictionary<Row>(mainMsiFileHashTable);
110 }
111
112 // Validate file row changes for keypath-related issues
113 this.ValidateFileRowChanges(mainTransform);
114 }
115
116 // Index File table of pairedTransform
117 Table pairedFileTable = pairedTransform.Tables["File"];
118 RowDictionary<FileRow> pairedFileRows = new RowDictionary<FileRow>(pairedFileTable);
119
120 if (null != mainFileTable)
121 {
122 if (copyFromPatch)
123 {
124 // Remove the MsiFileHash table because it will be updated later with the final file hash for each file
125 mainTransform.Tables.Remove("MsiFileHash");
126 }
127
128 foreach (FileRow mainFileRow in mainFileTable.Rows)
129 {
130 if (RowOperation.Delete == mainFileRow.Operation)
131 {
132 continue;
133 }
134 else if (RowOperation.None == mainFileRow.Operation && !copyToPatch)
135 {
136 continue;
137 }
138
139 WixFileRow mainWixFileRow = mainWixFiles.Get(mainFileRow.File);
140
141 if (copyToPatch) // when copying to the patch, we need compare the underlying files and include all file changes.
142 {
143 ObjectField objectField = (ObjectField)mainWixFileRow.Fields[6];
144 FileRow pairedFileRow = pairedFileRows.Get(mainFileRow.File);
145
146 // If the file is new, we always need to add it to the patch.
147 if (mainFileRow.Operation != RowOperation.Add)
148 {
149 // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare.
150 if (null == objectField.PreviousData)
151 {
152 if (mainFileRow.Operation == RowOperation.None)
153 {
154 continue;
155 }
156 }
157 else
158 {
159 // TODO: should this entire condition be placed in the binder file manager?
160 if ((0 == (PatchAttributeType.Ignore & mainWixFileRow.PatchAttributes)) &&
161 !this.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString()))
162 {
163 // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified.
164 mainFileRow.Operation = RowOperation.Modify;
165 if (null != pairedFileRow)
166 {
167 // Always patch-added, but never non-compressed.
168 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
169 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
170 pairedFileRow.Fields[6].Modified = true;
171 pairedFileRow.Operation = RowOperation.Modify;
172 }
173 }
174 else
175 {
176 // The File is same. We need mark all the attributes as unchanged.
177 mainFileRow.Operation = RowOperation.None;
178 foreach (Field field in mainFileRow.Fields)
179 {
180 field.Modified = false;
181 }
182
183 if (null != pairedFileRow)
184 {
185 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded;
186 pairedFileRow.Fields[6].Modified = false;
187 pairedFileRow.Operation = RowOperation.None;
188 }
189 continue;
190 }
191 }
192 }
193 else if (null != pairedFileRow) // RowOperation.Add
194 {
195 // Always patch-added, but never non-compressed.
196 pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded;
197 pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
198 pairedFileRow.Fields[6].Modified = true;
199 pairedFileRow.Operation = RowOperation.Add;
200 }
201 }
202
203 // index patch files by diskId+fileId
204 int diskId = mainWixFileRow.DiskId;
205
206 RowDictionary<WixFileRow> mediaFileRows;
207 if (!patchMediaFileRows.TryGetValue(diskId, out mediaFileRows))
208 {
209 mediaFileRows = new RowDictionary<WixFileRow>();
210 patchMediaFileRows.Add(diskId, mediaFileRows);
211 }
212
213 string fileId = mainFileRow.File;
214 WixFileRow patchFileRow = mediaFileRows.Get(fileId);
215 if (copyToPatch)
216 {
217 if (null == patchFileRow)
218 {
219 FileRow patchActualFileRow = (FileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
220 patchActualFileRow.CopyFrom(mainFileRow);
221
222 patchFileRow = (WixFileRow)patchFileTable.CreateRow(mainFileRow.SourceLineNumbers);
223 patchFileRow.CopyFrom(mainWixFileRow);
224
225 mediaFileRows.Add(patchFileRow);
226
227 allFileRows.Add(new FileFacade(patchActualFileRow, patchFileRow, null)); // TODO: should we be passing along delta information? Probably, right?
228 }
229 else
230 {
231 // TODO: confirm the rest of data is identical?
232
233 // make sure Source is same. Otherwise we are silently ignoring a file.
234 if (0 != String.Compare(patchFileRow.Source, mainWixFileRow.Source, StringComparison.OrdinalIgnoreCase))
235 {
236 Messaging.Instance.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainWixFileRow.Source));
237 }
238
239 // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow.
240 patchFileRow.AppendPreviousDataFrom(mainWixFileRow);
241 }
242 }
243 else
244 {
245 // copy data from the patch back to the transform
246 if (null != patchFileRow)
247 {
248 FileRow pairedFileRow = (FileRow)pairedFileRows.Get(fileId);
249 for (int i = 0; i < patchFileRow.Fields.Length; i++)
250 {
251 string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString();
252 string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString();
253
254 if (1 == i)
255 {
256 // File.Component_ changes should not come from the shared file rows
257 // that contain the file information as each individual transform might
258 // have different changes (or no changes at all).
259 }
260 // File.Attributes should not changed for binary deltas
261 else if (6 == i)
262 {
263 if (null != patchFileRow.Patch)
264 {
265 // File.Attribute should not change for binary deltas
266 pairedFileRow.Attributes = mainFileRow.Attributes;
267 mainFileRow.Fields[i].Modified = false;
268 }
269 }
270 // File.Sequence is updated in pairedTransform, not mainTransform
271 else if (7 == i)
272 {
273 // file sequence is updated in Patch table instead of File table for delta patches
274 if (null != patchFileRow.Patch)
275 {
276 pairedFileRow.Fields[i].Modified = false;
277 }
278 else
279 {
280 pairedFileRow[i] = patchFileRow[i];
281 pairedFileRow.Fields[i].Modified = true;
282 }
283 mainFileRow.Fields[i].Modified = false;
284 }
285 else if (patchValue != mainValue)
286 {
287 mainFileRow[i] = patchFileRow[i];
288 mainFileRow.Fields[i].Modified = true;
289 if (mainFileRow.Operation == RowOperation.None)
290 {
291 mainFileRow.Operation = RowOperation.Modify;
292 }
293 }
294 }
295
296 // copy MsiFileHash row for this File
297 Row patchHashRow;
298 if (!mainMsiFileHashIndex.TryGetValue(patchFileRow.File, out patchHashRow))
299 {
300 patchHashRow = patchFileRow.Hash;
301 }
302
303 if (null != patchHashRow)
304 {
305 Table mainHashTable = mainTransform.EnsureTable(this.TableDefinitions["MsiFileHash"]);
306 Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers);
307 for (int i = 0; i < patchHashRow.Fields.Length; i++)
308 {
309 mainHashRow[i] = patchHashRow[i];
310 if (i > 1)
311 {
312 // assume all hash fields have been modified
313 mainHashRow.Fields[i].Modified = true;
314 }
315 }
316
317 // assume the MsiFileHash operation follows the File one
318 mainHashRow.Operation = mainFileRow.Operation;
319 }
320
321 // copy MsiAssemblyName rows for this File
322 List<Row> patchAssemblyNameRows = patchFileRow.AssemblyNames;
323 if (null != patchAssemblyNameRows)
324 {
325 Table mainAssemblyNameTable = mainTransform.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
326 foreach (Row patchAssemblyNameRow in patchAssemblyNameRows)
327 {
328 // Copy if there isn't an identical modified/added row already in the transform.
329 bool foundMatchingModifiedRow = false;
330 foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows)
331 {
332 if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/')))
333 {
334 foundMatchingModifiedRow = true;
335 break;
336 }
337 }
338
339 if (!foundMatchingModifiedRow)
340 {
341 Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers);
342 for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++)
343 {
344 mainAssemblyNameRow[i] = patchAssemblyNameRow[i];
345 }
346
347 // assume value field has been modified
348 mainAssemblyNameRow.Fields[2].Modified = true;
349 mainAssemblyNameRow.Operation = mainFileRow.Operation;
350 }
351 }
352 }
353
354 // Add patch header for this file
355 if (null != patchFileRow.Patch)
356 {
357 // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables.
358 AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow);
359 AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow);
360
361 // Add to Patch table
362 Table patchTable = pairedTransform.EnsureTable(this.TableDefinitions["Patch"]);
363 if (0 == patchTable.Rows.Count)
364 {
365 patchTable.Operation = TableOperation.Add;
366 }
367
368 Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers);
369 patchRow[0] = patchFileRow.File;
370 patchRow[1] = patchFileRow.Sequence;
371
372 FileInfo patchFile = new FileInfo(patchFileRow.Source);
373 patchRow[2] = (int)patchFile.Length;
374 patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1;
375
376 string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1];
377 if (MsiInterop.MsiMaxStreamNameLength < streamName.Length)
378 {
379 streamName = "_" + Guid.NewGuid().ToString("D").ToUpperInvariant().Replace('-', '_');
380 Table patchHeadersTable = pairedTransform.EnsureTable(this.TableDefinitions["MsiPatchHeaders"]);
381 if (0 == patchHeadersTable.Rows.Count)
382 {
383 patchHeadersTable.Operation = TableOperation.Add;
384 }
385 Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers);
386 patchHeadersRow[0] = streamName;
387 patchHeadersRow[1] = patchFileRow.Patch;
388 patchRow[5] = streamName;
389 patchHeadersRow.Operation = RowOperation.Add;
390 }
391 else
392 {
393 patchRow[4] = patchFileRow.Patch;
394 }
395 patchRow.Operation = RowOperation.Add;
396 }
397 }
398 else
399 {
400 // TODO: throw because all transform rows should have made it into the patch
401 }
402 }
403 }
404 }
405
406 if (copyFromPatch)
407 {
408 this.Output.Tables.Remove("Media");
409 this.Output.Tables.Remove("File");
410 this.Output.Tables.Remove("MsiFileHash");
411 this.Output.Tables.Remove("MsiAssemblyName");
412 }
413 }
414 }
415 finally
416 {
417 this.FileManagerCore.ActiveSubStorage = null;
418 }
419#endif
420 this.FileFacades = allFileRows;
421 }
422
423 /// <summary>
424 /// Adds the PatchFiles action to the sequence table if it does not already exist.
425 /// </summary>
426 /// <param name="table">The sequence table to check or modify.</param>
427 /// <param name="mainTransform">The primary authoring transform.</param>
428 /// <param name="pairedTransform">The secondary patch transform.</param>
429 /// <param name="mainFileRow">The file row that contains information about the patched file.</param>
430 private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow)
431 {
432 // Find/add PatchFiles action (also determine sequence for it).
433 // Search mainTransform first, then pairedTransform (pairedTransform overrides).
434 bool hasPatchFilesAction = false;
435 int seqInstallFiles = 0;
436 int seqDuplicateFiles = 0;
437 string tableName = table.ToString();
438
439 TestSequenceTableForPatchFilesAction(
440 mainTransform.Tables[tableName],
441 ref hasPatchFilesAction,
442 ref seqInstallFiles,
443 ref seqDuplicateFiles);
444 TestSequenceTableForPatchFilesAction(
445 pairedTransform.Tables[tableName],
446 ref hasPatchFilesAction,
447 ref seqInstallFiles,
448 ref seqDuplicateFiles);
449 if (!hasPatchFilesAction)
450 {
451 Table iesTable = pairedTransform.EnsureTable(this.TableDefinitions[tableName]);
452 if (0 == iesTable.Rows.Count)
453 {
454 iesTable.Operation = TableOperation.Add;
455 }
456
457 Row patchAction = iesTable.CreateRow(null);
458 WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"];
459 int sequence = wixPatchAction.Sequence;
460 // Test for default sequence value's appropriateness
461 if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence))
462 {
463 if (0 != seqDuplicateFiles)
464 {
465 if (seqDuplicateFiles < seqInstallFiles)
466 {
467 throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
468 }
469 else
470 {
471 sequence = (seqDuplicateFiles + seqInstallFiles) / 2;
472 if (seqInstallFiles == sequence || seqDuplicateFiles == sequence)
473 {
474 throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action));
475 }
476 }
477 }
478 else
479 {
480 sequence = seqInstallFiles + 1;
481 }
482 }
483 patchAction[0] = wixPatchAction.Action;
484 patchAction[1] = wixPatchAction.Condition;
485 patchAction[2] = sequence;
486 patchAction.Operation = RowOperation.Add;
487 }
488 }
489
490 /// <summary>
491 /// Tests sequence table for PatchFiles and associated actions
492 /// </summary>
493 /// <param name="iesTable">The table to test.</param>
494 /// <param name="hasPatchFilesAction">Set to true if PatchFiles action is found. Left unchanged otherwise.</param>
495 /// <param name="seqInstallFiles">Set to sequence value of InstallFiles action if found. Left unchanged otherwise.</param>
496 /// <param name="seqDuplicateFiles">Set to sequence value of DuplicateFiles action if found. Left unchanged otherwise.</param>
497 private static void TestSequenceTableForPatchFilesAction(Table iesTable, ref bool hasPatchFilesAction, ref int seqInstallFiles, ref int seqDuplicateFiles)
498 {
499 if (null != iesTable)
500 {
501 foreach (Row iesRow in iesTable.Rows)
502 {
503 if (String.Equals("PatchFiles", (string)iesRow[0], StringComparison.Ordinal))
504 {
505 hasPatchFilesAction = true;
506 }
507 if (String.Equals("InstallFiles", (string)iesRow[0], StringComparison.Ordinal))
508 {
509 seqInstallFiles = (int)iesRow.Fields[2].Data;
510 }
511 if (String.Equals("DuplicateFiles", (string)iesRow[0], StringComparison.Ordinal))
512 {
513 seqDuplicateFiles = (int)iesRow.Fields[2].Data;
514 }
515 }
516 }
517 }
518
519 /// <summary>
520 /// Signal a warning if a non-keypath file was changed in a patch without also changing the keypath file of the component.
521 /// </summary>
522 /// <param name="output">The output to validate.</param>
523 private void ValidateFileRowChanges(Output transform)
524 {
525 Table componentTable = transform.Tables["Component"];
526 Table fileTable = transform.Tables["File"];
527
528 // There's no sense validating keypaths if the transform has no component or file table
529 if (componentTable == null || fileTable == null)
530 {
531 return;
532 }
533
534 Dictionary<string, string> componentKeyPath = new Dictionary<string, string>(componentTable.Rows.Count);
535
536 // Index the Component table for non-directory & non-registry key paths.
537 foreach (Row row in componentTable.Rows)
538 {
539 if (null != row.Fields[5].Data &&
540 0 != ((int)row.Fields[3].Data & MsiInterop.MsidbComponentAttributesRegistryKeyPath))
541 {
542 componentKeyPath.Add(row.Fields[0].Data.ToString(), row.Fields[5].Data.ToString());
543 }
544 }
545
546 Dictionary<string, string> componentWithChangedKeyPath = new Dictionary<string, string>();
547 Dictionary<string, string> componentWithNonKeyPathChanged = new Dictionary<string, string>();
548 // Verify changes in the file table, now that file diffing has occurred
549 foreach (FileRow row in fileTable.Rows)
550 {
551 string fileId = row.Fields[0].Data.ToString();
552 string componentId = row.Fields[1].Data.ToString();
553
554 if (RowOperation.Modify != row.Operation)
555 {
556 continue;
557 }
558
559 // If this file is the keypath of a component
560 if (componentKeyPath.ContainsValue(fileId))
561 {
562 if (!componentWithChangedKeyPath.ContainsKey(componentId))
563 {
564 componentWithChangedKeyPath.Add(componentId, fileId);
565 }
566 }
567 else
568 {
569 if (!componentWithNonKeyPathChanged.ContainsKey(componentId))
570 {
571 componentWithNonKeyPathChanged.Add(componentId, fileId);
572 }
573 }
574 }
575
576 foreach (KeyValuePair<string, string> componentFile in componentWithNonKeyPathChanged)
577 {
578 // Make sure all changes to non keypath files also had a change in the keypath.
579 if (!componentWithChangedKeyPath.ContainsKey(componentFile.Key) && componentKeyPath.ContainsKey(componentFile.Key))
580 {
581 Messaging.Instance.OnMessage(WixWarnings.UpdateOfNonKeyPathFile((string)componentFile.Value, (string)componentFile.Key, (string)componentKeyPath[componentFile.Key]));
582 }
583 }
584 }
585
586 private bool CompareFiles(string targetFile, string updatedFile)
587 {
588 bool? compared = null;
589 foreach (IBinderFileManager fileManager in this.FileManagers)
590 {
591 compared = fileManager.CompareFiles(targetFile, updatedFile);
592 if (compared.HasValue)
593 {
594 break;
595 }
596 }
597
598 if (!compared.HasValue)
599 {
600 throw new InvalidOperationException(); // TODO: something needs to be said here that none of the binder file managers returned a result.
601 }
602
603 return compared.Value;
604 }
605 }
606}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
deleted file mode 100644
index 35c8abb4..00000000
--- a/src/WixToolset.Core/Bind/Databases/CreateCabinetsCommand.cs
+++ /dev/null
@@ -1,489 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Runtime.InteropServices;
11 using System.Threading;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.Extensibility;
15
16 /// <summary>
17 /// Creates cabinet files.
18 /// </summary>
19 internal class CreateCabinetsCommand : ICommand
20 {
21 private List<FileTransfer> fileTransfers;
22
23 private FileSplitCabNamesCallback newCabNamesCallBack;
24
25 private Dictionary<string, string> lastCabinetAddedToMediaTable; // Key is First Cabinet Name, Value is Last Cabinet Added in the Split Sequence
26
27 public CreateCabinetsCommand()
28 {
29 this.fileTransfers = new List<FileTransfer>();
30
31 this.newCabNamesCallBack = NewCabNamesCallBack;
32 }
33
34 /// <summary>
35 /// Sets the number of threads to use for cabinet creation.
36 /// </summary>
37 public int CabbingThreadCount { private get; set; }
38
39 public string TempFilesLocation { private get; set; }
40
41 /// <summary>
42 /// Sets the default compression level to use for cabinets
43 /// that don't have their compression level explicitly set.
44 /// </summary>
45 public CompressionLevel DefaultCompressionLevel { private get; set; }
46
47 public Output Output { private get; set; }
48
49 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
50
51 public string LayoutDirectory { private get; set; }
52
53 public bool Compressed { private get; set; }
54
55 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; }
56
57 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
58
59 public TableDefinitionCollection TableDefinitions { private get; set; }
60
61 public Table WixMediaTable { private get; set; }
62
63 public IEnumerable<FileTransfer> FileTransfers { get { return this.fileTransfers; } }
64
65 /// <param name="output">Output to generate image for.</param>
66 /// <param name="fileTransfers">Array of files to be transfered.</param>
67 /// <param name="layoutDirectory">The directory in which the image should be layed out.</param>
68 /// <param name="compressed">Flag if source image should be compressed.</param>
69 /// <returns>The uncompressed file rows.</returns>
70 public void Execute()
71 {
72 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
73
74 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
75
76 this.SetCabbingThreadCount();
77
78 // Send Binder object to Facilitate NewCabNamesCallBack Callback
79 CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
80
81 // Supply Compile MediaTemplate Attributes to Cabinet Builder
82 int MaximumCabinetSizeForLargeFileSplitting;
83 int MaximumUncompressedMediaSize;
84 this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize);
85 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting;
86 cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize;
87
88 foreach (var entry in this.FileRowsByCabinet)
89 {
90 MediaRow mediaRow = entry.Key;
91 IEnumerable<FileFacade> files = entry.Value;
92 CompressionLevel compressionLevel = this.DefaultCompressionLevel;
93
94 WixMediaRow wixMediaRow = null;
95 string mediaLayoutFolder = null;
96
97 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
98 {
99 mediaLayoutFolder = wixMediaRow.Layout;
100
101 if (wixMediaRow.CompressionLevel.HasValue)
102 {
103 compressionLevel = wixMediaRow.CompressionLevel.Value;
104 }
105 }
106
107 string cabinetDir = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
108
109 CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(this.Output, cabinetDir, mediaRow, compressionLevel, files, this.fileTransfers);
110 if (null != cabinetWorkItem)
111 {
112 cabinetBuilder.Enqueue(cabinetWorkItem);
113 }
114 }
115
116 // stop processing if an error previously occurred
117 if (Messaging.Instance.EncounteredError)
118 {
119 return;
120 }
121
122 // create queued cabinets with multiple threads
123 cabinetBuilder.CreateQueuedCabinets();
124 if (Messaging.Instance.EncounteredError)
125 {
126 return;
127 }
128 }
129
130 /// <summary>
131 /// Sets the thead count to the number of processors if the current thread count is set to 0.
132 /// </summary>
133 /// <remarks>The thread count value must be greater than 0 otherwise and exception will be thrown.</remarks>
134 private void SetCabbingThreadCount()
135 {
136 // default the number of cabbing threads to the number of processors if it wasn't specified
137 if (0 == this.CabbingThreadCount)
138 {
139 string numberOfProcessors = System.Environment.GetEnvironmentVariable("NUMBER_OF_PROCESSORS");
140
141 try
142 {
143 if (null != numberOfProcessors)
144 {
145 this.CabbingThreadCount = Convert.ToInt32(numberOfProcessors, CultureInfo.InvariantCulture.NumberFormat);
146
147 if (0 >= this.CabbingThreadCount)
148 {
149 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
150 }
151 }
152 else // default to 1 if the environment variable is not set
153 {
154 this.CabbingThreadCount = 1;
155 }
156
157 Messaging.Instance.OnMessage(WixVerboses.SetCabbingThreadCount(this.CabbingThreadCount.ToString()));
158 }
159 catch (ArgumentException)
160 {
161 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
162 }
163 catch (FormatException)
164 {
165 throw new WixException(WixErrors.IllegalEnvironmentVariable("NUMBER_OF_PROCESSORS", numberOfProcessors));
166 }
167 }
168 }
169
170
171 /// <summary>
172 /// Creates a work item to create a cabinet.
173 /// </summary>
174 /// <param name="output">Output for the current database.</param>
175 /// <param name="cabinetDir">Directory to create cabinet in.</param>
176 /// <param name="mediaRow">MediaRow containing information about the cabinet.</param>
177 /// <param name="fileFacades">Collection of files in this cabinet.</param>
178 /// <param name="fileTransfers">Array of files to be transfered.</param>
179 /// <returns>created CabinetWorkItem object</returns>
180 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers)
181 {
182 CabinetWorkItem cabinetWorkItem = null;
183 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet);
184
185 // check for an empty cabinet
186 if (!fileFacades.Any())
187 {
188 string cabinetName = mediaRow.Cabinet;
189
190 // remove the leading '#' from the embedded cabinet name to make the warning easier to understand
191 if (cabinetName.StartsWith("#", StringComparison.Ordinal))
192 {
193 cabinetName = cabinetName.Substring(1);
194 }
195
196 // If building a patch, remind them to run -p for torch.
197 if (OutputType.Patch == output.Type)
198 {
199 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true));
200 }
201 else
202 {
203 Messaging.Instance.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName));
204 }
205 }
206
207 ResolvedCabinet resolvedCabinet = this.ResolveCabinet(tempCabinetFileX, fileFacades);
208
209 // create a cabinet work item if it's not being skipped
210 if (CabinetBuildOption.BuildAndCopy == resolvedCabinet.BuildOption || CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption)
211 {
212 int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet).
213
214 cabinetWorkItem = new CabinetWorkItem(fileFacades, resolvedCabinet.Path, maxThreshold, compressionLevel/*, this.FileManager*/);
215 }
216 else // reuse the cabinet from the cabinet cache.
217 {
218 Messaging.Instance.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, resolvedCabinet.Path));
219
220 try
221 {
222 // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The
223 // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that
224 // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from
225 // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output)
226 // causing the project to look like it perpetually needs a rebuild until all of the reused
227 // cabinets get newer timestamps.
228 File.SetLastWriteTime(resolvedCabinet.Path, DateTime.Now);
229 }
230 catch (Exception e)
231 {
232 Messaging.Instance.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, resolvedCabinet.Path, e.Message));
233 }
234 }
235
236 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
237 {
238 Table streamsTable = output.EnsureTable(this.TableDefinitions["_Streams"]);
239
240 Row streamRow = streamsTable.CreateRow(mediaRow.SourceLineNumbers);
241 streamRow[0] = mediaRow.Cabinet.Substring(1);
242 streamRow[1] = resolvedCabinet.Path;
243 }
244 else
245 {
246 string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet);
247 FileTransfer transfer;
248 if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer))
249 {
250 transfer.Built = true;
251 fileTransfers.Add(transfer);
252 }
253 }
254
255 return cabinetWorkItem;
256 }
257
258 private ResolvedCabinet ResolveCabinet(string cabinetPath, IEnumerable<FileFacade> fileFacades)
259 {
260 ResolvedCabinet resolved = null;
261
262 List<BindFileWithPath> filesWithPath = fileFacades.Select(f => new BindFileWithPath() { Id = f.File.File, Path = f.WixFile.Source }).ToList();
263
264 foreach (IBinderFileManager fileManager in this.FileManagers)
265 {
266 resolved = fileManager.ResolveCabinet(cabinetPath, filesWithPath);
267 if (null != resolved)
268 {
269 break;
270 }
271 }
272
273 return resolved;
274 }
275
276 /// <summary>
277 /// Delegate for Cabinet Split Callback
278 /// </summary>
279 [UnmanagedFunctionPointer(CallingConvention.StdCall)]
280 internal delegate void FileSplitCabNamesCallback([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken);
281
282 /// <summary>
283 /// Call back to Add File Transfer for new Cab and add new Cab to Media table
284 /// This callback can come from Multiple Cabinet Builder Threads and so should be thread safe
285 /// This callback will not be called in case there is no File splitting. i.e. MaximumCabinetSizeForLargeFileSplitting was not authored
286 /// </summary>
287 /// <param name="firstCabName">The name of splitting cabinet without extention e.g. "cab1".</param>
288 /// <param name="newCabName">The name of the new cabinet that would be formed by splitting e.g. "cab1b.cab"</param>
289 /// <param name="fileToken">The file token of the first file present in the splitting cabinet</param>
290 internal void NewCabNamesCallBack([MarshalAs(UnmanagedType.LPWStr)]string firstCabName, [MarshalAs(UnmanagedType.LPWStr)]string newCabName, [MarshalAs(UnmanagedType.LPWStr)]string fileToken)
291 {
292 // Locking Mutex here as this callback can come from Multiple Cabinet Builder Threads
293 Mutex mutex = new Mutex(false, "WixCabinetSplitBinderCallback");
294 try
295 {
296 if (!mutex.WaitOne(0, false)) // Check if you can get the lock
297 {
298 // Cound not get the Lock
299 Messaging.Instance.OnMessage(WixVerboses.CabinetsSplitInParallel());
300 mutex.WaitOne(); // Wait on other thread
301 }
302
303 string firstCabinetName = firstCabName + ".cab";
304 string newCabinetName = newCabName;
305 bool transferAdded = false; // Used for Error Handling
306
307 // Create File Transfer for new Cabinet using transfer of Base Cabinet
308 foreach (FileTransfer transfer in this.FileTransfers)
309 {
310 if (firstCabinetName.Equals(Path.GetFileName(transfer.Source), StringComparison.InvariantCultureIgnoreCase))
311 {
312 string newCabSourcePath = Path.Combine(Path.GetDirectoryName(transfer.Source), newCabinetName);
313 string newCabTargetPath = Path.Combine(Path.GetDirectoryName(transfer.Destination), newCabinetName);
314
315 FileTransfer newTransfer;
316 if (FileTransfer.TryCreate(newCabSourcePath, newCabTargetPath, transfer.Move, "Cabinet", transfer.SourceLineNumbers, out newTransfer))
317 {
318 newTransfer.Built = true;
319 this.fileTransfers.Add(newTransfer);
320 transferAdded = true;
321 break;
322 }
323 }
324 }
325
326 // Check if File Transfer was added
327 if (!transferAdded)
328 {
329 throw new WixException(WixErrors.SplitCabinetCopyRegistrationFailed(newCabinetName, firstCabinetName));
330 }
331
332 // Add the new Cabinets to media table using LastSequence of Base Cabinet
333 Table mediaTable = this.Output.Tables["Media"];
334 Table wixFileTable = this.Output.Tables["WixFile"];
335 int diskIDForLastSplitCabAdded = 0; // The DiskID value for the first cab in this cabinet split chain
336 int lastSequenceForLastSplitCabAdded = 0; // The LastSequence value for the first cab in this cabinet split chain
337 bool lastSplitCabinetFound = false; // Used for Error Handling
338
339 string lastCabinetOfThisSequence = String.Empty;
340 // Get the Value of Last Cabinet Added in this split Sequence from Dictionary
341 if (!this.lastCabinetAddedToMediaTable.TryGetValue(firstCabinetName, out lastCabinetOfThisSequence))
342 {
343 // If there is no value for this sequence, then use first Cabinet is the last one of this split sequence
344 lastCabinetOfThisSequence = firstCabinetName;
345 }
346
347 foreach (MediaRow mediaRow in mediaTable.Rows)
348 {
349 // Get details for the Last Cabinet Added in this Split Sequence
350 if ((lastSequenceForLastSplitCabAdded == 0) && lastCabinetOfThisSequence.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
351 {
352 lastSequenceForLastSplitCabAdded = mediaRow.LastSequence;
353 diskIDForLastSplitCabAdded = mediaRow.DiskId;
354 lastSplitCabinetFound = true;
355 }
356
357 // Check for Name Collision for the new Cabinet added
358 if (newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
359 {
360 // Name Collision of generated Split Cabinet Name and user Specified Cab name for current row
361 throw new WixException(WixErrors.SplitCabinetNameCollision(newCabinetName, firstCabinetName));
362 }
363 }
364
365 // Check if the last Split Cabinet was found in the Media Table
366 if (!lastSplitCabinetFound)
367 {
368 throw new WixException(WixErrors.SplitCabinetInsertionFailed(newCabinetName, firstCabinetName, lastCabinetOfThisSequence));
369 }
370
371 // The new Row has to be inserted just after the last cab in this cabinet split chain according to DiskID Sort
372 // This is because the FDI Extract requires DiskID of Split Cabinets to be continuous. It Fails otherwise with
373 // Error 2350 (FDI Server Error) as next DiskID did not have the right split cabinet during extraction
374 MediaRow newMediaRow = (MediaRow)mediaTable.CreateRow(null);
375 newMediaRow.Cabinet = newCabinetName;
376 newMediaRow.DiskId = diskIDForLastSplitCabAdded + 1; // When Sorted with DiskID, this new Cabinet Row is an Insertion
377 newMediaRow.LastSequence = lastSequenceForLastSplitCabAdded;
378
379 // Now increment the DiskID for all rows that come after the newly inserted row to Ensure that DiskId is unique
380 foreach (MediaRow mediaRow in mediaTable.Rows)
381 {
382 // Check if this row comes after inserted row and it is not the new cabinet inserted row
383 if (mediaRow.DiskId >= newMediaRow.DiskId && !newCabinetName.Equals(mediaRow.Cabinet, StringComparison.InvariantCultureIgnoreCase))
384 {
385 mediaRow.DiskId++; // Increment DiskID
386 }
387 }
388
389 // Now Increment DiskID for All files Rows so that they refer to the right Media Row
390 foreach (WixFileRow wixFileRow in wixFileTable.Rows)
391 {
392 // Check if this row comes after inserted row and if this row is not the file that has to go into the current cabinet
393 // This check will work as we have only one large file in every splitting cabinet
394 // If we want to support splitting cabinet with more large files we need to update this code
395 if (wixFileRow.DiskId >= newMediaRow.DiskId && !wixFileRow.File.Equals(fileToken, StringComparison.InvariantCultureIgnoreCase))
396 {
397 wixFileRow.DiskId++; // Increment DiskID
398 }
399 }
400
401 // Update the Last Cabinet Added in the Split Sequence in Dictionary for future callback
402 this.lastCabinetAddedToMediaTable[firstCabinetName] = newCabinetName;
403
404 mediaTable.ValidateRows(); // Valdiates DiskDIs, throws Exception as Wix Error if validation fails
405 }
406 finally
407 {
408 // Releasing the Mutex here
409 mutex.ReleaseMutex();
410 }
411 }
412
413
414 /// <summary>
415 /// Gets Compiler Values of MediaTemplate Attributes governing Maximum Cabinet Size after applying Environment Variable Overrides
416 /// </summary>
417 /// <param name="output">Output to generate image for.</param>
418 /// <param name="fileRows">The indexed file rows.</param>
419 private void GetMediaTemplateAttributes(out int maxCabSizeForLargeFileSplitting, out int maxUncompressedMediaSize)
420 {
421 // Get Environment Variable Overrides for MediaTemplate Attributes governing Maximum Cabinet Size
422 string mcslfsString = Environment.GetEnvironmentVariable("WIX_MCSLFS");
423 string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS");
424 int maxCabSizeForLargeFileInMB = 0;
425 int maxPreCompressedSizeInMB = 0;
426 ulong testOverFlow = 0;
427
428 // Supply Compile MediaTemplate Attributes to Cabinet Builder
429 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
430 if (mediaTemplateTable != null)
431 {
432 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
433
434 // Get the Value for Max Cab Size for File Splitting
435 try
436 {
437 // Override authored mcslfs value if environment variable is authored.
438 if (!String.IsNullOrEmpty(mcslfsString))
439 {
440 maxCabSizeForLargeFileInMB = Int32.Parse(mcslfsString);
441 }
442 else
443 {
444 maxCabSizeForLargeFileInMB = mediaTemplateRow.MaximumCabinetSizeForLargeFileSplitting;
445 }
446 testOverFlow = (ulong)maxCabSizeForLargeFileInMB * 1024 * 1024;
447 }
448 catch (FormatException)
449 {
450 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MCSLFS", mcslfsString));
451 }
452 catch (OverflowException)
453 {
454 throw new WixException(WixErrors.MaximumCabinetSizeForLargeFileSplittingTooLarge(null, maxCabSizeForLargeFileInMB, CompilerCore.MaxValueOfMaxCabSizeForLargeFileSplitting));
455 }
456
457 try
458 {
459 // Override authored mums value if environment variable is authored.
460 if (!String.IsNullOrEmpty(mumsString))
461 {
462 maxPreCompressedSizeInMB = Int32.Parse(mumsString);
463 }
464 else
465 {
466 maxPreCompressedSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize;
467 }
468 testOverFlow = (ulong)maxPreCompressedSizeInMB * 1024 * 1024;
469 }
470 catch (FormatException)
471 {
472 throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString));
473 }
474 catch (OverflowException)
475 {
476 throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCompressedSizeInMB));
477 }
478
479 maxCabSizeForLargeFileSplitting = maxCabSizeForLargeFileInMB;
480 maxUncompressedMediaSize = maxPreCompressedSizeInMB;
481 }
482 else
483 {
484 maxCabSizeForLargeFileSplitting = 0;
485 maxUncompressedMediaSize = CompilerCore.DefaultMaximumUncompressedMediaSize;
486 }
487 }
488 }
489}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs
deleted file mode 100644
index 933a1ea8..00000000
--- a/src/WixToolset.Core/Bind/Databases/CreateDeltaPatchesCommand.cs
+++ /dev/null
@@ -1,86 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 /// <summary>
13 /// Creates delta patches and updates the appropriate rows to point to the newly generated patches.
14 /// </summary>
15 internal class CreateDeltaPatchesCommand : ICommand
16 {
17 public IEnumerable<FileFacade> FileFacades { private get; set; }
18
19 public Table WixPatchIdTable { private get; set; }
20
21 public string TempFilesLocation { private get; set; }
22
23 public void Execute()
24 {
25 bool optimizePatchSizeForLargeFiles = false;
26 PatchAPI.PatchInterop.PatchSymbolFlagsType apiPatchingSymbolFlags = 0;
27
28 if (null != this.WixPatchIdTable)
29 {
30 Row row = this.WixPatchIdTable.Rows[0];
31 if (null != row)
32 {
33 if (null != row[2])
34 {
35 optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture));
36 }
37
38 if (null != row[3])
39 {
40 apiPatchingSymbolFlags = (PatchAPI.PatchInterop.PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture);
41 }
42 }
43 }
44
45 foreach (FileFacade facade in this.FileFacades)
46 {
47 if (RowOperation.Modify == facade.File.Operation &&
48 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile))
49 {
50 string deltaBase = String.Concat("delta_", facade.File.File);
51 string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf"));
52 string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd"));
53
54 bool retainRangeWarning = false;
55
56 if (PatchAPI.PatchInterop.CreateDelta(
57 deltaFile,
58 facade.WixFile.Source,
59 facade.DeltaPatchFile.Symbols,
60 facade.DeltaPatchFile.RetainOffsets,
61 new[] { facade.WixFile.PreviousSource },
62 facade.DeltaPatchFile.PreviousSymbols.Split(new[] { ';' }),
63 facade.DeltaPatchFile.PreviousIgnoreLengths.Split(new[] { ';' }),
64 facade.DeltaPatchFile.PreviousIgnoreOffsets.Split(new[] { ';' }),
65 facade.DeltaPatchFile.PreviousRetainLengths.Split(new[] { ';' }),
66 facade.DeltaPatchFile.PreviousRetainOffsets.Split(new[] { ';' }),
67 apiPatchingSymbolFlags,
68 optimizePatchSizeForLargeFiles,
69 out retainRangeWarning))
70 {
71 PatchAPI.PatchInterop.ExtractDeltaHeader(deltaFile, headerFile);
72
73 facade.WixFile.Source = deltaFile;
74 facade.WixFile.DeltaPatchHeaderSource = headerFile;
75 }
76
77 if (retainRangeWarning)
78 {
79 // TODO: get patch family to add to warning message for PatchWiz parity.
80 Messaging.Instance.OnMessage(WixWarnings.RetainRangeMismatch(facade.File.SourceLineNumbers, facade.File.File));
81 }
82 }
83 }
84 }
85 }
86}
diff --git a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs
deleted file mode 100644
index 5db2768b..00000000
--- a/src/WixToolset.Core/Bind/Databases/CreateSpecialPropertiesCommand.cs
+++ /dev/null
@@ -1,68 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class CreateSpecialPropertiesCommand : ICommand
11 {
12 public Table PropertyTable { private get; set; }
13
14 public Table WixPropertyTable { private get; set; }
15
16 public void Execute()
17 {
18 // Create the special properties.
19 if (null != this.WixPropertyTable)
20 {
21 // Create lists of the properties that contribute to the special lists of properties.
22 SortedSet<string> adminProperties = new SortedSet<string>();
23 SortedSet<string> secureProperties = new SortedSet<string>();
24 SortedSet<string> hiddenProperties = new SortedSet<string>();
25
26 foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows)
27 {
28 if (wixPropertyRow.Admin)
29 {
30 adminProperties.Add(wixPropertyRow.Id);
31 }
32
33 if (wixPropertyRow.Hidden)
34 {
35 hiddenProperties.Add(wixPropertyRow.Id);
36 }
37
38 if (wixPropertyRow.Secure)
39 {
40 secureProperties.Add(wixPropertyRow.Id);
41 }
42 }
43
44 Table propertyTable = this.PropertyTable;
45 if (0 < adminProperties.Count)
46 {
47 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
48 row.Property = "AdminProperties";
49 row.Value = String.Join(";", adminProperties);
50 }
51
52 if (0 < secureProperties.Count)
53 {
54 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
55 row.Property = "SecureCustomProperties";
56 row.Value = String.Join(";", secureProperties);
57 }
58
59 if (0 < hiddenProperties.Count)
60 {
61 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null);
62 row.Property = "MsiHiddenProperties";
63 row.Value = String.Join(";", hiddenProperties);
64 }
65 }
66 }
67 }
68}
diff --git a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs
deleted file mode 100644
index bee1488b..00000000
--- a/src/WixToolset.Core/Bind/Databases/ExtractMergeModuleFilesCommand.cs
+++ /dev/null
@@ -1,225 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Runtime.InteropServices;
12 using WixToolset.Cab;
13 using WixToolset.Data;
14 using WixToolset.Data.Rows;
15 using WixToolset.MergeMod;
16 using WixToolset.Msi;
17 using WixToolset.Core.Native;
18
19 /// <summary>
20 /// Retrieve files information and extract them from merge modules.
21 /// </summary>
22 internal class ExtractMergeModuleFilesCommand : ICommand
23 {
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public Table FileTable { private get; set; }
27
28 public Table WixFileTable { private get; set; }
29
30 public Table WixMergeTable { private get; set; }
31
32 public int OutputInstallerVersion { private get; set; }
33
34 public bool SuppressLayout { private get; set; }
35
36 public string TempFilesLocation { private get; set; }
37
38 public IEnumerable<FileFacade> MergeModulesFileFacades { get; private set; }
39
40 public void Execute()
41 {
42 List<FileFacade> mergeModulesFileFacades = new List<FileFacade>();
43
44 IMsmMerge2 merge = MsmInterop.GetMsmMerge();
45
46 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
47 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
48 // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out
49 // there are other cases where we need all the file rows indexed, however they are not common cases.
50 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
51 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
52 // is a lot more costly for the common cases.
53 Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal);
54
55 foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows)
56 {
57 bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
58
59 // If the module has files and creating layout
60 if (containsFiles && !this.SuppressLayout)
61 {
62 this.ExtractFilesFromMergeModule(merge, wixMergeRow);
63 }
64 }
65
66 this.MergeModulesFileFacades = mergeModulesFileFacades;
67 }
68
69 private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades)
70 {
71 bool containsFiles = false;
72
73 try
74 {
75 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
76 using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
77 {
78 if (db.TableExists("File") && db.TableExists("Component"))
79 {
80 Dictionary<string, FileFacade> uniqueModuleFileIdentifiers = new Dictionary<string, FileFacade>(StringComparer.OrdinalIgnoreCase);
81
82 using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
83 {
84 // add each file row from the merge module into the file row collection (check for errors along the way)
85 while (true)
86 {
87 using (Record record = view.Fetch())
88 {
89 if (null == record)
90 {
91 break;
92 }
93
94 // NOTE: this is very tricky - the merge module file rows are not added to the
95 // file table because they should not be created via idt import. Instead, these
96 // rows are created by merging in the actual modules.
97 FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
98 fileRow.File = record[1];
99 fileRow.Compressed = wixMergeRow.FileCompression;
100
101 WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
102 wixFileRow.Directory = record[2];
103 wixFileRow.DiskId = wixMergeRow.DiskId;
104 wixFileRow.PatchGroup = -1;
105 wixFileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), Path.DirectorySeparatorChar, record[1]);
106
107 FileFacade mergeModuleFileFacade = new FileFacade(true, fileRow, wixFileRow);
108
109 FileFacade collidingFacade;
110
111 // If case-sensitive collision with another merge module or a user-authored file identifier.
112 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade))
113 {
114 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File));
115 }
116 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
117 {
118 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File));
119 }
120 else // no collision
121 {
122 mergeModulesFileFacades.Add(mergeModuleFileFacade);
123
124 // Keep updating the indexes as new rows are added.
125 indexedFileFacades.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
126 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.File.File, mergeModuleFileFacade);
127 }
128
129 containsFiles = true;
130 }
131 }
132 }
133 }
134
135 // Get the summary information to detect the Schema
136 using (SummaryInformation summaryInformation = new SummaryInformation(db))
137 {
138 string moduleInstallerVersionString = summaryInformation.GetProperty(14);
139
140 try
141 {
142 int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
143 if (moduleInstallerVersion > this.OutputInstallerVersion)
144 {
145 Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion));
146 }
147 }
148 catch (FormatException)
149 {
150 throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString));
151 }
152 }
153 }
154 }
155 catch (FileNotFoundException)
156 {
157 throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
158 }
159 catch (Win32Exception)
160 {
161 throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile));
162 }
163
164 return containsFiles;
165 }
166
167 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow)
168 {
169 bool moduleOpen = false;
170 short mergeLanguage;
171
172 try
173 {
174 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
175 }
176 catch (System.FormatException)
177 {
178 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
179 return;
180 }
181
182 try
183 {
184 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
185 moduleOpen = true;
186
187 string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat);
188
189 // extract the module cabinet, then explode all of the files to a temp directory
190 string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab");
191 merge.ExtractCAB(moduleCabPath);
192
193 string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId);
194 Directory.CreateDirectory(mergeIdPath);
195
196 using (WixExtractCab extractCab = new WixExtractCab())
197 {
198 try
199 {
200 extractCab.Extract(moduleCabPath, mergeIdPath);
201 }
202 catch (FileNotFoundException)
203 {
204 throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
205 }
206 catch
207 {
208 throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
209 }
210 }
211 }
212 catch (COMException ce)
213 {
214 throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message));
215 }
216 finally
217 {
218 if (moduleOpen)
219 {
220 merge.CloseModule();
221 }
222 }
223 }
224 }
225}
diff --git a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs
deleted file mode 100644
index b6bcd3af..00000000
--- a/src/WixToolset.Core/Bind/Databases/GetFileFacadesCommand.cs
+++ /dev/null
@@ -1,148 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11
12 internal class GetFileFacadesCommand : ICommand
13 {
14 public Table FileTable { private get; set; }
15
16 public Table WixFileTable { private get; set; }
17
18 public Table WixDeltaPatchFileTable { private get; set; }
19
20 public Table WixDeltaPatchSymbolPathsTable { private get; set; }
21
22 public List<FileFacade> FileFacades { get; private set; }
23
24 public void Execute()
25 {
26 List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count);
27
28 RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable);
29 RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable);
30
31 foreach (FileRow file in this.FileTable.Rows)
32 {
33 WixDeltaPatchFileRow deltaPatchFile = null;
34
35 deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile);
36
37 facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile));
38 }
39
40 if (null != this.WixDeltaPatchSymbolPathsTable)
41 {
42 this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
43 }
44
45 this.FileFacades = facades;
46 }
47
48 /// <summary>
49 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
50 /// </summary>
51 public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades)
52 {
53 ILookup<string, FileFacade> filesByComponent = null;
54 ILookup<string, FileFacade> filesByDirectory = null;
55 ILookup<string, FileFacade> filesByDiskId = null;
56
57 foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type))
58 {
59 switch (row.Type)
60 {
61 case SymbolPathType.File:
62 this.MergeSymbolPaths(row, deltaPatchFiles[row.Id]);
63 break;
64
65 case SymbolPathType.Component:
66 if (null == filesByComponent)
67 {
68 filesByComponent = facades.ToLookup(f => f.File.Component);
69 }
70
71 foreach (FileFacade facade in filesByComponent[row.Id])
72 {
73 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
74 }
75 break;
76
77 case SymbolPathType.Directory:
78 if (null == filesByDirectory)
79 {
80 filesByDirectory = facades.ToLookup(f => f.WixFile.Directory);
81 }
82
83 foreach (FileFacade facade in filesByDirectory[row.Id])
84 {
85 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
86 }
87 break;
88
89 case SymbolPathType.Media:
90 if (null == filesByDiskId)
91 {
92 filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture));
93 }
94
95 foreach (FileFacade facade in filesByDiskId[row.Id])
96 {
97 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
98 }
99 break;
100
101 case SymbolPathType.Product:
102 foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values)
103 {
104 this.MergeSymbolPaths(row, fileRow);
105 }
106 break;
107
108 default:
109 // error
110 break;
111 }
112 }
113
114 return deltaPatchFiles;
115 }
116
117 /// <summary>
118 /// Merge data from a row in the WixPatchSymbolsPaths table into an associated WixDeltaPatchFile row.
119 /// </summary>
120 /// <param name="row">Row from the WixPatchSymbolsPaths table.</param>
121 /// <param name="file">FileRow into which to set symbol information.</param>
122 /// <comment>This includes PreviousData as well.</comment>
123 private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file)
124 {
125 if (null == file.Symbols)
126 {
127 file.Symbols = row.SymbolPaths;
128 }
129 else
130 {
131 file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths);
132 }
133
134 Field field = row.Fields[2];
135 if (null != field.PreviousData)
136 {
137 if (null == file.PreviousSymbols)
138 {
139 file.PreviousSymbols = field.PreviousData;
140 }
141 else
142 {
143 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
144 }
145 }
146 }
147 }
148}
diff --git a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs b/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs
deleted file mode 100644
index 035ef059..00000000
--- a/src/WixToolset.Core/Bind/Databases/MergeModulesCommand.cs
+++ /dev/null
@@ -1,350 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Globalization;
11 using System.IO;
12 using System.Linq;
13 using System.Runtime.InteropServices;
14 using System.Text;
15 using System.Xml;
16 using System.Xml.XPath;
17 using WixToolset.Clr.Interop;
18 using WixToolset.Data;
19 using WixToolset.Data.Rows;
20 using WixToolset.MergeMod;
21 using WixToolset.Msi;
22 using WixToolset.Core.Native;
23
24 /// <summary>
25 /// Update file information.
26 /// </summary>
27 internal class MergeModulesCommand : ICommand
28 {
29 public IEnumerable<FileFacade> FileFacades { private get; set; }
30
31 public Output Output { private get; set; }
32
33 public string OutputPath { private get; set; }
34
35 public IEnumerable<string> SuppressedTableNames { private get; set; }
36
37 public string TempFilesLocation { private get; set; }
38
39 public void Execute()
40 {
41 Debug.Assert(OutputType.Product == this.Output.Type);
42
43 Table wixMergeTable = this.Output.Tables["WixMerge"];
44 Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"];
45
46 // check for merge rows to see if there is any work to do
47 if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count)
48 {
49 return;
50 }
51
52 IMsmMerge2 merge = null;
53 bool commit = true;
54 bool logOpen = false;
55 bool databaseOpen = false;
56 string logPath = null;
57 try
58 {
59 merge = MsmInterop.GetMsmMerge();
60
61 logPath = Path.Combine(this.TempFilesLocation, "merge.log");
62 merge.OpenLog(logPath);
63 logOpen = true;
64
65 merge.OpenDatabase(this.OutputPath);
66 databaseOpen = true;
67
68 // process all the merge rows
69 foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows)
70 {
71 bool moduleOpen = false;
72
73 try
74 {
75 short mergeLanguage;
76
77 try
78 {
79 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
80 }
81 catch (System.FormatException)
82 {
83 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
84 continue;
85 }
86
87 Messaging.Instance.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage));
88 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
89 moduleOpen = true;
90
91 // If there is merge configuration data, create a callback object to contain it all.
92 ConfigurationCallback callback = null;
93 if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData))
94 {
95 callback = new ConfigurationCallback(wixMergeRow.ConfigurationData);
96 }
97
98 // merge the module into the database that's being built
99 Messaging.Instance.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile));
100 merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback);
101
102 // connect any non-primary features
103 if (null != wixFeatureModulesTable)
104 {
105 foreach (Row row in wixFeatureModulesTable.Rows)
106 {
107 if (wixMergeRow.Id == (string)row[1])
108 {
109 Messaging.Instance.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0]));
110 merge.Connect((string)row[0]);
111 }
112 }
113 }
114 }
115 catch (COMException)
116 {
117 commit = false;
118 }
119 finally
120 {
121 IMsmErrors mergeErrors = merge.Errors;
122
123 // display all the errors encountered during the merge operations for this module
124 for (int i = 1; i <= mergeErrors.Count; i++)
125 {
126 IMsmError mergeError = mergeErrors[i];
127 StringBuilder databaseKeys = new StringBuilder();
128 StringBuilder moduleKeys = new StringBuilder();
129
130 // build a string of the database keys
131 for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++)
132 {
133 if (1 != j)
134 {
135 databaseKeys.Append(';');
136 }
137 databaseKeys.Append(mergeError.DatabaseKeys[j]);
138 }
139
140 // build a string of the module keys
141 for (int j = 1; j <= mergeError.ModuleKeys.Count; j++)
142 {
143 if (1 != j)
144 {
145 moduleKeys.Append(';');
146 }
147 moduleKeys.Append(mergeError.ModuleKeys[j]);
148 }
149
150 // display the merge error based on the msm error type
151 switch (mergeError.Type)
152 {
153 case MsmErrorType.msmErrorExclusion:
154 Messaging.Instance.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString()));
155 break;
156 case MsmErrorType.msmErrorFeatureRequired:
157 Messaging.Instance.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id));
158 break;
159 case MsmErrorType.msmErrorLanguageFailed:
160 Messaging.Instance.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
161 break;
162 case MsmErrorType.msmErrorLanguageUnsupported:
163 Messaging.Instance.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile));
164 break;
165 case MsmErrorType.msmErrorResequenceMerge:
166 Messaging.Instance.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
167 break;
168 case MsmErrorType.msmErrorTableMerge:
169 if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table
170 {
171 Messaging.Instance.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile));
172 }
173 break;
174 case MsmErrorType.msmErrorPlatformMismatch:
175 Messaging.Instance.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
176 break;
177 default:
178 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace));
179 break;
180 }
181 }
182
183 if (0 >= mergeErrors.Count && !commit)
184 {
185 Messaging.Instance.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace));
186 }
187
188 if (moduleOpen)
189 {
190 merge.CloseModule();
191 }
192 }
193 }
194 }
195 finally
196 {
197 if (databaseOpen)
198 {
199 merge.CloseDatabase(commit);
200 }
201
202 if (logOpen)
203 {
204 merge.CloseLog();
205 }
206 }
207
208 // stop processing if an error previously occurred
209 if (Messaging.Instance.EncounteredError)
210 {
211 return;
212 }
213
214 using (Database db = new Database(this.OutputPath, OpenDatabase.Direct))
215 {
216 Table suppressActionTable = this.Output.Tables["WixSuppressAction"];
217
218 // suppress individual actions
219 if (null != suppressActionTable)
220 {
221 foreach (Row row in suppressActionTable.Rows)
222 {
223 if (db.TableExists((string)row[0]))
224 {
225 string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]);
226
227 using (View view = db.OpenExecuteView(query))
228 {
229 using (Record record = view.Fetch())
230 {
231 if (null != record)
232 {
233 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString()));
234 view.Modify(ModifyView.Delete, record);
235 }
236 }
237 }
238 }
239 }
240 }
241
242 // query for merge module actions in suppressed sequences and drop them
243 foreach (string tableName in this.SuppressedTableNames)
244 {
245 if (!db.TableExists(tableName))
246 {
247 continue;
248 }
249
250 using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName)))
251 {
252 while (true)
253 {
254 using (Record resultRecord = view.Fetch())
255 {
256 if (null == resultRecord)
257 {
258 break;
259 }
260
261 Messaging.Instance.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName));
262 }
263 }
264 }
265
266 // drop suppressed sequences
267 using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName)))
268 {
269 }
270
271 // delete the validation rows
272 using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?")))
273 {
274 using (Record record = new Record(1))
275 {
276 record.SetString(1, tableName);
277 view.Execute(record);
278 }
279 }
280 }
281
282 // now update the Attributes column for the files from the Merge Modules
283 Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles());
284 using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
285 {
286 foreach (FileFacade file in this.FileFacades)
287 {
288 if (!file.FromModule)
289 {
290 continue;
291 }
292
293 using (Record record = new Record(1))
294 {
295 record.SetString(1, file.File.File);
296 view.Execute(record);
297 }
298
299 using (Record recordUpdate = view.Fetch())
300 {
301 if (null == recordUpdate)
302 {
303 throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module.");
304 }
305
306 recordUpdate.SetInteger(1, file.File.Sequence);
307
308 // update the file attributes to match the compression specified
309 // on the Merge element or on the Package element
310 int attributes = 0;
311
312 // get the current value if its not null
313 if (!recordUpdate.IsNull(2))
314 {
315 attributes = recordUpdate.GetInteger(2);
316 }
317
318 if (YesNoType.Yes == file.File.Compressed)
319 {
320 // these are mutually exclusive
321 attributes |= MsiInterop.MsidbFileAttributesCompressed;
322 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
323 }
324 else if (YesNoType.No == file.File.Compressed)
325 {
326 // these are mutually exclusive
327 attributes |= MsiInterop.MsidbFileAttributesNoncompressed;
328 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
329 }
330 else // not specified
331 {
332 Debug.Assert(YesNoType.NotSet == file.File.Compressed);
333
334 // clear any compression bits
335 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
336 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
337 }
338
339 recordUpdate.SetInteger(2, attributes);
340
341 view.Modify(ModifyView.Update, recordUpdate);
342 }
343 }
344 }
345
346 db.Commit();
347 }
348 }
349 }
350}
diff --git a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs
deleted file mode 100644
index dd7b85b7..00000000
--- a/src/WixToolset.Core/Bind/Databases/ProcessUncompressedFilesCommand.cs
+++ /dev/null
@@ -1,115 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Msi;
12 using WixToolset.Core.Native;
13
14 /// <summary>
15 /// Defines the file transfers necessary to layout the uncompressed files.
16 /// </summary>
17 internal class ProcessUncompressedFilesCommand : ICommand
18 {
19 public string DatabasePath { private get; set; }
20
21 public IEnumerable<FileFacade> FileFacades { private get; set; }
22
23 public RowDictionary<MediaRow> MediaRows { private get; set; }
24
25 public string LayoutDirectory { private get; set; }
26
27 public bool Compressed { private get; set; }
28
29 public bool LongNamesInImage { private get; set; }
30
31 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; }
32
33 public Table WixMediaTable { private get; set; }
34
35 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
36
37 public void Execute()
38 {
39 List<FileTransfer> fileTransfers = new List<FileTransfer>();
40
41 Hashtable directories = new Hashtable();
42
43 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
44
45 using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
46 {
47 using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
48 {
49 while (true)
50 {
51 using (Record directoryRecord = directoryView.Fetch())
52 {
53 if (null == directoryRecord)
54 {
55 break;
56 }
57
58 string sourceName = Installer.GetName(directoryRecord.GetString(3), true, this.LongNamesInImage);
59
60 directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName));
61 }
62 }
63 }
64
65 using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?"))
66 {
67 using (Record fileQueryRecord = new Record(1))
68 {
69 // for each file in the array of uncompressed files
70 foreach (FileFacade facade in this.FileFacades)
71 {
72 MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId);
73 string relativeFileLayoutPath = null;
74
75 WixMediaRow wixMediaRow = null;
76 string mediaLayoutFolder = null;
77
78 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow))
79 {
80 mediaLayoutFolder = wixMediaRow.Layout;
81 }
82
83 string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory);
84
85 // setup up the query record and find the appropriate file in the
86 // previously executed file view
87 fileQueryRecord[1] = facade.File.File;
88 fileView.Execute(fileQueryRecord);
89
90 using (Record fileRecord = fileView.Fetch())
91 {
92 if (null == fileRecord)
93 {
94 throw new WixException(WixErrors.FileIdentifierNotFound(facade.File.SourceLineNumbers, facade.File.File));
95 }
96
97 relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], this.Compressed, this.LongNamesInImage);
98 }
99
100 // finally put together the base media layout path and the relative file layout path
101 string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
102 FileTransfer transfer;
103 if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer))
104 {
105 fileTransfers.Add(transfer);
106 }
107 }
108 }
109 }
110 }
111
112 this.FileTransfers = fileTransfers;
113 }
114 }
115}
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs
deleted file mode 100644
index 9e17ee02..00000000
--- a/src/WixToolset.Core/Bind/Databases/UpdateControlTextCommand.cs
+++ /dev/null
@@ -1,80 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.IO;
7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9
10 internal class UpdateControlTextCommand : ICommand
11 {
12 public Table BBControlTable { private get; set; }
13
14 public Table WixBBControlTable { private get; set; }
15
16 public Table ControlTable { private get; set; }
17
18 public Table WixControlTable { private get; set; }
19
20 public void Execute()
21 {
22 if (null != this.WixBBControlTable)
23 {
24 RowDictionary<BBControlRow> bbControlRows = new RowDictionary<BBControlRow>(this.BBControlTable);
25 foreach (Row wixRow in this.WixBBControlTable.Rows)
26 {
27 BBControlRow bbControlRow = bbControlRows.Get(wixRow.GetPrimaryKey());
28 bbControlRow.Text = this.ReadTextFile(bbControlRow.SourceLineNumbers, wixRow.FieldAsString(2));
29 }
30 }
31
32 if (null != this.WixControlTable)
33 {
34 RowDictionary<ControlRow> controlRows = new RowDictionary<ControlRow>(this.ControlTable);
35 foreach (Row wixRow in this.WixControlTable.Rows)
36 {
37 ControlRow controlRow = controlRows.Get(wixRow.GetPrimaryKey());
38 controlRow.Text = this.ReadTextFile(controlRow.SourceLineNumbers, wixRow.FieldAsString(2));
39 }
40 }
41 }
42
43 /// <summary>
44 /// Reads a text file and returns the contents.
45 /// </summary>
46 /// <param name="sourceLineNumbers">Source line numbers for row from source.</param>
47 /// <param name="source">Source path to file to read.</param>
48 /// <returns>Text string read from file.</returns>
49 private string ReadTextFile(SourceLineNumber sourceLineNumbers, string source)
50 {
51 string text = null;
52
53 try
54 {
55 using (StreamReader reader = new StreamReader(source))
56 {
57 text = reader.ReadToEnd();
58 }
59 }
60 catch (DirectoryNotFoundException e)
61 {
62 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
63 }
64 catch (FileNotFoundException e)
65 {
66 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
67 }
68 catch (IOException e)
69 {
70 Messaging.Instance.OnMessage(WixErrors.BinderFileManagerMissingFile(sourceLineNumbers, e.Message));
71 }
72 catch (NotSupportedException)
73 {
74 Messaging.Instance.OnMessage(WixErrors.FileNotFound(sourceLineNumbers, source));
75 }
76
77 return text;
78 }
79 }
80}
diff --git a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs b/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
deleted file mode 100644
index 36818afa..00000000
--- a/src/WixToolset.Core/Bind/Databases/UpdateFileFacadesCommand.cs
+++ /dev/null
@@ -1,532 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind.Databases
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using System.Xml;
13 using System.Xml.XPath;
14 using WixToolset.Clr.Interop;
15 using WixToolset.Data;
16 using WixToolset.Data.Rows;
17 using WixToolset.Msi;
18
19 /// <summary>
20 /// Update file information.
21 /// </summary>
22 internal class UpdateFileFacadesCommand : ICommand
23 {
24 public IEnumerable<FileFacade> FileFacades { private get; set; }
25
26 public IEnumerable<FileFacade> UpdateFileFacades { private get; set; }
27
28 public string ModularizationGuid { private get; set; }
29
30 public Output Output { private get; set; }
31
32 public bool OverwriteHash { private get; set; }
33
34 public TableDefinitionCollection TableDefinitions { private get; set; }
35
36 public IDictionary<string, string> VariableCache { private get; set; }
37
38 public void Execute()
39 {
40 foreach (FileFacade file in this.UpdateFileFacades)
41 {
42 this.UpdateFileFacade(file);
43 }
44 }
45
46 private void UpdateFileFacade(FileFacade file)
47 {
48 FileInfo fileInfo = null;
49 try
50 {
51 fileInfo = new FileInfo(file.WixFile.Source);
52 }
53 catch (ArgumentException)
54 {
55 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
56 return;
57 }
58 catch (PathTooLongException)
59 {
60 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
61 return;
62 }
63 catch (NotSupportedException)
64 {
65 Messaging.Instance.OnMessage(WixErrors.InvalidFileName(file.File.SourceLineNumbers, file.WixFile.Source));
66 return;
67 }
68
69 if (!fileInfo.Exists)
70 {
71 Messaging.Instance.OnMessage(WixErrors.CannotFindFile(file.File.SourceLineNumbers, file.File.File, file.File.FileName, file.WixFile.Source));
72 return;
73 }
74
75 using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
76 {
77 if (Int32.MaxValue < fileStream.Length)
78 {
79 throw new WixException(WixErrors.FileTooLarge(file.File.SourceLineNumbers, file.WixFile.Source));
80 }
81
82 file.File.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture);
83 }
84
85 string version = null;
86 string language = null;
87 try
88 {
89 Installer.GetFileVersion(fileInfo.FullName, out version, out language);
90 }
91 catch (Win32Exception e)
92 {
93 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
94 {
95 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
96 }
97 else
98 {
99 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
100 }
101 }
102
103 // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install.
104 if (String.IsNullOrEmpty(version)) // unversioned files have their hashes added to the MsiFileHash table
105 {
106 if (!this.OverwriteHash)
107 {
108 // not overwriting hash, so don't do the rest of these options.
109 }
110 else if (null != file.File.Version)
111 {
112 // Search all of the file rows available to see if the specified version is actually a companion file. Yes, this looks
113 // very expensive and you're probably thinking it would be better to create an index of some sort to do an O(1) look up.
114 // That's a reasonable thought but companion file usage is usually pretty rare so we'd be doing something expensive (indexing
115 // all the file rows) for a relatively uncommon situation. Let's not do that.
116 //
117 // Also, if we do not find a matching file identifier then the user provided a default version and is providing a version
118 // for unversioned file. That's allowed but generally a dangerous thing to do so let's point that out to the user.
119 if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal)))
120 {
121 Messaging.Instance.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Version, file.File.File));
122 }
123 }
124 else
125 {
126 if (null != file.File.Language)
127 {
128 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
129 }
130
131 int[] hash;
132 try
133 {
134 Installer.GetFileHash(fileInfo.FullName, 0, out hash);
135 }
136 catch (Win32Exception e)
137 {
138 if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND
139 {
140 throw new WixException(WixErrors.FileNotFound(file.File.SourceLineNumbers, fileInfo.FullName));
141 }
142 else
143 {
144 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message));
145 }
146 }
147
148 if (null == file.Hash)
149 {
150 Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]);
151 file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers);
152 }
153
154 file.Hash[0] = file.File.File;
155 file.Hash[1] = 0;
156 file.Hash[2] = hash[0];
157 file.Hash[3] = hash[1];
158 file.Hash[4] = hash[2];
159 file.Hash[5] = hash[3];
160 }
161 }
162 else // update the file row with the version and language information.
163 {
164 // If no version was provided by the user, use the version from the file itself.
165 // This is the most common case.
166 if (String.IsNullOrEmpty(file.File.Version))
167 {
168 file.File.Version = version;
169 }
170 else if (!this.FileFacades.Any(r => file.File.Version.Equals(r.File.File, StringComparison.Ordinal))) // this looks expensive, but see explanation below.
171 {
172 // The user provided a default version for the file row so we looked for a companion file (a file row with Id matching
173 // the version value). We didn't find it so, we will override the default version they provided with the actual
174 // version from the file itself. Now, I know it looks expensive to search through all the file rows trying to match
175 // on the Id. However, the alternative is to build a big index of all file rows to do look ups. Since this case
176 // where the file version is already present is rare (companion files are pretty uncommon), we'll do the more
177 // CPU intensive search to save on the memory intensive index that wouldn't be used much.
178 //
179 // Also note this case can occur when the file is being updated using the WixBindUpdatedFiles extension mechanism.
180 // That's typically even more rare than companion files so again, no index, just search.
181 file.File.Version = version;
182 }
183
184 if (!String.IsNullOrEmpty(file.File.Language) && String.IsNullOrEmpty(language))
185 {
186 Messaging.Instance.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(file.File.SourceLineNumbers, file.File.Language, file.File.File));
187 }
188 else // override the default provided by the user (usually nothing) with the actual language from the file itself.
189 {
190 file.File.Language = language;
191 }
192
193 // Populate the binder variables for this file information if requested.
194 if (null != this.VariableCache)
195 {
196 if (!String.IsNullOrEmpty(file.File.Version))
197 {
198 string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File));
199 this.VariableCache[key] = file.File.Version;
200 }
201
202 if (!String.IsNullOrEmpty(file.File.Language))
203 {
204 string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", BindDatabaseCommand.Demodularize(this.Output.Type, ModularizationGuid, file.File.File));
205 this.VariableCache[key] = file.File.Language;
206 }
207 }
208 }
209
210 // If this is a CLR assembly, load the assembly and get the assembly name information
211 if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType)
212 {
213 bool targetNetfx1 = false;
214 StringDictionary assemblyNameValues = new StringDictionary();
215
216 ClrInterop.IReferenceIdentity referenceIdentity = null;
217 Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid;
218 uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity);
219 if (0 == result && null != referenceIdentity)
220 {
221 string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion");
222 if (null != imageRuntimeVersion)
223 {
224 targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase);
225 }
226
227 string culture = referenceIdentity.GetAttribute(null, "Culture") ?? "neutral";
228 assemblyNameValues.Add("Culture", culture);
229
230 string name = referenceIdentity.GetAttribute(null, "Name");
231 if (null != name)
232 {
233 assemblyNameValues.Add("Name", name);
234 }
235
236 string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture");
237 if (null != processorArchitecture)
238 {
239 assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture);
240 }
241
242 string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken");
243 if (null != publicKeyToken)
244 {
245 bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase));
246
247 // Managed code expects "null" instead of "neutral", and
248 // this won't be installed to the GAC since it's not signed anyway.
249 assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant());
250 assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken);
251 }
252 else if (file.WixFile.AssemblyApplication == null)
253 {
254 throw new WixException(WixErrors.GacAssemblyNoStrongName(file.File.SourceLineNumbers, fileInfo.FullName, file.File.Component));
255 }
256
257 string assemblyVersion = referenceIdentity.GetAttribute(null, "Version");
258 if (null != version)
259 {
260 assemblyNameValues.Add("Version", assemblyVersion);
261 }
262 }
263 else
264 {
265 Messaging.Instance.OnMessage(WixErrors.InvalidAssemblyFile(file.File.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result)));
266 return;
267 }
268
269 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
270 if (assemblyNameValues.ContainsKey("name"))
271 {
272 this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]);
273 }
274
275 if (!String.IsNullOrEmpty(version))
276 {
277 this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version);
278 }
279
280 if (assemblyNameValues.ContainsKey("version"))
281 {
282 string assemblyVersion = assemblyNameValues["version"];
283
284 if (!targetNetfx1)
285 {
286 // There is a bug in v1 fusion that requires the assembly's "version" attribute
287 // to be equal to or longer than the "fileVersion" in length when its present;
288 // the workaround is to prepend zeroes to the last version number in the assembly
289 // version.
290 if (null != version && version.Length > assemblyVersion.Length)
291 {
292 string padding = new string('0', version.Length - assemblyVersion.Length);
293 string[] assemblyVersionNumbers = assemblyVersion.Split('.');
294
295 if (assemblyVersionNumbers.Length > 0)
296 {
297 assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]);
298 assemblyVersion = String.Join(".", assemblyVersionNumbers);
299 }
300 }
301 }
302
303 this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion);
304 }
305
306 if (assemblyNameValues.ContainsKey("culture"))
307 {
308 this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]);
309 }
310
311 if (assemblyNameValues.ContainsKey("publicKeyToken"))
312 {
313 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]);
314 }
315
316 if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
317 {
318 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
319 }
320
321 if (assemblyNameValues.ContainsKey("processorArchitecture"))
322 {
323 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]);
324 }
325
326 // add the assembly name to the information cache
327 if (null != this.VariableCache)
328 {
329 string fileId = BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File);
330 string key = String.Concat("assemblyfullname.", fileId);
331 string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]);
332 if (assemblyNameValues.ContainsKey("processorArchitecture"))
333 {
334 assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]);
335 }
336
337 this.VariableCache[key] = assemblyName;
338
339 // Add entries with the preserved case publicKeyToken
340 string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId);
341 this.VariableCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]);
342
343 string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId);
344 this.VariableCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"];
345 }
346 }
347 else if (FileAssemblyType.Win32Assembly == file.WixFile.AssemblyType)
348 {
349 // TODO: Consider passing in the this.FileFacades as an indexed collection instead of searching through
350 // all files like this. Even though this is a rare case it looks like we might be able to index the
351 // file earlier.
352 FileFacade fileManifest = this.FileFacades.SingleOrDefault(r => r.File.File.Equals(file.WixFile.AssemblyManifest, StringComparison.Ordinal));
353 if (null == fileManifest)
354 {
355 Messaging.Instance.OnMessage(WixErrors.MissingManifestForWin32Assembly(file.File.SourceLineNumbers, file.File.File, file.WixFile.AssemblyManifest));
356 }
357
358 string win32Type = null;
359 string win32Name = null;
360 string win32Version = null;
361 string win32ProcessorArchitecture = null;
362 string win32PublicKeyToken = null;
363
364 // loading the dom is expensive we want more performant APIs than the DOM
365 // Navigator is cheaper than dom. Perhaps there is a cheaper API still.
366 try
367 {
368 XPathDocument doc = new XPathDocument(fileManifest.WixFile.Source);
369 XPathNavigator nav = doc.CreateNavigator();
370 nav.MoveToRoot();
371
372 // this assumes a particular schema for a win32 manifest and does not
373 // provide error checking if the file does not conform to schema.
374 // The fallback case here is that nothing is added to the MsiAssemblyName
375 // table for an out of tolerance Win32 manifest. Perhaps warnings needed.
376 if (nav.MoveToFirstChild())
377 {
378 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly")
379 {
380 nav.MoveToNext();
381 }
382
383 if (nav.MoveToFirstChild())
384 {
385 bool hasNextSibling = true;
386 while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling)
387 {
388 hasNextSibling = nav.MoveToNext();
389 }
390 if (!hasNextSibling)
391 {
392 Messaging.Instance.OnMessage(WixErrors.InvalidManifestContent(file.File.SourceLineNumbers, fileManifest.WixFile.Source));
393 return;
394 }
395
396 if (nav.MoveToAttribute("type", String.Empty))
397 {
398 win32Type = nav.Value;
399 nav.MoveToParent();
400 }
401
402 if (nav.MoveToAttribute("name", String.Empty))
403 {
404 win32Name = nav.Value;
405 nav.MoveToParent();
406 }
407
408 if (nav.MoveToAttribute("version", String.Empty))
409 {
410 win32Version = nav.Value;
411 nav.MoveToParent();
412 }
413
414 if (nav.MoveToAttribute("processorArchitecture", String.Empty))
415 {
416 win32ProcessorArchitecture = nav.Value;
417 nav.MoveToParent();
418 }
419
420 if (nav.MoveToAttribute("publicKeyToken", String.Empty))
421 {
422 win32PublicKeyToken = nav.Value;
423 nav.MoveToParent();
424 }
425 }
426 }
427 }
428 catch (FileNotFoundException fe)
429 {
430 Messaging.Instance.OnMessage(WixErrors.FileNotFound(new SourceLineNumber(fileManifest.WixFile.Source), fe.FileName, "AssemblyManifest"));
431 }
432 catch (XmlException xe)
433 {
434 Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message));
435 }
436
437 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
438 if (!String.IsNullOrEmpty(win32Name))
439 {
440 this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name);
441 }
442
443 if (!String.IsNullOrEmpty(win32Version))
444 {
445 this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version);
446 }
447
448 if (!String.IsNullOrEmpty(win32Type))
449 {
450 this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type);
451 }
452
453 if (!String.IsNullOrEmpty(win32ProcessorArchitecture))
454 {
455 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture);
456 }
457
458 if (!String.IsNullOrEmpty(win32PublicKeyToken))
459 {
460 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken);
461 }
462 }
463 }
464
465 /// <summary>
466 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
467 /// create a new row.
468 /// </summary>
469 /// <param name="assemblyNameTable">MsiAssemblyName table.</param>
470 /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
471 /// <param name="name">MsiAssemblyName name.</param>
472 /// <param name="value">MsiAssemblyName value.</param>
473 private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value)
474 {
475 // check for null value (this can occur when grabbing the file version from an assembly without one)
476 if (String.IsNullOrEmpty(value))
477 {
478 Messaging.Instance.OnMessage(WixWarnings.NullMsiAssemblyNameValue(file.File.SourceLineNumbers, file.File.Component, name));
479 }
480 else
481 {
482 Row assemblyNameRow = null;
483
484 // override directly authored value
485 foreach (Row row in assemblyNameTable.Rows)
486 {
487 if ((string)row[0] == file.File.Component && (string)row[1] == name)
488 {
489 assemblyNameRow = row;
490 break;
491 }
492 }
493
494 // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail.
495 if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType &&
496 String.IsNullOrEmpty(file.WixFile.AssemblyApplication) &&
497 !String.Equals(Path.GetFileNameWithoutExtension(file.File.LongFileName), value, StringComparison.OrdinalIgnoreCase))
498 {
499 Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value));
500 }
501
502 if (null == assemblyNameRow)
503 {
504 assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers);
505 assemblyNameRow[0] = file.File.Component;
506 assemblyNameRow[1] = name;
507 assemblyNameRow[2] = value;
508
509 // put the MsiAssemblyName row in the same section as the related File row
510 assemblyNameRow.SectionId = file.File.SectionId;
511
512 if (null == file.AssemblyNames)
513 {
514 file.AssemblyNames = new List<Row>();
515 }
516
517 file.AssemblyNames.Add(assemblyNameRow);
518 }
519 else
520 {
521 assemblyNameRow[2] = value;
522 }
523
524 if (this.VariableCache != null)
525 {
526 string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, BindDatabaseCommand.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant();
527 this.VariableCache[key] = (string)assemblyNameRow[2];
528 }
529 }
530 }
531 }
532}
diff --git a/src/WixToolset.Core/Bind/DelayedField.cs b/src/WixToolset.Core/Bind/DelayedField.cs
index 181ac3e3..6c56f27c 100644
--- a/src/WixToolset.Core/Bind/DelayedField.cs
+++ b/src/WixToolset.Core/Bind/DelayedField.cs
@@ -1,18 +1,15 @@
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. 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 2
3namespace WixToolset.Bind 3namespace WixToolset.Core.Bind
4{ 4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using System.Text;
9 using WixToolset.Data; 5 using WixToolset.Data;
6 using WixToolset.Extensibility;
10 7
11 /// <summary> 8 /// <summary>
12 /// Structure used to hold a row and field that contain binder variables, which need to be resolved 9 /// Structure used to hold a row and field that contain binder variables, which need to be resolved
13 /// later, once the files have been resolved. 10 /// later, once the files have been resolved.
14 /// </summary> 11 /// </summary>
15 internal class DelayedField 12 internal class DelayedField : IDelayedField
16 { 13 {
17 /// <summary> 14 /// <summary>
18 /// Basic constructor for struct 15 /// Basic constructor for struct
@@ -28,11 +25,11 @@ namespace WixToolset.Bind
28 /// <summary> 25 /// <summary>
29 /// The row containing the field. 26 /// The row containing the field.
30 /// </summary> 27 /// </summary>
31 public Row Row { get; private set; } 28 public Row Row { get; }
32 29
33 /// <summary> 30 /// <summary>
34 /// The field needing further resolving. 31 /// The field needing further resolving.
35 /// </summary> 32 /// </summary>
36 public Field Field { get; private set; } 33 public Field Field { get; }
37 } 34 }
38} 35}
diff --git a/src/WixToolset.Core/Bind/ExpectedExtractFile.cs b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs
new file mode 100644
index 00000000..fc2b43c7
--- /dev/null
+++ b/src/WixToolset.Core/Bind/ExpectedExtractFile.cs
@@ -0,0 +1,16 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using WixToolset.Extensibility;
7
8 internal class ExpectedExtractFile : IExpectedExtractFile
9 {
10 public Uri Uri { get; set; }
11
12 public int EmbeddedFileIndex { get; set; }
13
14 public string OutputPath { get; set; }
15 }
16}
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs
index 0ecd0096..28fc4817 100644
--- a/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs
+++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFiles.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Bind 3namespace WixToolset.Core.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -13,11 +13,11 @@ namespace WixToolset.Bind
13 /// <summary> 13 /// <summary>
14 /// Internal helper class used to extract embedded files. 14 /// Internal helper class used to extract embedded files.
15 /// </summary> 15 /// </summary>
16 internal sealed class ExtractEmbeddedFiles 16 internal class ExtractEmbeddedFiles
17 { 17 {
18 private Dictionary<Uri, SortedList<int, string>> filesWithEmbeddedFiles = new Dictionary<Uri, SortedList<int, string>>(); 18 private Dictionary<Uri, SortedList<int, string>> filesWithEmbeddedFiles = new Dictionary<Uri, SortedList<int, string>>();
19 19
20 public IEnumerable<Uri> Uris { get { return this.filesWithEmbeddedFiles.Keys; } } 20 public IEnumerable<Uri> Uris => this.filesWithEmbeddedFiles.Keys;
21 21
22 /// <summary> 22 /// <summary>
23 /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path. 23 /// Adds an embedded file index to track and returns the path where the embedded file will be extracted. Duplicates will return the same extract path.
@@ -53,15 +53,30 @@ namespace WixToolset.Bind
53 return extractPath; 53 return extractPath;
54 } 54 }
55 55
56 public IEnumerable<ExtractFile> GetExtractFilesForUri(Uri uri) 56 public IEnumerable<ExpectedExtractFile> GetExpectedEmbeddedFiles()
57 { 57 {
58 SortedList<int, string> extracts; 58 foreach (var uriWithExtracts in filesWithEmbeddedFiles)
59 if (!filesWithEmbeddedFiles.TryGetValue(uri, out extracts)) 59 {
60 foreach (var extracts in uriWithExtracts.Value)
61 {
62 yield return new ExpectedExtractFile
63 {
64 Uri = uriWithExtracts.Key,
65 EmbeddedFileIndex = extracts.Key,
66 OutputPath = extracts.Value,
67 };
68 }
69 }
70 }
71
72 public IEnumerable<ExpectedExtractFile> GetExtractFilesForUri(Uri uri)
73 {
74 if (!filesWithEmbeddedFiles.TryGetValue(uri, out var extracts))
60 { 75 {
61 extracts = new SortedList<int, string>(); 76 extracts = new SortedList<int, string>();
62 } 77 }
63 78
64 return extracts.Select(e => new ExtractFile() { EmbeddedFileIndex = e.Key, OutputPath = e.Value }); 79 return extracts.Select(e => new ExpectedExtractFile() { Uri = uri, EmbeddedFileIndex = e.Key, OutputPath = e.Value });
65 } 80 }
66 81
67 private string HashUri(string uri) 82 private string HashUri(string uri)
@@ -72,12 +87,5 @@ namespace WixToolset.Bind
72 return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_'); 87 return Convert.ToBase64String(hash).TrimEnd('=').Replace('+', '-').Replace('/', '_');
73 } 88 }
74 } 89 }
75
76 internal struct ExtractFile
77 {
78 public int EmbeddedFileIndex { get; set; }
79
80 public string OutputPath { get; set; }
81 }
82 } 90 }
83} 91}
diff --git a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs
index 68bfd8d7..7de40fb8 100644
--- a/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs
+++ b/src/WixToolset.Core/Bind/ExtractEmbeddedFilesCommand.cs
@@ -1,19 +1,26 @@
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. 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 2
3namespace WixToolset.Bind 3namespace WixToolset.Core.Bind
4{ 4{
5 using System.Collections.Generic;
5 using System.IO; 6 using System.IO;
7 using System.Linq;
6 using System.Reflection; 8 using System.Reflection;
7 using WixToolset.Data; 9 using WixToolset.Data;
10 using WixToolset.Extensibility;
8 11
9 internal class ExtractEmbeddedFilesCommand : ICommand 12 public class ExtractEmbeddedFilesCommand
10 { 13 {
11 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } 14 public IEnumerable<IExpectedExtractFile> FilesWithEmbeddedFiles { private get; set; }
12 15
13 public void Execute() 16 public void Execute()
14 { 17 {
15 foreach (var baseUri in this.FilesWithEmbeddedFiles.Uris) 18 var group = this.FilesWithEmbeddedFiles.GroupBy(e => e.Uri);
19
20 foreach (var expectedEmbeddedFileByUri in group)
16 { 21 {
22 var baseUri = expectedEmbeddedFileByUri.Key;
23
17 Stream stream = null; 24 Stream stream = null;
18 try 25 try
19 { 26 {
@@ -34,18 +41,20 @@ namespace WixToolset.Bind
34 41
35 using (FileStructure fs = FileStructure.Read(stream)) 42 using (FileStructure fs = FileStructure.Read(stream))
36 { 43 {
37 foreach (var embeddedFile in this.FilesWithEmbeddedFiles.GetExtractFilesForUri(baseUri)) 44 var uniqueIndicies = new SortedSet<int>();
45
46 foreach (var embeddedFile in expectedEmbeddedFileByUri)
38 { 47 {
39 fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath); 48 if (uniqueIndicies.Add(embeddedFile.EmbeddedFileIndex))
49 {
50 fs.ExtractEmbeddedFile(embeddedFile.EmbeddedFileIndex, embeddedFile.OutputPath);
51 }
40 } 52 }
41 } 53 }
42 } 54 }
43 finally 55 finally
44 { 56 {
45 if (null != stream) 57 stream?.Close();
46 {
47 stream.Close();
48 }
49 } 58 }
50 } 59 }
51 } 60 }
diff --git a/src/WixToolset.Core/Bind/Databases/FileFacade.cs b/src/WixToolset.Core/Bind/FileFacade.cs
index 37115c97..aaa6b7d3 100644
--- a/src/WixToolset.Core/Bind/Databases/FileFacade.cs
+++ b/src/WixToolset.Core/Bind/FileFacade.cs
@@ -1,6 +1,6 @@
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. 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 2
3namespace WixToolset.Bind.Databases 3namespace WixToolset.Core.Bind
4{ 4{
5 using System.Collections.Generic; 5 using System.Collections.Generic;
6 using WixToolset.Data; 6 using WixToolset.Data;
diff --git a/src/WixToolset.Core/Bind/FileResolver.cs b/src/WixToolset.Core/Bind/FileResolver.cs
new file mode 100644
index 00000000..8d624e6f
--- /dev/null
+++ b/src/WixToolset.Core/Bind/FileResolver.cs
@@ -0,0 +1,231 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Runtime.InteropServices;
10 using WixToolset.Data;
11 using WixToolset.Data.Bind;
12 using WixToolset.Extensibility;
13
14 internal class FileResolver
15 {
16 private const string BindPathOpenString = "!(bindpath.";
17
18 private FileResolver(IEnumerable<BindPath> bindPaths)
19 {
20 this.BindPaths = (bindPaths ?? Array.Empty<BindPath>()).ToLookup(b => b.Stage);
21 this.RebaseTarget = this.BindPaths[BindStage.Target].Any();
22 this.RebaseUpdated = this.BindPaths[BindStage.Updated].Any();
23 }
24
25 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<IBinderExtension> extensions) : this(bindPaths)
26 {
27 this.BinderExtensions = extensions ?? Array.Empty<IBinderExtension>();
28 }
29
30 public FileResolver(IEnumerable<BindPath> bindPaths, IEnumerable<ILibrarianExtension> extensions) : this(bindPaths)
31 {
32 this.LibrarianExtensions = extensions ?? Array.Empty<ILibrarianExtension>();
33 }
34
35 private ILookup<BindStage, BindPath> BindPaths { get; }
36
37 public bool RebaseTarget { get; }
38
39 public bool RebaseUpdated { get; }
40
41 private IEnumerable<IBinderExtension> BinderExtensions { get; }
42
43 private IEnumerable<ILibrarianExtension> LibrarianExtensions { get; }
44
45 /// <summary>
46 /// Copies a file.
47 /// </summary>
48 /// <param name="source">The file to copy.</param>
49 /// <param name="destination">The destination file.</param>
50 /// <param name="overwrite">true if the destination file can be overwritten; otherwise, false.</param>
51 public bool CopyFile(string source, string destination, bool overwrite)
52 {
53 foreach (var extension in this.BinderExtensions)
54 {
55 if (extension.CopyFile(source, destination, overwrite))
56 {
57 return true;
58 }
59 }
60
61 if (overwrite && File.Exists(destination))
62 {
63 File.Delete(destination);
64 }
65
66 if (!CreateHardLink(destination, source, IntPtr.Zero))
67 {
68#if DEBUG
69 int er = Marshal.GetLastWin32Error();
70#endif
71
72 File.Copy(source, destination, overwrite);
73 }
74
75 return true;
76 }
77
78 /// <summary>
79 /// Moves a file.
80 /// </summary>
81 /// <param name="source">The file to move.</param>
82 /// <param name="destination">The destination file.</param>
83 public bool MoveFile(string source, string destination, bool overwrite)
84 {
85 foreach (var extension in this.BinderExtensions)
86 {
87 if (extension.MoveFile(source, destination, overwrite))
88 {
89 return true;
90 }
91 }
92
93 if (overwrite && File.Exists(destination))
94 {
95 File.Delete(destination);
96 }
97
98 var directory = Path.GetDirectoryName(destination);
99 if (!String.IsNullOrEmpty(directory))
100 {
101 Directory.CreateDirectory(directory);
102 }
103
104 File.Move(source, destination);
105
106 return true;
107 }
108
109 public string Resolve(SourceLineNumber sourceLineNumbers, string table, string path)
110 {
111 foreach (var extension in this.LibrarianExtensions)
112 {
113 var resolved = extension.Resolve(sourceLineNumbers, table, path);
114
115 if (null != resolved)
116 {
117 return resolved;
118 }
119 }
120
121 return this.ResolveUsingBindPaths(path, table, sourceLineNumbers, BindStage.Normal);
122 }
123
124 /// <summary>
125 /// Resolves the source path of a file using binder extensions.
126 /// </summary>
127 /// <param name="source">Original source value.</param>
128 /// <param name="type">Optional type of source file being resolved.</param>
129 /// <param name="sourceLineNumbers">Optional source line of source file being resolved.</param>
130 /// <param name="bindStage">The binding stage used to determine what collection of bind paths will be used</param>
131 /// <returns>Should return a valid path for the stream to be imported.</returns>
132 public string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
133 {
134 foreach (var extension in this.BinderExtensions)
135 {
136 var resolved = extension.ResolveFile(source, type, sourceLineNumbers, bindStage);
137
138 if (null != resolved)
139 {
140 return resolved;
141 }
142 }
143
144 return this.ResolveUsingBindPaths(source, type, sourceLineNumbers, bindStage);
145 }
146
147 private string ResolveUsingBindPaths(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage)
148 {
149 string resolved = null;
150
151 // If the file exists, we're good to go.
152 if (CheckFileExists(source))
153 {
154 resolved = source;
155 }
156 else if (Path.IsPathRooted(source)) // path is rooted so bindpaths won't help, bail since the file apparently doesn't exist.
157 {
158 resolved = null;
159 }
160 else // not a rooted path so let's try applying all the different source resolution options.
161 {
162 string bindName = String.Empty;
163 var path = source;
164 string pathWithoutSourceDir = null;
165
166 if (source.StartsWith(BindPathOpenString, StringComparison.Ordinal))
167 {
168 int closeParen = source.IndexOf(')', BindPathOpenString.Length);
169 if (-1 != closeParen)
170 {
171 bindName = source.Substring(BindPathOpenString.Length, closeParen - BindPathOpenString.Length);
172 path = source.Substring(BindPathOpenString.Length + bindName.Length + 1); // +1 for the closing brace.
173 path = path.TrimStart('\\'); // remove starting '\\' char so the path doesn't look rooted.
174 }
175 }
176 else if (source.StartsWith("SourceDir\\", StringComparison.Ordinal) || source.StartsWith("SourceDir/", StringComparison.Ordinal))
177 {
178 pathWithoutSourceDir = path.Substring(10);
179 }
180
181 var bindPaths = this.BindPaths[bindStage];
182
183 foreach (var bindPath in bindPaths)
184 {
185 if (!String.IsNullOrEmpty(pathWithoutSourceDir))
186 {
187 var filePath = Path.Combine(bindPath.Path, pathWithoutSourceDir);
188
189 if (CheckFileExists(filePath))
190 {
191 resolved = filePath;
192 }
193 }
194
195 if (String.IsNullOrEmpty(resolved))
196 {
197 var filePath = Path.Combine(bindPath.Path, path);
198
199 if (CheckFileExists(filePath))
200 {
201 resolved = filePath;
202 }
203 }
204 }
205 }
206
207 if (null == resolved)
208 {
209 throw new WixFileNotFoundException(sourceLineNumbers, source, type);
210 }
211
212 // Didn't find the file.
213 return resolved;
214 }
215
216 private static bool CheckFileExists(string path)
217 {
218 try
219 {
220 return File.Exists(path);
221 }
222 catch (ArgumentException)
223 {
224 throw new WixException(WixErrors.IllegalCharactersInPath(path));
225 }
226 }
227
228 [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
229 private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
230 }
231}
diff --git a/src/WixToolset.Core/Bind/FileTransfer.cs b/src/WixToolset.Core/Bind/FileTransfer.cs
deleted file mode 100644
index 64bbc5f1..00000000
--- a/src/WixToolset.Core/Bind/FileTransfer.cs
+++ /dev/null
@@ -1,113 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind
4{
5 using System;
6 using System.IO;
7 using WixToolset;
8 using WixToolset.Data;
9
10 /// <summary>
11 /// Structure used for all file transfer information.
12 /// </summary>
13 internal class FileTransfer
14 {
15 /// <summary>Source path to file.</summary>
16 public string Source { get; set; }
17
18 /// <summary>Destination path for file.</summary>
19 public string Destination { get; set; }
20
21 /// <summary>Flag if file should be moved (optimal).</summary>
22 public bool Move { get; set; }
23
24 /// <summary>Optional source line numbers where this file transfer orginated.</summary>
25 public SourceLineNumber SourceLineNumbers { get; set; }
26
27 /// <summary>Optional type of file this transfer is moving or copying.</summary>
28 public string Type { get; set; }
29
30 /// <summary>Indicates whether the file transer was a built by this build or copied from other some build.</summary>
31 internal bool Built { get; set; }
32
33 /// <summary>Set during layout of media when the file transfer when the source and target resolve to the same path.</summary>
34 internal bool Redundant { get; set; }
35
36 /// <summary>
37 /// Prefer the TryCreate() method to create FileTransfer objects.
38 /// </summary>
39 /// <param name="source">Source path to file.</param>
40 /// <param name="destination">Destination path for file.</param>
41 /// <param name="move">File if file should be moved (optimal).</param>
42 /// <param name="type">Optional type of file this transfer is transferring.</param>
43 /// <param name="sourceLineNumbers">Optional source line numbers wher this transfer originated.</param>
44 public FileTransfer(string source, string destination, bool move, string type = null, SourceLineNumber sourceLineNumbers = null)
45 {
46 this.Source = source;
47 this.Destination = destination;
48 this.Move = move;
49
50 this.Type = type;
51 this.SourceLineNumbers = sourceLineNumbers;
52 }
53
54 /// <summary>
55 /// Creates a file transfer if the source and destination are different.
56 /// </summary>
57 /// <param name="source">Source path to file.</param>
58 /// <param name="destination">Destination path for file.</param>
59 /// <param name="move">File if file should be moved (optimal).</param>
60 /// <param name="type">Optional type of file this transfer is transferring.</param>
61 /// <param name="sourceLineNumbers">Optional source line numbers wher this transfer originated.</param>
62 /// <returns>true if the source and destination are the different, false if no file transfer is created.</returns>
63 public static bool TryCreate(string source, string destination, bool move, string type, SourceLineNumber sourceLineNumbers, out FileTransfer transfer)
64 {
65 string sourceFullPath = GetValidatedFullPath(sourceLineNumbers, source);
66
67 string fileLayoutFullPath = GetValidatedFullPath(sourceLineNumbers, destination);
68
69 // if the current source path (where we know that the file already exists) and the resolved
70 // path as dictated by the Directory table are not the same, then propagate the file. The
71 // image that we create may have already been done by some other process other than the linker, so
72 // there is no reason to copy the files to the resolved source if they are already there.
73 if (String.Equals(sourceFullPath, fileLayoutFullPath, StringComparison.OrdinalIgnoreCase))
74 {
75 transfer = null;
76 return false;
77 }
78
79 transfer = new FileTransfer(source, destination, move, type, sourceLineNumbers);
80 return true;
81 }
82
83 private static string GetValidatedFullPath(SourceLineNumber sourceLineNumbers, string path)
84 {
85 string result;
86
87 try
88 {
89 result = Path.GetFullPath(path);
90
91 string filename = Path.GetFileName(result);
92
93 foreach (string reservedName in Common.ReservedFileNames)
94 {
95 if (reservedName.Equals(filename, StringComparison.OrdinalIgnoreCase))
96 {
97 throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path));
98 }
99 }
100 }
101 catch (System.ArgumentException)
102 {
103 throw new WixException(WixErrors.InvalidFileName(sourceLineNumbers, path));
104 }
105 catch (System.IO.PathTooLongException)
106 {
107 throw new WixException(WixErrors.PathTooLong(sourceLineNumbers, path));
108 }
109
110 return result;
111 }
112 }
113}
diff --git a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs
deleted file mode 100644
index fdf1ab32..00000000
--- a/src/WixToolset.Core/Bind/GenerateDatabaseCommand.cs
+++ /dev/null
@@ -1,335 +0,0 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Text;
11 using WixToolset.Data;
12 using WixToolset.Extensibility;
13 using WixToolset.Msi;
14 using WixToolset.Core.Native;
15
16 internal class GenerateDatabaseCommand : ICommand
17 {
18 public int Codepage { private get; set; }
19
20 public IEnumerable<IBinderExtension> Extensions { private get; set; }
21
22 public IEnumerable<IBinderFileManager> FileManagers { private get; set; }
23
24 /// <summary>
25 /// Whether to keep columns added in a transform.
26 /// </summary>
27 public bool KeepAddedColumns { private get; set; }
28
29 public Output Output { private get; set; }
30
31 public string OutputPath { private get; set; }
32
33 public TableDefinitionCollection TableDefinitions { private get; set; }
34
35 public string TempFilesLocation { private get; set; }
36
37 /// <summary>
38 /// Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.
39 /// </summary>
40 public bool SuppressAddingValidationRows { private get; set; }
41
42 public bool UseSubDirectory { private get; set; }
43
44 public void Execute()
45 {
46 // Add the _Validation rows.
47 if (!this.SuppressAddingValidationRows)
48 {
49 Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]);
50
51 foreach (Table table in this.Output.Tables)
52 {
53 if (!table.Definition.Unreal)
54 {
55 // Add the validation rows for this table.
56 table.Definition.AddValidationRows(validationTable);
57 }
58 }
59 }
60
61 // Set the base directory.
62 string baseDirectory = this.TempFilesLocation;
63
64 if (this.UseSubDirectory)
65 {
66 string filename = Path.GetFileNameWithoutExtension(this.OutputPath);
67 baseDirectory = Path.Combine(baseDirectory, filename);
68
69 // make sure the directory exists
70 Directory.CreateDirectory(baseDirectory);
71 }
72
73 try
74 {
75 OpenDatabase type = OpenDatabase.CreateDirect;
76
77 // set special flag for patch files
78 if (OutputType.Patch == this.Output.Type)
79 {
80 type |= OpenDatabase.OpenPatchFile;
81 }
82
83#if DEBUG
84 Console.WriteLine("Opening database at: {0}", this.OutputPath);
85#endif
86
87 using (Database db = new Database(this.OutputPath, type))
88 {
89 // Localize the codepage if a value was specified directly.
90 if (-1 != this.Codepage)
91 {
92 this.Output.Codepage = this.Codepage;
93 }
94
95 // if we're not using the default codepage, import a new one into our
96 // database before we add any tables (or the tables would be added
97 // with the wrong codepage).
98 if (0 != this.Output.Codepage)
99 {
100 this.SetDatabaseCodepage(db, this.Output.Codepage);
101 }
102
103 foreach (Table table in this.Output.Tables)
104 {
105 Table importTable = table;
106 bool hasBinaryColumn = false;
107
108 // Skip all unreal tables other than _Streams.
109 if (table.Definition.Unreal && "_Streams" != table.Name)
110 {
111 continue;
112 }
113
114 // Do not put the _Validation table in patches, it is not needed.
115 if (OutputType.Patch == this.Output.Type && "_Validation" == table.Name)
116 {
117 continue;
118 }
119
120 // The only way to import binary data is to copy it to a local subdirectory first.
121 // To avoid this extra copying and perf hit, import an empty table with the same
122 // definition and later import the binary data from source using records.
123 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
124 {
125 if (ColumnType.Object == columnDefinition.Type)
126 {
127 importTable = new Table(table.Section, table.Definition);
128 hasBinaryColumn = true;
129 break;
130 }
131 }
132
133 // Create the table via IDT import.
134 if ("_Streams" != importTable.Name)
135 {
136 try
137 {
138 db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns);
139 }
140 catch (WixInvalidIdtException)
141 {
142 // If ValidateRows finds anything it doesn't like, it throws
143 importTable.ValidateRows();
144
145 // Otherwise we rethrow the InvalidIdt
146 throw;
147 }
148 }
149
150 // insert the rows via SQL query if this table contains object fields
151 if (hasBinaryColumn)
152 {
153 StringBuilder query = new StringBuilder("SELECT ");
154
155 // Build the query for the view.
156 bool firstColumn = true;
157 foreach (ColumnDefinition columnDefinition in table.Definition.Columns)
158 {
159 if (!firstColumn)
160 {
161 query.Append(",");
162 }
163
164 query.AppendFormat(" `{0}`", columnDefinition.Name);
165 firstColumn = false;
166 }
167 query.AppendFormat(" FROM `{0}`", table.Name);
168
169 using (View tableView = db.OpenExecuteView(query.ToString()))
170 {
171 // Import each row containing a stream
172 foreach (Row row in table.Rows)
173 {
174 using (Record record = new Record(table.Definition.Columns.Count))
175 {
176 StringBuilder streamName = new StringBuilder();
177 bool needStream = false;
178
179 // the _Streams table doesn't prepend the table name (or a period)
180 if ("_Streams" != table.Name)
181 {
182 streamName.Append(table.Name);
183 }
184
185 for (int i = 0; i < table.Definition.Columns.Count; i++)
186 {
187 ColumnDefinition columnDefinition = table.Definition.Columns[i];
188
189 switch (columnDefinition.Type)
190 {
191 case ColumnType.Localized:
192 case ColumnType.Preserved:
193 case ColumnType.String:
194 if (columnDefinition.PrimaryKey)
195 {
196 if (0 < streamName.Length)
197 {
198 streamName.Append(".");
199 }
200 streamName.Append((string)row[i]);
201 }
202
203 record.SetString(i + 1, (string)row[i]);
204 break;
205 case ColumnType.Number:
206 record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture));
207 break;
208 case ColumnType.Object:
209 if (null != row[i])
210 {
211 needStream = true;
212 try
213 {
214 record.SetStream(i + 1, (string)row[i]);
215 }
216 catch (Win32Exception e)
217 {
218 if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME
219 {
220 throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i]));
221 }
222 else
223 {
224 throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message));
225 }
226 }
227 }
228 break;
229 }
230 }
231
232 // stream names are created by concatenating the name of the table with the values
233 // of the primary key (delimited by periods)
234 // check for a stream name that is more than 62 characters long (the maximum allowed length)
235 if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length)
236 {
237 Messaging.Instance.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length));
238 }
239 else // add the row to the database
240 {
241 tableView.Modify(ModifyView.Assign, record);
242 }
243 }
244 }
245 }
246
247 // Remove rows from the _Streams table for wixpdbs.
248 if ("_Streams" == table.Name)
249 {
250 table.Rows.Clear();
251 }
252 }
253 }
254
255 // Insert substorages (usually transforms inside a patch or instance transforms in a package).
256 if (0 < this.Output.SubStorages.Count)
257 {
258 using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`"))
259 {
260 foreach (SubStorage subStorage in this.Output.SubStorages)
261 {
262 string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst"));
263
264 // Bind the transform.
265 this.BindTransform(subStorage.Data, transformFile);
266
267 if (Messaging.Instance.EncounteredError)
268 {
269 continue;
270 }
271
272 // add the storage
273 using (Record record = new Record(2))
274 {
275 record.SetString(1, subStorage.Name);
276 record.SetStream(2, transformFile);
277 storagesView.Modify(ModifyView.Assign, record);
278 }
279 }
280 }
281 }
282
283 // We're good, commit the changes to the new database.
284 db.Commit();
285 }
286 }
287 catch (IOException)
288 {
289 // TODO: this error message doesn't seem specific enough
290 throw new WixFileNotFoundException(new SourceLineNumber(this.OutputPath), this.OutputPath);
291 }
292 }
293
294 private void BindTransform(Output transform, string outputPath)
295 {
296 BindTransformCommand command = new BindTransformCommand();
297 command.Extensions = this.Extensions;
298 command.FileManagers = this.FileManagers;
299 command.TempFilesLocation = this.TempFilesLocation;
300 command.Transform = transform;
301 command.OutputPath = outputPath;
302 command.TableDefinitions = this.TableDefinitions;
303 command.Execute();
304 }
305
306 /// <summary>
307 /// Sets the codepage of a database.
308 /// </summary>
309 /// <param name="db">Database to set codepage into.</param>
310 /// <param name="output">Output with the codepage for the database.</param>
311 private void SetDatabaseCodepage(Database db, int codepage)
312 {
313 // write out the _ForceCodepage IDT file
314 string idtPath = Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt");
315 using (StreamWriter idtFile = new StreamWriter(idtPath, false, Encoding.ASCII))
316 {
317 idtFile.WriteLine(); // dummy column name record
318 idtFile.WriteLine(); // dummy column definition record
319 idtFile.Write(codepage);
320 idtFile.WriteLine("\t_ForceCodepage");
321 }
322
323 // try to import the table into the MSI
324 try
325 {
326 db.Import(idtPath);
327 }
328 catch (WixInvalidIdtException)
329 {
330 // the IDT should be valid, so an invalid code page was given
331 throw new WixException(WixErrors.IllegalCodepage(codepage));
332 }
333 }
334 }
335}
diff --git a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs
index 4ffe9e82..15365c2a 100644
--- a/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs
+++ b/src/WixToolset.Core/Bind/ResolveDelayedFieldsCommand.cs
@@ -1,23 +1,22 @@
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. 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 2
3namespace WixToolset.Bind 3namespace WixToolset.Core.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Globalization; 7 using System.Globalization;
8 using System.Linq;
9 using System.Text;
10 using WixToolset.Data; 8 using WixToolset.Data;
9 using WixToolset.Extensibility;
11 10
12 /// <summary> 11 /// <summary>
13 /// Resolves the fields which had variables that needed to be resolved after the file information 12 /// Resolves the fields which had variables that needed to be resolved after the file information
14 /// was loaded. 13 /// was loaded.
15 /// </summary> 14 /// </summary>
16 internal class ResolveDelayedFieldsCommand : ICommand 15 public class ResolveDelayedFieldsCommand : ICommand
17 { 16 {
18 public OutputType OutputType { private get; set;} 17 public OutputType OutputType { private get; set;}
19 18
20 public IEnumerable<DelayedField> DelayedFields { private get; set;} 19 public IEnumerable<IDelayedField> DelayedFields { private get; set;}
21 20
22 public IDictionary<string, string> VariableCache { private get; set; } 21 public IDictionary<string, string> VariableCache { private get; set; }
23 22
@@ -29,9 +28,9 @@ namespace WixToolset.Bind
29 /// <param name="modularizationGuid">The modularization guid (used in case of a merge module).</param> 28 /// <param name="modularizationGuid">The modularization guid (used in case of a merge module).</param>
30 public void Execute() 29 public void Execute()
31 { 30 {
32 List<DelayedField> deferredFields = new List<DelayedField>(); 31 var deferredFields = new List<IDelayedField>();
33 32
34 foreach (DelayedField delayedField in this.DelayedFields) 33 foreach (IDelayedField delayedField in this.DelayedFields)
35 { 34 {
36 try 35 try
37 { 36 {
@@ -43,7 +42,7 @@ namespace WixToolset.Bind
43 string value = WixVariableResolver.ResolveDelayedVariables(propertyRow.SourceLineNumbers, (string)delayedField.Field.Data, this.VariableCache); 42 string value = WixVariableResolver.ResolveDelayedVariables(propertyRow.SourceLineNumbers, (string)delayedField.Field.Data, this.VariableCache);
44 43
45 // update the variable cache with the new value 44 // update the variable cache with the new value
46 string key = String.Concat("property.", BindDatabaseCommand.Demodularize(this.OutputType, this.ModularizationGuid, (string)propertyRow[0])); 45 string key = String.Concat("property.", Common.Demodularize(this.OutputType, this.ModularizationGuid, (string)propertyRow[0]));
47 this.VariableCache[key] = value; 46 this.VariableCache[key] = value;
48 47
49 // update the field data 48 // update the field data
diff --git a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
index 4caec9b4..f4f4f9e8 100644
--- a/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
+++ b/src/WixToolset.Core/Bind/ResolveFieldsCommand.cs
@@ -1,29 +1,32 @@
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. 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 2
3namespace WixToolset.Bind 3namespace WixToolset.Core.Bind
4{ 4{
5 using System.Collections.Generic; 5 using System.Collections.Generic;
6 using WixToolset.Data; 6 using WixToolset.Data;
7 using WixToolset.Data.Bind;
7 using WixToolset.Extensibility; 8 using WixToolset.Extensibility;
8 9
9 /// <summary> 10 /// <summary>
10 /// Resolve source fields in the tables included in the output 11 /// Resolve source fields in the tables included in the output
11 /// </summary> 12 /// </summary>
12 internal class ResolveFieldsCommand : ICommand 13 internal class ResolveFieldsCommand
13 { 14 {
14 public TableIndexedCollection Tables { private get; set; } 15 public bool BuildingPatch { private get; set; }
15 16
16 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; } 17 public IBindVariableResolver BindVariableResolver { private get; set; }
17 18
18 public BinderFileManagerCore FileManagerCore { private get; set; } 19 public IEnumerable<BindPath> BindPaths { private get; set; }
19 20
20 public IEnumerable<IBinderFileManager> FileManagers { private get; set; } 21 public IEnumerable<IBinderExtension> Extensions { private get; set; }
21 22
22 public bool SupportDelayedResolution { private get; set; } 23 public ExtractEmbeddedFiles FilesWithEmbeddedFiles { private get; set; }
24
25 public string IntermediateFolder { private get; set; }
23 26
24 public string TempFilesLocation { private get; set; } 27 public TableIndexedCollection Tables { private get; set; }
25 28
26 public WixVariableResolver WixVariableResolver { private get; set; } 29 public bool SupportDelayedResolution { private get; set; }
27 30
28 public IEnumerable<DelayedField> DelayedFields { get; private set; } 31 public IEnumerable<DelayedField> DelayedFields { get; private set; }
29 32
@@ -31,6 +34,8 @@ namespace WixToolset.Bind
31 { 34 {
32 List<DelayedField> delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null; 35 List<DelayedField> delayedFields = this.SupportDelayedResolution ? new List<DelayedField>() : null;
33 36
37 var fileResolver = new FileResolver(this.BindPaths, this.Extensions);
38
34 foreach (Table table in this.Tables) 39 foreach (Table table in this.Tables)
35 { 40 {
36 foreach (Row row in table.Rows) 41 foreach (Row row in table.Rows)
@@ -46,7 +51,7 @@ namespace WixToolset.Bind
46 // resolve localization and wix variables 51 // resolve localization and wix variables
47 if (field.Data is string) 52 if (field.Data is string)
48 { 53 {
49 field.Data = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, field.AsString(), false, ref isDefault, ref delayedResolve); 54 field.Data = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, field.AsString(), false, out isDefault, out delayedResolve);
50 if (delayedResolve) 55 if (delayedResolve)
51 { 56 {
52 delayedFields.Add(new DelayedField(row, field)); 57 delayedFields.Add(new DelayedField(row, field));
@@ -74,7 +79,7 @@ namespace WixToolset.Bind
74 // File is embedded and path to it was not modified above. 79 // File is embedded and path to it was not modified above.
75 if (objectField.EmbeddedFileIndex.HasValue && isDefault) 80 if (objectField.EmbeddedFileIndex.HasValue && isDefault)
76 { 81 {
77 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.TempFilesLocation); 82 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.BaseUri, objectField.EmbeddedFileIndex.Value, this.IntermediateFolder);
78 83
79 // Set the path to the embedded file once where it will be extracted. 84 // Set the path to the embedded file once where it will be extracted.
80 objectField.Data = extractPath; 85 objectField.Data = extractPath;
@@ -83,7 +88,7 @@ namespace WixToolset.Bind
83 { 88 {
84 try 89 try
85 { 90 {
86 if (OutputType.Patch != this.FileManagerCore.Output.Type) // Normal binding for non-Patch scenario such as link (light.exe) 91 if (!this.BuildingPatch) // Normal binding for non-Patch scenario such as link (light.exe)
87 { 92 {
88 // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file 93 // keep a copy of the un-resolved data for future replay. This will be saved into wixpdb file
89 if (null == objectField.UnresolvedData) 94 if (null == objectField.UnresolvedData)
@@ -92,12 +97,12 @@ namespace WixToolset.Bind
92 } 97 }
93 98
94 // resolve the path to the file 99 // resolve the path to the file
95 objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); 100 objectField.Data = fileResolver.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal);
96 } 101 }
97 else if (!(this.FileManagerCore.RebaseTarget || this.FileManagerCore.RebaseUpdated)) // Normal binding for Patch Scenario (normal patch, no re-basing logic) 102 else if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated) // Normal binding for Patch Scenario (normal patch, no re-basing logic)
98 { 103 {
99 // resolve the path to the file 104 // resolve the path to the file
100 objectField.Data = this.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal); 105 objectField.Data = fileResolver.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal);
101 } 106 }
102 else // Re-base binding path scenario caused by pyro.exe -bt -bu 107 else // Re-base binding path scenario caused by pyro.exe -bt -bu
103 { 108 {
@@ -106,7 +111,7 @@ namespace WixToolset.Bind
106 111
107 // if -bu is used in pyro command, this condition holds true and the tool 112 // if -bu is used in pyro command, this condition holds true and the tool
108 // will use pre-resolved source for new wixpdb file 113 // will use pre-resolved source for new wixpdb file
109 if (this.FileManagerCore.RebaseUpdated) 114 if (fileResolver.RebaseUpdated)
110 { 115 {
111 // try to use the unResolved Source if it exists. 116 // try to use the unResolved Source if it exists.
112 // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll 117 // New version of wixpdb file keeps a copy of pre-resolved Source. i.e. !(bindpath.test)\foo.dll
@@ -117,7 +122,7 @@ namespace WixToolset.Bind
117 } 122 }
118 } 123 }
119 124
120 objectField.Data = this.ResolveFile(filePathToResolve, table.Name, row.SourceLineNumbers, BindStage.Updated); 125 objectField.Data = fileResolver.ResolveFile(filePathToResolve, table.Name, row.SourceLineNumbers, BindStage.Updated);
121 } 126 }
122 } 127 }
123 catch (WixFileNotFoundException) 128 catch (WixFileNotFoundException)
@@ -127,10 +132,10 @@ namespace WixToolset.Bind
127 } 132 }
128 } 133 }
129 134
130 isDefault = true;
131 if (null != objectField.PreviousData) 135 if (null != objectField.PreviousData)
132 { 136 {
133 objectField.PreviousData = this.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, objectField.PreviousData, false, ref isDefault); 137 objectField.PreviousData = this.BindVariableResolver.ResolveVariables(row.SourceLineNumbers, objectField.PreviousData, false, out isDefault);
138
134 if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field. 139 if (!Messaging.Instance.EncounteredError) // TODO: make this error handling more specific to just the failure to resolve variables in this field.
135 { 140 {
136 // file is compressed in a cabinet (and not modified above) 141 // file is compressed in a cabinet (and not modified above)
@@ -142,7 +147,7 @@ namespace WixToolset.Bind
142 objectField.PreviousBaseUri = objectField.BaseUri; 147 objectField.PreviousBaseUri = objectField.BaseUri;
143 } 148 }
144 149
145 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.TempFilesLocation); 150 string extractPath = this.FilesWithEmbeddedFiles.AddEmbeddedFileIndex(objectField.PreviousBaseUri, objectField.PreviousEmbeddedFileIndex.Value, this.IntermediateFolder);
146 151
147 // set the path to the file once its extracted from the cabinet 152 // set the path to the file once its extracted from the cabinet
148 objectField.PreviousData = extractPath; 153 objectField.PreviousData = extractPath;
@@ -151,14 +156,14 @@ namespace WixToolset.Bind
151 { 156 {
152 try 157 try
153 { 158 {
154 if (!this.FileManagerCore.RebaseTarget && !this.FileManagerCore.RebaseUpdated) 159 if (!fileResolver.RebaseTarget && !fileResolver.RebaseUpdated)
155 { 160 {
156 // resolve the path to the file 161 // resolve the path to the file
157 objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal); 162 objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal);
158 } 163 }
159 else 164 else
160 { 165 {
161 if (this.FileManagerCore.RebaseTarget) 166 if (fileResolver.RebaseTarget)
162 { 167 {
163 // if -bt is used, it come here 168 // if -bt is used, it come here
164 // Try to use the original unresolved source from either target build or update build 169 // Try to use the original unresolved source from either target build or update build
@@ -172,7 +177,7 @@ namespace WixToolset.Bind
172 } 177 }
173 178
174 // resolve the path to the file 179 // resolve the path to the file
175 objectField.PreviousData = this.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Target); 180 objectField.PreviousData = fileResolver.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Target);
176 181
177 } 182 }
178 } 183 }
@@ -192,24 +197,28 @@ namespace WixToolset.Bind
192 this.DelayedFields = delayedFields; 197 this.DelayedFields = delayedFields;
193 } 198 }
194 199
200#if false
195 private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal) 201 private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage = BindStage.Normal)
196 { 202 {
197 string path = null; 203 string path = null;
198 foreach (IBinderFileManager fileManager in this.FileManagers) 204 foreach (var extension in this.Extensions)
199 { 205 {
200 path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage); 206 path = extension.ResolveFile(source, type, sourceLineNumbers, bindStage);
201 if (null != path) 207 if (null != path)
202 { 208 {
203 break; 209 break;
204 } 210 }
205 } 211 }
206 212
207 if (null == path) 213 throw new NotImplementedException(); // need to do default binder stuff
208 { 214
209 throw new WixFileNotFoundException(sourceLineNumbers, source, type); 215 //if (null == path)
210 } 216 //{
217 // throw new WixFileNotFoundException(sourceLineNumbers, source, type);
218 //}
211 219
212 return path; 220 //return path;
213 } 221 }
222#endif
214 } 223 }
215} 224}
diff --git a/src/WixToolset.Core/Bind/ResolveResult.cs b/src/WixToolset.Core/Bind/ResolveResult.cs
new file mode 100644
index 00000000..13f25054
--- /dev/null
+++ b/src/WixToolset.Core/Bind/ResolveResult.cs
@@ -0,0 +1,14 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Bind
4{
5 using System.Collections.Generic;
6 using WixToolset.Extensibility;
7
8 public class ResolveResult
9 {
10 public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; }
11
12 public IEnumerable<IDelayedField> DelayedFields { get; set; }
13 }
14} \ No newline at end of file
diff --git a/src/WixToolset.Core/Bind/ResolvedDirectory.cs b/src/WixToolset.Core/Bind/ResolvedDirectory.cs
index 6985f95d..fca706d8 100644
--- a/src/WixToolset.Core/Bind/ResolvedDirectory.cs
+++ b/src/WixToolset.Core/Bind/ResolvedDirectory.cs
@@ -5,7 +5,7 @@ namespace WixToolset.Bind
5 /// <summary> 5 /// <summary>
6 /// Structure used for resolved directory information. 6 /// Structure used for resolved directory information.
7 /// </summary> 7 /// </summary>
8 internal struct ResolvedDirectory 8 public struct ResolvedDirectory
9 { 9 {
10 /// <summary>The directory parent.</summary> 10 /// <summary>The directory parent.</summary>
11 public string DirectoryParent; 11 public string DirectoryParent;
diff --git a/src/WixToolset.Core/Bind/TransferFilesCommand.cs b/src/WixToolset.Core/Bind/TransferFilesCommand.cs
index 719b8b20..f116569c 100644
--- a/src/WixToolset.Core/Bind/TransferFilesCommand.cs
+++ b/src/WixToolset.Core/Bind/TransferFilesCommand.cs
@@ -1,29 +1,37 @@
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. 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 2
3namespace WixToolset.Bind 3namespace WixToolset.Core.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.IO; 7 using System.IO;
8 using System.Security.AccessControl; 8 using System.Security.AccessControl;
9 using WixToolset.Data; 9 using WixToolset.Data;
10 using WixToolset.Data.Bind;
10 using WixToolset.Extensibility; 11 using WixToolset.Extensibility;
11 12
12 internal class TransferFilesCommand : ICommand 13 internal class TransferFilesCommand
13 { 14 {
14 public IEnumerable<IBinderFileManager> FileManagers { private get; set; } 15 public TransferFilesCommand(IEnumerable<BindPath> bindPaths, IEnumerable<IBinderExtension> extensions, IEnumerable<FileTransfer> fileTransfers, bool suppressAclReset)
16 {
17 this.FileResolver = new FileResolver(bindPaths, extensions);
18 this.FileTransfers = fileTransfers;
19 this.SuppressAclReset = suppressAclReset;
20 }
21
22 private FileResolver FileResolver { get; }
15 23
16 public IEnumerable<FileTransfer> FileTransfers { private get; set; } 24 private IEnumerable<FileTransfer> FileTransfers { get; }
17 25
18 public bool SuppressAclReset { private get; set; } 26 private bool SuppressAclReset { get; }
19 27
20 public void Execute() 28 public void Execute()
21 { 29 {
22 List<string> destinationFiles = new List<string>(); 30 List<string> destinationFiles = new List<string>();
23 31
24 foreach (FileTransfer fileTransfer in this.FileTransfers) 32 foreach (var fileTransfer in this.FileTransfers)
25 { 33 {
26 string fileSource = this.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal); 34 string fileSource = this.FileResolver.ResolveFile(fileTransfer.Source, fileTransfer.Type, fileTransfer.SourceLineNumbers, BindStage.Normal);
27 35
28 // If the source and destination are identical, then there's nothing to do here 36 // If the source and destination are identical, then there's nothing to do here
29 if (0 == String.Compare(fileSource, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase)) 37 if (0 == String.Compare(fileSource, fileTransfer.Destination, StringComparison.OrdinalIgnoreCase))
@@ -165,44 +173,17 @@ namespace WixToolset.Bind
165 } 173 }
166 } 174 }
167 175
168 private string ResolveFile(string source, string type, SourceLineNumber sourceLineNumbers, BindStage bindStage) 176 private void TransferFile(bool move, string source, string destination)
169 { 177 {
170 string path = null; 178 bool complete = false;
171 foreach (IBinderFileManager fileManager in this.FileManagers)
172 {
173 path = fileManager.ResolveFile(source, type, sourceLineNumbers, bindStage);
174 if (null != path)
175 {
176 break;
177 }
178 }
179 179
180 if (null == path) 180 if (move)
181 { 181 {
182 throw new WixFileNotFoundException(sourceLineNumbers, source, type); 182 complete = this.FileResolver.MoveFile(source, destination, true);
183 } 183 }
184 184 else
185 return path;
186 }
187
188 private void TransferFile(bool move, string source, string destination)
189 {
190 bool complete = false;
191 foreach (IBinderFileManager fileManager in this.FileManagers)
192 { 185 {
193 if (move) 186 complete = this.FileResolver.CopyFile(source, destination, true);
194 {
195 complete = fileManager.MoveFile(source, destination, true);
196 }
197 else
198 {
199 complete = fileManager.CopyFile(source, destination, true);
200 }
201
202 if (complete)
203 {
204 break;
205 }
206 } 187 }
207 188
208 if (!complete) 189 if (!complete)