diff options
author | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
commit | dbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch) | |
tree | 0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core/Bind | |
parent | fbf986eb97f68396797a89fc7d40dec07b775440 (diff) | |
download | wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2 wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip |
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core/Bind')
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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | ||
3 | namespace WixToolset.Bind | 3 | namespace 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 | |||
3 | namespace 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 | ||
3 | namespace WixToolset.Bind | 3 | namespace 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 | ||
3 | namespace WixToolset.Bind | 3 | namespace 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 | ||
3 | namespace WixToolset.Bind.Databases | 3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | |||
3 | namespace 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 | ||
3 | namespace WixToolset.Bind | 3 | namespace 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 | ||
3 | namespace WixToolset.Bind | 3 | namespace 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 | |||
3 | namespace 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 | ||
3 | namespace WixToolset.Bind | 3 | namespace 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) |