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 | |
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')
108 files changed, 1625 insertions, 19824 deletions
diff --git a/src/WixToolset.Core/BackendContext.cs b/src/WixToolset.Core/BackendContext.cs new file mode 100644 index 00000000..7166a3a3 --- /dev/null +++ b/src/WixToolset.Core/BackendContext.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 | ||
4 | { | ||
5 | using WixToolset.Data; | ||
6 | |||
7 | public class BackendContext | ||
8 | { | ||
9 | internal BackendContext() | ||
10 | { | ||
11 | this.Messaging = Messaging.Instance; | ||
12 | } | ||
13 | |||
14 | public Messaging Messaging { get; } | ||
15 | } | ||
16 | } | ||
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) |
diff --git a/src/WixToolset.Core/BindContext.cs b/src/WixToolset.Core/BindContext.cs new file mode 100644 index 00000000..499f3245 --- /dev/null +++ b/src/WixToolset.Core/BindContext.cs | |||
@@ -0,0 +1,57 @@ | |||
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 | ||
4 | { | ||
5 | using System.Collections.Generic; | ||
6 | using WixToolset.Data; | ||
7 | using WixToolset.Extensibility; | ||
8 | |||
9 | public class BindContext : IBindContext | ||
10 | { | ||
11 | public Messaging Messaging { get; set; } | ||
12 | |||
13 | public IEnumerable<BindPath> BindPaths { get; set; } | ||
14 | |||
15 | public int CabbingThreadCount { get; set; } | ||
16 | |||
17 | public string CabCachePath { get; set; } | ||
18 | |||
19 | public int Codepage { get; set; } | ||
20 | |||
21 | public CompressionLevel DefaultCompressionLevel { get; set; } | ||
22 | |||
23 | public IEnumerable<IDelayedField> DelayedFields { get; set; } | ||
24 | |||
25 | public IEnumerable<IExpectedExtractFile> ExpectedEmbeddedFiles { get; set; } | ||
26 | |||
27 | public IExtensionManager ExtensionManager { get; set; } | ||
28 | |||
29 | public IEnumerable<IBinderExtension> Extensions { get; set; } | ||
30 | |||
31 | public IEnumerable<string> Ices { get; set; } | ||
32 | |||
33 | public string IntermediateFolder { get; set; } | ||
34 | |||
35 | public Output IntermediateRepresentation { get; set; } | ||
36 | |||
37 | public string OutputPath { get; set; } | ||
38 | |||
39 | public string OutputPdbPath { get; set; } | ||
40 | |||
41 | public bool SuppressAclReset { get; set; } | ||
42 | |||
43 | public IEnumerable<string> SuppressIces { get; set; } | ||
44 | |||
45 | public bool SuppressValidation { get; set; } | ||
46 | |||
47 | public IBindVariableResolver WixVariableResolver { get; set; } | ||
48 | |||
49 | public string ContentsFile { get; set; } | ||
50 | |||
51 | public string OutputsFile { get; set; } | ||
52 | |||
53 | public string BuiltOutputsFile { get; set; } | ||
54 | |||
55 | public string WixprojectFile { get; set; } | ||
56 | } | ||
57 | } | ||
diff --git a/src/WixToolset.Core/Binder.cs b/src/WixToolset.Core/Binder.cs index 18ad2d62..43c15634 100644 --- a/src/WixToolset.Core/Binder.cs +++ b/src/WixToolset.Core/Binder.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 | 3 | namespace WixToolset.Core |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Collections; | 6 | using System.Collections; |
@@ -11,105 +11,72 @@ namespace WixToolset | |||
11 | using System.Linq; | 11 | using System.Linq; |
12 | using System.Reflection; | 12 | using System.Reflection; |
13 | using WixToolset.Bind; | 13 | using WixToolset.Bind; |
14 | using WixToolset.Core.Bind; | ||
14 | using WixToolset.Data; | 15 | using WixToolset.Data; |
16 | using WixToolset.Data.Bind; | ||
15 | using WixToolset.Data.Rows; | 17 | using WixToolset.Data.Rows; |
16 | using WixToolset.Extensibility; | 18 | using WixToolset.Extensibility; |
17 | using WixToolset.Msi; | ||
18 | |||
19 | // TODO: (4.0) Refactor so that these don't need to be copied. | ||
20 | // Copied verbatim from ext\UtilExtension\wixext\UtilCompiler.cs | ||
21 | [Flags] | ||
22 | internal enum WixFileSearchAttributes | ||
23 | { | ||
24 | Default = 0x001, | ||
25 | MinVersionInclusive = 0x002, | ||
26 | MaxVersionInclusive = 0x004, | ||
27 | MinSizeInclusive = 0x008, | ||
28 | MaxSizeInclusive = 0x010, | ||
29 | MinDateInclusive = 0x020, | ||
30 | MaxDateInclusive = 0x040, | ||
31 | WantVersion = 0x080, | ||
32 | WantExists = 0x100, | ||
33 | IsDirectory = 0x200, | ||
34 | } | ||
35 | |||
36 | [Flags] | ||
37 | internal enum WixRegistrySearchAttributes | ||
38 | { | ||
39 | Raw = 0x01, | ||
40 | Compatible = 0x02, | ||
41 | ExpandEnvironmentVariables = 0x04, | ||
42 | WantValue = 0x08, | ||
43 | WantExists = 0x10, | ||
44 | Win64 = 0x20, | ||
45 | } | ||
46 | |||
47 | internal enum WixComponentSearchAttributes | ||
48 | { | ||
49 | KeyPath = 0x1, | ||
50 | State = 0x2, | ||
51 | WantDirectory = 0x4, | ||
52 | } | ||
53 | |||
54 | [Flags] | ||
55 | internal enum WixProductSearchAttributes | ||
56 | { | ||
57 | Version = 0x1, | ||
58 | Language = 0x2, | ||
59 | State = 0x4, | ||
60 | Assignment = 0x8, | ||
61 | UpgradeCode = 0x10, | ||
62 | } | ||
63 | 19 | ||
64 | /// <summary> | 20 | /// <summary> |
65 | /// Binder of the WiX toolset. | 21 | /// Binder of the WiX toolset. |
66 | /// </summary> | 22 | /// </summary> |
67 | public sealed class Binder | 23 | public sealed class Binder |
68 | { | 24 | { |
69 | private BinderCore core; | 25 | //private BinderCore core; |
70 | private BinderFileManagerCore fileManagerCore; | 26 | //private List<IBinderExtension> extensions; |
71 | private List<IBinderExtension> extensions; | 27 | //private List<IBinderFileManager> fileManagers; |
72 | private List<IBinderFileManager> fileManagers; | ||
73 | private List<InspectorExtension> inspectorExtensions; | ||
74 | 28 | ||
75 | public Binder() | 29 | public Binder() |
76 | { | 30 | { |
77 | this.DefaultCompressionLevel = CompressionLevel.High; | 31 | //this.DefaultCompressionLevel = CompressionLevel.High; |
78 | 32 | ||
79 | this.BindPaths = new List<BindPath>(); | 33 | //this.BindPaths = new List<BindPath>(); |
80 | this.TargetBindPaths = new List<BindPath>(); | 34 | //this.TargetBindPaths = new List<BindPath>(); |
81 | this.UpdatedBindPaths = new List<BindPath>(); | 35 | //this.UpdatedBindPaths = new List<BindPath>(); |
82 | 36 | ||
83 | this.extensions = new List<IBinderExtension>(); | 37 | //this.extensions = new List<IBinderExtension>(); |
84 | this.fileManagers = new List<IBinderFileManager>(); | 38 | //this.fileManagers = new List<IBinderFileManager>(); |
85 | this.inspectorExtensions = new List<InspectorExtension>(); | 39 | //this.inspectorExtensions = new List<InspectorExtension>(); |
86 | 40 | ||
87 | this.Ices = new List<string>(); | 41 | //this.Ices = new List<string>(); |
88 | this.SuppressIces = new List<string>(); | 42 | //this.SuppressIces = new List<string>(); |
89 | } | 43 | } |
90 | 44 | ||
91 | public string ContentsFile { private get; set; } | 45 | public Binder(BindContext context) |
46 | { | ||
47 | this.Context = context; | ||
48 | |||
49 | this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); | ||
50 | } | ||
51 | |||
52 | private BindContext Context { get; } | ||
53 | |||
54 | private TableDefinitionCollection TableDefinitions { get; } | ||
55 | |||
56 | //public IEnumerable<IBackendFactory> BackendFactories { get; set; } | ||
92 | 57 | ||
93 | public string OutputsFile { private get; set; } | 58 | //public string ContentsFile { private get; set; } |
94 | 59 | ||
95 | public string BuiltOutputsFile { private get; set; } | 60 | //public string OutputsFile { private get; set; } |
96 | 61 | ||
97 | public string WixprojectFile { private get; set; } | 62 | //public string BuiltOutputsFile { private get; set; } |
63 | |||
64 | //public string WixprojectFile { private get; set; } | ||
98 | 65 | ||
99 | /// <summary> | 66 | /// <summary> |
100 | /// Gets the list of bindpaths. | 67 | /// Gets the list of bindpaths. |
101 | /// </summary> | 68 | /// </summary> |
102 | public List<BindPath> BindPaths { get; private set; } | 69 | //public List<BindPath> BindPaths { get; private set; } |
103 | 70 | ||
104 | /// <summary> | 71 | /// <summary> |
105 | /// Gets the list of target bindpaths. | 72 | /// Gets the list of target bindpaths. |
106 | /// </summary> | 73 | /// </summary> |
107 | public List<BindPath> TargetBindPaths { get; private set; } | 74 | //public List<BindPath> TargetBindPaths { get; private set; } |
108 | 75 | ||
109 | /// <summary> | 76 | /// <summary> |
110 | /// Gets the list of updated bindpaths. | 77 | /// Gets the list of updated bindpaths. |
111 | /// </summary> | 78 | /// </summary> |
112 | public List<BindPath> UpdatedBindPaths { get; private set; } | 79 | //public List<BindPath> UpdatedBindPaths { get; private set; } |
113 | 80 | ||
114 | /// <summary> | 81 | /// <summary> |
115 | /// Gets or sets the option to enable building binary delta patches. | 82 | /// Gets or sets the option to enable building binary delta patches. |
@@ -132,17 +99,17 @@ namespace WixToolset | |||
132 | /// Gets or sets the default compression level to use for cabinets | 99 | /// Gets or sets the default compression level to use for cabinets |
133 | /// that don't have their compression level explicitly set. | 100 | /// that don't have their compression level explicitly set. |
134 | /// </summary> | 101 | /// </summary> |
135 | public CompressionLevel DefaultCompressionLevel { get; set; } | 102 | //public CompressionLevel DefaultCompressionLevel { get; set; } |
136 | 103 | ||
137 | /// <summary> | 104 | /// <summary> |
138 | /// Gets and sets the location to save the WixPdb. | 105 | /// Gets and sets the location to save the WixPdb. |
139 | /// </summary> | 106 | /// </summary> |
140 | /// <value>The location in which to save the WixPdb. Null if the the WixPdb should not be output.</value> | 107 | /// <value>The location in which to save the WixPdb. Null if the the WixPdb should not be output.</value> |
141 | public string PdbFile { get; set; } | 108 | //public string PdbFile { get; set; } |
142 | 109 | ||
143 | public List<string> Ices { get; private set; } | 110 | //public List<string> Ices { get; private set; } |
144 | 111 | ||
145 | public List<string> SuppressIces { get; private set; } | 112 | //public List<string> SuppressIces { get; private set; } |
146 | 113 | ||
147 | /// <summary> | 114 | /// <summary> |
148 | /// Gets and sets the option to suppress resetting ACLs by the binder. | 115 | /// Gets and sets the option to suppress resetting ACLs by the binder. |
@@ -185,24 +152,164 @@ namespace WixToolset | |||
185 | /// Gets or sets the Wix variable resolver. | 152 | /// Gets or sets the Wix variable resolver. |
186 | /// </summary> | 153 | /// </summary> |
187 | /// <value>The Wix variable resolver.</value> | 154 | /// <value>The Wix variable resolver.</value> |
188 | public WixVariableResolver WixVariableResolver { get; set; } | 155 | internal WixVariableResolver WixVariableResolver { get; set; } |
189 | 156 | ||
190 | /// <summary> | 157 | /// <summary> |
191 | /// Add a binder extension. | 158 | /// Add a binder extension. |
192 | /// </summary> | 159 | /// </summary> |
193 | /// <param name="extension">New extension.</param> | 160 | /// <param name="extension">New extension.</param> |
194 | public void AddExtension(IBinderExtension extension) | 161 | //public void AddExtension(IBinderExtension extension) |
195 | { | 162 | //{ |
196 | this.extensions.Add(extension); | 163 | // this.extensions.Add(extension); |
197 | } | 164 | //} |
198 | 165 | ||
199 | /// <summary> | 166 | /// <summary> |
200 | /// Add a file manager extension. | 167 | /// Add a file manager extension. |
201 | /// </summary> | 168 | /// </summary> |
202 | /// <param name="extension">New file manager.</param> | 169 | /// <param name="extension">New file manager.</param> |
203 | public void AddExtension(IBinderFileManager extension) | 170 | //public void AddExtension(IBinderFileManager extension) |
171 | //{ | ||
172 | // this.fileManagers.Add(extension); | ||
173 | //} | ||
174 | |||
175 | public bool Bind() | ||
176 | { | ||
177 | //if (!String.IsNullOrEmpty(this.Context.FileManagerCore.CabCachePath)) | ||
178 | //{ | ||
179 | // Directory.CreateDirectory(this.Context.FileManagerCore.CabCachePath); | ||
180 | //} | ||
181 | |||
182 | //this.core = new BinderCore(); | ||
183 | //this.core.FileManagerCore = this.Context.FileManagerCore; | ||
184 | |||
185 | this.WriteBuildInfoTable(this.Context.IntermediateRepresentation, this.Context.OutputPath); | ||
186 | |||
187 | // Prebind. | ||
188 | // | ||
189 | this.Context.Extensions = this.Context.ExtensionManager.Create<IBinderExtension>(); | ||
190 | |||
191 | foreach (IBinderExtension extension in this.Context.Extensions) | ||
192 | { | ||
193 | extension.PreBind(this.Context); | ||
194 | } | ||
195 | |||
196 | // Resolve. | ||
197 | // | ||
198 | var resolveResult = this.Resolve(); | ||
199 | |||
200 | this.Context.DelayedFields = resolveResult.DelayedFields; | ||
201 | |||
202 | this.Context.ExpectedEmbeddedFiles = resolveResult.ExpectedEmbeddedFiles; | ||
203 | |||
204 | // Backend. | ||
205 | // | ||
206 | var bindResult = this.BackendBind(); | ||
207 | |||
208 | if (bindResult != null) | ||
209 | { | ||
210 | // Postbind. | ||
211 | // | ||
212 | foreach (IBinderExtension extension in this.Context.Extensions) | ||
213 | { | ||
214 | extension.PostBind(bindResult); | ||
215 | } | ||
216 | |||
217 | // Layout. | ||
218 | // | ||
219 | this.Layout(bindResult); | ||
220 | } | ||
221 | |||
222 | return Messaging.Instance.EncounteredError; | ||
223 | } | ||
224 | |||
225 | private ResolveResult Resolve() | ||
226 | { | ||
227 | var buildingPatch = (this.Context.IntermediateRepresentation.Type == OutputType.Patch); | ||
228 | |||
229 | var filesWithEmbeddedFiles = new ExtractEmbeddedFiles(); | ||
230 | |||
231 | IEnumerable<DelayedField> delayedFields; | ||
232 | { | ||
233 | var command = new ResolveFieldsCommand(); | ||
234 | command.BuildingPatch = buildingPatch; | ||
235 | command.BindVariableResolver = this.Context.WixVariableResolver; | ||
236 | command.BindPaths = this.Context.BindPaths; | ||
237 | command.Extensions = this.Context.Extensions; | ||
238 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
239 | command.IntermediateFolder = this.Context.IntermediateFolder; | ||
240 | command.Tables = this.Context.IntermediateRepresentation.Tables; | ||
241 | command.SupportDelayedResolution = true; | ||
242 | command.Execute(); | ||
243 | |||
244 | delayedFields = command.DelayedFields; | ||
245 | } | ||
246 | |||
247 | if (this.Context.IntermediateRepresentation.SubStorages != null) | ||
248 | { | ||
249 | foreach (SubStorage transform in this.Context.IntermediateRepresentation.SubStorages) | ||
250 | { | ||
251 | var command = new ResolveFieldsCommand(); | ||
252 | command.BuildingPatch = buildingPatch; | ||
253 | command.BindVariableResolver = this.Context.WixVariableResolver; | ||
254 | command.BindPaths = this.Context.BindPaths; | ||
255 | command.Extensions = this.Context.Extensions; | ||
256 | command.FilesWithEmbeddedFiles = filesWithEmbeddedFiles; | ||
257 | command.IntermediateFolder = this.Context.IntermediateFolder; | ||
258 | command.Tables = transform.Data.Tables; | ||
259 | command.SupportDelayedResolution = false; | ||
260 | command.Execute(); | ||
261 | } | ||
262 | } | ||
263 | |||
264 | var expectedEmbeddedFiles = filesWithEmbeddedFiles.GetExpectedEmbeddedFiles(); | ||
265 | |||
266 | return new ResolveResult | ||
267 | { | ||
268 | ExpectedEmbeddedFiles = expectedEmbeddedFiles, | ||
269 | DelayedFields = delayedFields, | ||
270 | }; | ||
271 | } | ||
272 | |||
273 | private BindResult BackendBind() | ||
274 | { | ||
275 | var backendFactories = this.Context.ExtensionManager.Create<IBackendFactory>(); | ||
276 | |||
277 | foreach (var factory in backendFactories) | ||
278 | { | ||
279 | if (factory.TryCreateBackend(this.Context.IntermediateRepresentation.Type.ToString(), this.Context.OutputPath, null, out var backend)) | ||
280 | { | ||
281 | var result = backend.Bind(this.Context); | ||
282 | return result; | ||
283 | } | ||
284 | } | ||
285 | |||
286 | // TODO: messaging that a backend could not be found to bind the output type? | ||
287 | |||
288 | return null; | ||
289 | } | ||
290 | private void Layout(BindResult result) | ||
204 | { | 291 | { |
205 | this.fileManagers.Add(extension); | 292 | try |
293 | { | ||
294 | this.LayoutMedia(result.FileTransfers); | ||
295 | } | ||
296 | finally | ||
297 | { | ||
298 | if (!String.IsNullOrEmpty(this.Context.ContentsFile) && result.ContentFilePaths != null) | ||
299 | { | ||
300 | this.CreateContentsFile(this.Context.ContentsFile, result.ContentFilePaths); | ||
301 | } | ||
302 | |||
303 | if (!String.IsNullOrEmpty(this.Context.OutputsFile) && result.FileTransfers != null) | ||
304 | { | ||
305 | this.CreateOutputsFile(this.Context.OutputsFile, result.FileTransfers, this.Context.OutputPdbPath); | ||
306 | } | ||
307 | |||
308 | if (!String.IsNullOrEmpty(this.Context.BuiltOutputsFile) && result.FileTransfers != null) | ||
309 | { | ||
310 | this.CreateBuiltOutputsFile(this.Context.BuiltOutputsFile, result.FileTransfers, this.Context.OutputPdbPath); | ||
311 | } | ||
312 | } | ||
206 | } | 313 | } |
207 | 314 | ||
208 | /// <summary> | 315 | /// <summary> |
@@ -212,6 +319,7 @@ namespace WixToolset | |||
212 | /// <param name="file">The Windows Installer file to create.</param> | 319 | /// <param name="file">The Windows Installer file to create.</param> |
213 | /// <remarks>The Binder.DeleteTempFiles method should be called after calling this method.</remarks> | 320 | /// <remarks>The Binder.DeleteTempFiles method should be called after calling this method.</remarks> |
214 | /// <returns>true if binding completed successfully; false otherwise</returns> | 321 | /// <returns>true if binding completed successfully; false otherwise</returns> |
322 | #if false | ||
215 | public bool Bind(Output output, string file) | 323 | public bool Bind(Output output, string file) |
216 | { | 324 | { |
217 | // Ensure the cabinet cache path exists if we are going to use it. | 325 | // Ensure the cabinet cache path exists if we are going to use it. |
@@ -220,20 +328,20 @@ namespace WixToolset | |||
220 | Directory.CreateDirectory(this.CabCachePath); | 328 | Directory.CreateDirectory(this.CabCachePath); |
221 | } | 329 | } |
222 | 330 | ||
223 | this.fileManagerCore = new BinderFileManagerCore(); | 331 | //var fileManagerCore = new BinderFileManagerCore(); |
224 | this.fileManagerCore.CabCachePath = this.CabCachePath; | 332 | //fileManagerCore.CabCachePath = this.CabCachePath; |
225 | this.fileManagerCore.Output = output; | 333 | //fileManagerCore.Output = output; |
226 | this.fileManagerCore.TempFilesLocation = this.TempFilesLocation; | 334 | //fileManagerCore.TempFilesLocation = this.TempFilesLocation; |
227 | this.fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal); | 335 | //fileManagerCore.AddBindPaths(this.BindPaths, BindStage.Normal); |
228 | this.fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target); | 336 | //fileManagerCore.AddBindPaths(this.TargetBindPaths, BindStage.Target); |
229 | this.fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated); | 337 | //fileManagerCore.AddBindPaths(this.UpdatedBindPaths, BindStage.Updated); |
230 | foreach (IBinderFileManager fileManager in this.fileManagers) | 338 | //foreach (IBinderFileManager fileManager in this.fileManagers) |
231 | { | 339 | //{ |
232 | fileManager.Core = this.fileManagerCore; | 340 | // fileManager.Core = fileManagerCore; |
233 | } | 341 | //} |
234 | 342 | ||
235 | this.core = new BinderCore(); | 343 | this.core = new BinderCore(); |
236 | this.core.FileManagerCore = this.fileManagerCore; | 344 | this.core.FileManagerCore = fileManagerCore; |
237 | 345 | ||
238 | this.WriteBuildInfoTable(output, file); | 346 | this.WriteBuildInfoTable(output, file); |
239 | 347 | ||
@@ -246,54 +354,69 @@ namespace WixToolset | |||
246 | } | 354 | } |
247 | 355 | ||
248 | // Gather all the wix variables. | 356 | // Gather all the wix variables. |
249 | Table wixVariableTable = output.Tables["WixVariable"]; | 357 | //Table wixVariableTable = output.Tables["WixVariable"]; |
250 | if (null != wixVariableTable) | 358 | //if (null != wixVariableTable) |
359 | //{ | ||
360 | // foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) | ||
361 | // { | ||
362 | // this.WixVariableResolver.AddVariable(wixVariableRow); | ||
363 | // } | ||
364 | //} | ||
365 | |||
366 | //BindContext context = new BindContext(); | ||
367 | //context.CabbingThreadCount = this.CabbingThreadCount; | ||
368 | //context.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
369 | //context.Extensions = this.extensions; | ||
370 | //context.FileManagerCore = fileManagerCore; | ||
371 | //context.FileManagers = this.fileManagers; | ||
372 | //context.Ices = this.Ices; | ||
373 | //context.IntermediateFolder = this.TempFilesLocation; | ||
374 | //context.IntermediateRepresentation = output; | ||
375 | //context.Localizer = this.Localizer; | ||
376 | //context.OutputPath = file; | ||
377 | //context.OutputPdbPath = this.PdbFile; | ||
378 | //context.SuppressIces = this.SuppressIces; | ||
379 | //context.SuppressValidation = this.SuppressValidation; | ||
380 | //context.WixVariableResolver = this.WixVariableResolver; | ||
381 | |||
382 | BindResult result = null; | ||
383 | |||
384 | foreach (var factory in this.BackendFactories) | ||
251 | { | 385 | { |
252 | foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) | 386 | if (factory.TryCreateBackend(output.Type.ToString(), file, null, out var backend)) |
253 | { | 387 | { |
254 | this.WixVariableResolver.AddVariable(wixVariableRow); | 388 | result = backend.Bind(context); |
389 | break; | ||
255 | } | 390 | } |
256 | } | 391 | } |
257 | 392 | ||
258 | IEnumerable<FileTransfer> fileTransfers = null; | 393 | if (result == null) |
259 | IEnumerable<string> contentPaths = null; | ||
260 | |||
261 | switch (output.Type) | ||
262 | { | 394 | { |
263 | case OutputType.Bundle: | 395 | // TODO: messaging that a backend could not be found to bind the output type? |
264 | this.BindBundle(output, file, out fileTransfers, out contentPaths); | ||
265 | break; | ||
266 | |||
267 | case OutputType.Transform: | ||
268 | this.BindTransform(output, file); | ||
269 | break; | ||
270 | 396 | ||
271 | default: | 397 | return false; |
272 | this.BindDatabase(output, file, out fileTransfers, out contentPaths); | ||
273 | break; | ||
274 | } | 398 | } |
275 | 399 | ||
276 | |||
277 | // Layout media | 400 | // Layout media |
278 | try | 401 | try |
279 | { | 402 | { |
280 | this.LayoutMedia(fileTransfers); | 403 | this.LayoutMedia(result.FileTransfers); |
281 | } | 404 | } |
282 | finally | 405 | finally |
283 | { | 406 | { |
284 | if (!String.IsNullOrEmpty(this.ContentsFile) && contentPaths != null) | 407 | if (!String.IsNullOrEmpty(this.ContentsFile) && result.ContentFilePaths != null) |
285 | { | 408 | { |
286 | this.CreateContentsFile(this.ContentsFile, contentPaths); | 409 | this.CreateContentsFile(this.ContentsFile, result.ContentFilePaths); |
287 | } | 410 | } |
288 | 411 | ||
289 | if (!String.IsNullOrEmpty(this.OutputsFile) && fileTransfers != null) | 412 | if (!String.IsNullOrEmpty(this.OutputsFile) && result.FileTransfers != null) |
290 | { | 413 | { |
291 | this.CreateOutputsFile(this.OutputsFile, fileTransfers, this.PdbFile); | 414 | this.CreateOutputsFile(this.OutputsFile, result.FileTransfers, this.PdbFile); |
292 | } | 415 | } |
293 | 416 | ||
294 | if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && fileTransfers != null) | 417 | if (!String.IsNullOrEmpty(this.BuiltOutputsFile) && result.FileTransfers != null) |
295 | { | 418 | { |
296 | this.CreateBuiltOutputsFile(this.BuiltOutputsFile, fileTransfers, this.PdbFile); | 419 | this.CreateBuiltOutputsFile(this.BuiltOutputsFile, result.FileTransfers, this.PdbFile); |
297 | } | 420 | } |
298 | } | 421 | } |
299 | 422 | ||
@@ -301,6 +424,7 @@ namespace WixToolset | |||
301 | 424 | ||
302 | return Messaging.Instance.EncounteredError; | 425 | return Messaging.Instance.EncounteredError; |
303 | } | 426 | } |
427 | #endif | ||
304 | 428 | ||
305 | /// <summary> | 429 | /// <summary> |
306 | /// Does any housekeeping after Bind. | 430 | /// Does any housekeeping after Bind. |
@@ -312,12 +436,12 @@ namespace WixToolset | |||
312 | { | 436 | { |
313 | if (!this.DeleteTempFiles()) | 437 | if (!this.DeleteTempFiles()) |
314 | { | 438 | { |
315 | this.core.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation)); | 439 | this.Context.Messaging.OnMessage(WixWarnings.FailedToDeleteTempDir(this.TempFilesLocation)); |
316 | } | 440 | } |
317 | } | 441 | } |
318 | else | 442 | else |
319 | { | 443 | { |
320 | this.core.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation)); | 444 | this.Context.Messaging.OnMessage(WixVerboses.BinderTempDirLocatedAt(this.TempFilesLocation)); |
321 | } | 445 | } |
322 | } | 446 | } |
323 | 447 | ||
@@ -327,7 +451,7 @@ namespace WixToolset | |||
327 | /// <returns>True if all files were deleted, false otherwise.</returns> | 451 | /// <returns>True if all files were deleted, false otherwise.</returns> |
328 | private bool DeleteTempFiles() | 452 | private bool DeleteTempFiles() |
329 | { | 453 | { |
330 | bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.core); | 454 | bool deleted = Common.DeleteTempFiles(this.TempFilesLocation, this.Context.Messaging); |
331 | return deleted; | 455 | return deleted; |
332 | } | 456 | } |
333 | 457 | ||
@@ -338,7 +462,7 @@ namespace WixToolset | |||
338 | /// <param name="databaseFile">The output file if OutputFile not set.</param> | 462 | /// <param name="databaseFile">The output file if OutputFile not set.</param> |
339 | private void WriteBuildInfoTable(Output output, string outputFile) | 463 | private void WriteBuildInfoTable(Output output, string outputFile) |
340 | { | 464 | { |
341 | Table buildInfoTable = output.EnsureTable(this.core.TableDefinitions["WixBuildInfo"]); | 465 | Table buildInfoTable = output.EnsureTable(this.TableDefinitions["WixBuildInfo"]); |
342 | Row buildInfoRow = buildInfoTable.CreateRow(null); | 466 | Row buildInfoRow = buildInfoTable.CreateRow(null); |
343 | 467 | ||
344 | Assembly executingAssembly = Assembly.GetExecutingAssembly(); | 468 | Assembly executingAssembly = Assembly.GetExecutingAssembly(); |
@@ -346,17 +470,18 @@ namespace WixToolset | |||
346 | buildInfoRow[0] = fileVersion.FileVersion; | 470 | buildInfoRow[0] = fileVersion.FileVersion; |
347 | buildInfoRow[1] = outputFile; | 471 | buildInfoRow[1] = outputFile; |
348 | 472 | ||
349 | if (!String.IsNullOrEmpty(this.WixprojectFile)) | 473 | if (!String.IsNullOrEmpty(this.Context.WixprojectFile)) |
350 | { | 474 | { |
351 | buildInfoRow[2] = this.WixprojectFile; | 475 | buildInfoRow[2] = this.Context.WixprojectFile; |
352 | } | 476 | } |
353 | 477 | ||
354 | if (!String.IsNullOrEmpty(this.PdbFile)) | 478 | if (!String.IsNullOrEmpty(this.Context.OutputPdbPath)) |
355 | { | 479 | { |
356 | buildInfoRow[3] = this.PdbFile; | 480 | buildInfoRow[3] = this.Context.OutputPdbPath; |
357 | } | 481 | } |
358 | } | 482 | } |
359 | 483 | ||
484 | #if DELETE_THIS_CODE | ||
360 | /// <summary> | 485 | /// <summary> |
361 | /// Binds a bundle. | 486 | /// Binds a bundle. |
362 | /// </summary> | 487 | /// </summary> |
@@ -454,6 +579,7 @@ namespace WixToolset | |||
454 | command.OutputPath = outputPath; | 579 | command.OutputPath = outputPath; |
455 | command.Execute(); | 580 | command.Execute(); |
456 | } | 581 | } |
582 | #endif | ||
457 | 583 | ||
458 | /// <summary> | 584 | /// <summary> |
459 | /// Final step in binding that transfers (moves/copies) all files generated into the appropriate | 585 | /// Final step in binding that transfers (moves/copies) all files generated into the appropriate |
@@ -464,12 +590,9 @@ namespace WixToolset | |||
464 | { | 590 | { |
465 | if (null != transfers && transfers.Any()) | 591 | if (null != transfers && transfers.Any()) |
466 | { | 592 | { |
467 | this.core.OnMessage(WixVerboses.LayingOutMedia()); | 593 | this.Context.Messaging.OnMessage(WixVerboses.LayingOutMedia()); |
468 | 594 | ||
469 | TransferFilesCommand command = new TransferFilesCommand(); | 595 | var command = new TransferFilesCommand(this.Context.BindPaths, this.Context.Extensions, transfers, this.Context.SuppressAclReset); |
470 | command.FileManagers = this.fileManagers; | ||
471 | command.FileTransfers = transfers; | ||
472 | command.SuppressAclReset = this.SuppressAclReset; | ||
473 | command.Execute(); | 596 | command.Execute(); |
474 | } | 597 | } |
475 | } | 598 | } |
@@ -482,7 +605,7 @@ namespace WixToolset | |||
482 | /// <param name="directory">Directory identifier.</param> | 605 | /// <param name="directory">Directory identifier.</param> |
483 | /// <param name="canonicalize">Canonicalize the path for standard directories.</param> | 606 | /// <param name="canonicalize">Canonicalize the path for standard directories.</param> |
484 | /// <returns>Source path of a directory.</returns> | 607 | /// <returns>Source path of a directory.</returns> |
485 | internal static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize) | 608 | public static string GetDirectoryPath(Hashtable directories, Hashtable componentIdGenSeeds, string directory, bool canonicalize) |
486 | { | 609 | { |
487 | if (!directories.Contains(directory)) | 610 | if (!directories.Contains(directory)) |
488 | { | 611 | { |
@@ -543,9 +666,9 @@ namespace WixToolset | |||
543 | /// <param name="compressed">Specifies the package is compressed.</param> | 666 | /// <param name="compressed">Specifies the package is compressed.</param> |
544 | /// <param name="useLongName">Specifies the package uses long file names.</param> | 667 | /// <param name="useLongName">Specifies the package uses long file names.</param> |
545 | /// <returns>Source path of file relative to package directory.</returns> | 668 | /// <returns>Source path of file relative to package directory.</returns> |
546 | internal static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName) | 669 | public static string GetFileSourcePath(Hashtable directories, string directoryId, string fileName, bool compressed, bool useLongName) |
547 | { | 670 | { |
548 | string fileSourcePath = Installer.GetName(fileName, true, useLongName); | 671 | string fileSourcePath = Common.GetName(fileName, true, useLongName); |
549 | 672 | ||
550 | if (compressed) | 673 | if (compressed) |
551 | { | 674 | { |
diff --git a/src/WixToolset.Core/BinderFileManager.cs b/src/WixToolset.Core/BinderFileManager.cs index 0da54002..1527d93d 100644 --- a/src/WixToolset.Core/BinderFileManager.cs +++ b/src/WixToolset.Core/BinderFileManager.cs | |||
@@ -12,6 +12,7 @@ namespace WixToolset | |||
12 | using WixToolset.Data.Rows; | 12 | using WixToolset.Data.Rows; |
13 | using WixToolset.Extensibility; | 13 | using WixToolset.Extensibility; |
14 | 14 | ||
15 | #if false | ||
15 | /// <summary> | 16 | /// <summary> |
16 | /// Base class for creating a binder file manager. | 17 | /// Base class for creating a binder file manager. |
17 | /// </summary> | 18 | /// </summary> |
@@ -367,4 +368,5 @@ namespace WixToolset | |||
367 | [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | 368 | [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] |
368 | private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); | 369 | private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); |
369 | } | 370 | } |
371 | #endif | ||
370 | } | 372 | } |
diff --git a/src/WixToolset.Core/BinderFileManagerCore.cs b/src/WixToolset.Core/BinderFileManagerCore.cs index 6a5e1d5e..f1a78880 100644 --- a/src/WixToolset.Core/BinderFileManagerCore.cs +++ b/src/WixToolset.Core/BinderFileManagerCore.cs | |||
@@ -6,6 +6,7 @@ namespace WixToolset | |||
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
7 | using System.Linq; | 7 | using System.Linq; |
8 | using WixToolset.Data; | 8 | using WixToolset.Data; |
9 | using WixToolset.Data.Bind; | ||
9 | using WixToolset.Extensibility; | 10 | using WixToolset.Extensibility; |
10 | 11 | ||
11 | public class BinderFileManagerCore : IBinderFileManagerCore | 12 | public class BinderFileManagerCore : IBinderFileManagerCore |
diff --git a/src/WixToolset.Core/CLR/Interop/CLRInterop.cs b/src/WixToolset.Core/CLR/Interop/CLRInterop.cs deleted file mode 100644 index 4157f23a..00000000 --- a/src/WixToolset.Core/CLR/Interop/CLRInterop.cs +++ /dev/null | |||
@@ -1,147 +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.Clr.Interop | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Interop class for mscorwks.dll assembly name APIs. | ||
10 | /// </summary> | ||
11 | internal sealed class ClrInterop | ||
12 | { | ||
13 | private static readonly Guid referenceIdentityGuid = new Guid("6eaf5ace-7917-4f3c-b129-e046a9704766"); | ||
14 | |||
15 | /// <summary> | ||
16 | /// Protect the constructor. | ||
17 | /// </summary> | ||
18 | private ClrInterop() | ||
19 | { | ||
20 | } | ||
21 | |||
22 | /// <summary> | ||
23 | /// Represents a reference to the unique signature of a code object. | ||
24 | /// </summary> | ||
25 | [ComImport] | ||
26 | [Guid("6eaf5ace-7917-4f3c-b129-e046a9704766")] | ||
27 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
28 | internal interface IReferenceIdentity | ||
29 | { | ||
30 | /// <summary> | ||
31 | /// Get an assembly attribute. | ||
32 | /// </summary> | ||
33 | /// <param name="attributeNamespace">Attribute namespace.</param> | ||
34 | /// <param name="attributeName">Attribute name.</param> | ||
35 | /// <returns>The assembly attribute.</returns> | ||
36 | [return: MarshalAs(UnmanagedType.LPWStr)] | ||
37 | string GetAttribute( | ||
38 | [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, | ||
39 | [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName); | ||
40 | |||
41 | /// <summary> | ||
42 | /// Set an assembly attribute. | ||
43 | /// </summary> | ||
44 | /// <param name="attributeNamespace">Attribute namespace.</param> | ||
45 | /// <param name="attributeName">Attribute name.</param> | ||
46 | /// <param name="attributeValue">Attribute value.</param> | ||
47 | void SetAttribute( | ||
48 | [In, MarshalAs(UnmanagedType.LPWStr)] string attributeNamespace, | ||
49 | [In, MarshalAs(UnmanagedType.LPWStr)] string attributeName, | ||
50 | [In, MarshalAs(UnmanagedType.LPWStr)] string attributeValue); | ||
51 | |||
52 | /// <summary> | ||
53 | /// Get an iterator for the assembly's attributes. | ||
54 | /// </summary> | ||
55 | /// <returns>Assembly attribute enumerator.</returns> | ||
56 | IEnumIDENTITY_ATTRIBUTE EnumAttributes(); | ||
57 | |||
58 | /// <summary> | ||
59 | /// Clone an IReferenceIdentity. | ||
60 | /// </summary> | ||
61 | /// <param name="countOfDeltas">Count of deltas.</param> | ||
62 | /// <param name="deltas">The deltas.</param> | ||
63 | /// <returns>Cloned IReferenceIdentity.</returns> | ||
64 | IReferenceIdentity Clone( | ||
65 | [In] IntPtr /*SIZE_T*/ countOfDeltas, | ||
66 | [In, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] deltas); | ||
67 | } | ||
68 | |||
69 | /// <summary> | ||
70 | /// IEnumIDENTITY_ATTRIBUTE interface. | ||
71 | /// </summary> | ||
72 | [ComImport] | ||
73 | [Guid("9cdaae75-246e-4b00-a26d-b9aec137a3eb")] | ||
74 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
75 | internal interface IEnumIDENTITY_ATTRIBUTE | ||
76 | { | ||
77 | /// <summary> | ||
78 | /// Gets the next attributes. | ||
79 | /// </summary> | ||
80 | /// <param name="celt">Count of elements.</param> | ||
81 | /// <param name="attributes">Array of attributes being returned.</param> | ||
82 | /// <returns>The next attribute.</returns> | ||
83 | uint Next( | ||
84 | [In] uint celt, | ||
85 | [Out, MarshalAs(UnmanagedType.LPArray)] IDENTITY_ATTRIBUTE[] attributes); | ||
86 | |||
87 | /// <summary> | ||
88 | /// Copy the current attribute into a buffer. | ||
89 | /// </summary> | ||
90 | /// <param name="available">Number of available bytes.</param> | ||
91 | /// <param name="data">Buffer into which attribute should be written.</param> | ||
92 | /// <returns>Pointer to buffer containing the attribute.</returns> | ||
93 | IntPtr CurrentIntoBuffer( | ||
94 | [In] IntPtr /*SIZE_T*/ available, | ||
95 | [Out, MarshalAs(UnmanagedType.LPArray)] byte[] data); | ||
96 | |||
97 | /// <summary> | ||
98 | /// Skip past a number of elements. | ||
99 | /// </summary> | ||
100 | /// <param name="celt">Count of elements to skip.</param> | ||
101 | void Skip([In] uint celt); | ||
102 | |||
103 | /// <summary> | ||
104 | /// Reset the enumeration to the beginning. | ||
105 | /// </summary> | ||
106 | void Reset(); | ||
107 | |||
108 | /// <summary> | ||
109 | /// Clone this attribute enumeration. | ||
110 | /// </summary> | ||
111 | /// <returns>Clone of a IEnumIDENTITY_ATTRIBUTE.</returns> | ||
112 | IEnumIDENTITY_ATTRIBUTE Clone(); | ||
113 | } | ||
114 | |||
115 | /// <summary> | ||
116 | /// Gets the guid. | ||
117 | /// </summary> | ||
118 | public static Guid ReferenceIdentityGuid | ||
119 | { | ||
120 | get { return referenceIdentityGuid; } | ||
121 | } | ||
122 | |||
123 | /// <summary> | ||
124 | /// Gets an interface pointer to an object with the specified IID, in the assembly at the specified file path. | ||
125 | /// </summary> | ||
126 | /// <param name="wszAssemblyPath">A valid path to the requested assembly.</param> | ||
127 | /// <param name="riid">The IID of the interface to return.</param> | ||
128 | /// <param name="i">The returned interface pointer.</param> | ||
129 | /// <returns>The error code.</returns> | ||
130 | [DllImport("mscorwks.dll", CharSet = CharSet.Unicode, EntryPoint = "GetAssemblyIdentityFromFile")] | ||
131 | internal static extern uint GetAssemblyIdentityFromFile(System.String wszAssemblyPath, ref Guid riid, out IReferenceIdentity i); | ||
132 | |||
133 | /// <summary> | ||
134 | /// Assembly attributes. Contains data about an IReferenceIdentity. | ||
135 | /// </summary> | ||
136 | [StructLayout(LayoutKind.Sequential)] | ||
137 | internal struct IDENTITY_ATTRIBUTE | ||
138 | { | ||
139 | [MarshalAs(UnmanagedType.LPWStr)] | ||
140 | public string AttributeNamespace; | ||
141 | [MarshalAs(UnmanagedType.LPWStr)] | ||
142 | public string AttributeName; | ||
143 | [MarshalAs(UnmanagedType.LPWStr)] | ||
144 | public string AttributeValue; | ||
145 | } | ||
146 | } | ||
147 | } | ||
diff --git a/src/WixToolset.Core/Cab/CabinetFileInfo.cs b/src/WixToolset.Core/Cab/CabinetFileInfo.cs index 849bb3bb..816f9e3e 100644 --- a/src/WixToolset.Core/Cab/CabinetFileInfo.cs +++ b/src/WixToolset.Core/Cab/CabinetFileInfo.cs | |||
@@ -1,19 +1,12 @@ | |||
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 | 3 | namespace WixToolset.Core.Cab |
4 | { | 4 | { |
5 | using System; | ||
6 | |||
7 | /// <summary> | 5 | /// <summary> |
8 | /// Properties of a file in a cabinet. | 6 | /// Properties of a file in a cabinet. |
9 | /// </summary> | 7 | /// </summary> |
10 | internal sealed class CabinetFileInfo | 8 | public sealed class CabinetFileInfo |
11 | { | 9 | { |
12 | private string fileId; | ||
13 | private ushort date; | ||
14 | private ushort time; | ||
15 | private int size; | ||
16 | |||
17 | /// <summary> | 10 | /// <summary> |
18 | /// Constructs CabinetFileInfo | 11 | /// Constructs CabinetFileInfo |
19 | /// </summary> | 12 | /// </summary> |
@@ -22,43 +15,31 @@ namespace WixToolset | |||
22 | /// <param name="time">Last modified time (MS-DOS time)</param> | 15 | /// <param name="time">Last modified time (MS-DOS time)</param> |
23 | public CabinetFileInfo(string fileId, ushort date, ushort time, int size) | 16 | public CabinetFileInfo(string fileId, ushort date, ushort time, int size) |
24 | { | 17 | { |
25 | this.fileId = fileId; | 18 | this.FileId = fileId; |
26 | this.date = date; | 19 | this.Date = date; |
27 | this.time = time; | 20 | this.Time = time; |
28 | this.size = size; | 21 | this.Size = size; |
29 | } | 22 | } |
30 | 23 | ||
31 | /// <summary> | 24 | /// <summary> |
32 | /// Gets the file Id of the file. | 25 | /// Gets the file Id of the file. |
33 | /// </summary> | 26 | /// </summary> |
34 | /// <value>file Id</value> | 27 | /// <value>file Id</value> |
35 | public string FileId | 28 | public string FileId { get; } |
36 | { | ||
37 | get { return this.fileId; } | ||
38 | } | ||
39 | 29 | ||
40 | /// <summary> | 30 | /// <summary> |
41 | /// Gets modified date (DOS format). | 31 | /// Gets modified date (DOS format). |
42 | /// </summary> | 32 | /// </summary> |
43 | public ushort Date | 33 | public ushort Date { get; } |
44 | { | ||
45 | get { return this.date; } | ||
46 | } | ||
47 | 34 | ||
48 | /// <summary> | 35 | /// <summary> |
49 | /// Gets modified time (DOS format). | 36 | /// Gets modified time (DOS format). |
50 | /// </summary> | 37 | /// </summary> |
51 | public ushort Time | 38 | public ushort Time { get; } |
52 | { | ||
53 | get { return this.time; } | ||
54 | } | ||
55 | 39 | ||
56 | /// <summary> | 40 | /// <summary> |
57 | /// Gets the size of the file in bytes. | 41 | /// Gets the size of the file in bytes. |
58 | /// </summary> | 42 | /// </summary> |
59 | public int Size | 43 | public int Size { get; } |
60 | { | ||
61 | get { return this.size; } | ||
62 | } | ||
63 | } | 44 | } |
64 | } | 45 | } |
diff --git a/src/WixToolset.Core/Cab/WixCreateCab.cs b/src/WixToolset.Core/Cab/WixCreateCab.cs index 8f985a43..4ebdd1c0 100644 --- a/src/WixToolset.Core/Cab/WixCreateCab.cs +++ b/src/WixToolset.Core/Cab/WixCreateCab.cs | |||
@@ -1,12 +1,12 @@ | |||
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.Cab | 3 | namespace WixToolset.Core.Cab |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Globalization; | 6 | using System.Globalization; |
7 | using System.IO; | 7 | using System.IO; |
8 | using System.Runtime.InteropServices; | 8 | using System.Runtime.InteropServices; |
9 | using WixToolset.Bind.Databases; | 9 | using WixToolset.Core.Bind; |
10 | using WixToolset.Core.Native; | 10 | using WixToolset.Core.Native; |
11 | using WixToolset.Data; | 11 | using WixToolset.Data; |
12 | 12 | ||
diff --git a/src/WixToolset.Core/Cab/WixEnumerateCab.cs b/src/WixToolset.Core/Cab/WixEnumerateCab.cs index 017eeffb..0b4055d6 100644 --- a/src/WixToolset.Core/Cab/WixEnumerateCab.cs +++ b/src/WixToolset.Core/Cab/WixEnumerateCab.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.Cab | 3 | namespace WixToolset.Core.Cab |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
@@ -10,7 +10,7 @@ namespace WixToolset.Cab | |||
10 | /// <summary> | 10 | /// <summary> |
11 | /// Wrapper class around interop with wixcab.dll to enumerate files from a cabinet. | 11 | /// Wrapper class around interop with wixcab.dll to enumerate files from a cabinet. |
12 | /// </summary> | 12 | /// </summary> |
13 | internal sealed class WixEnumerateCab : IDisposable | 13 | public sealed class WixEnumerateCab : IDisposable |
14 | { | 14 | { |
15 | private bool disposed; | 15 | private bool disposed; |
16 | private List<CabinetFileInfo> fileInfoList; | 16 | private List<CabinetFileInfo> fileInfoList; |
@@ -38,7 +38,7 @@ namespace WixToolset.Cab | |||
38 | /// </summary> | 38 | /// </summary> |
39 | /// <param name="cabinetFile">path to cabinet</param> | 39 | /// <param name="cabinetFile">path to cabinet</param> |
40 | /// <returns>list of CabinetFileInfo</returns> | 40 | /// <returns>list of CabinetFileInfo</returns> |
41 | internal List<CabinetFileInfo> Enumerate(string cabinetFile) | 41 | public List<CabinetFileInfo> Enumerate(string cabinetFile) |
42 | { | 42 | { |
43 | this.fileInfoList = new List<CabinetFileInfo>(); | 43 | this.fileInfoList = new List<CabinetFileInfo>(); |
44 | 44 | ||
diff --git a/src/WixToolset.Core/Cab/WixExtractCab.cs b/src/WixToolset.Core/Cab/WixExtractCab.cs index debdaf15..e776b08e 100644 --- a/src/WixToolset.Core/Cab/WixExtractCab.cs +++ b/src/WixToolset.Core/Cab/WixExtractCab.cs | |||
@@ -1,9 +1,8 @@ | |||
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.Cab | 3 | namespace WixToolset.Core.Cab |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Runtime.InteropServices; | ||
7 | using WixToolset.Core.Native; | 6 | using WixToolset.Core.Native; |
8 | 7 | ||
9 | /// <summary> | 8 | /// <summary> |
diff --git a/src/WixToolset.Core/CommandLine/BuildCommand.cs b/src/WixToolset.Core/CommandLine/BuildCommand.cs index afb9e829..32da5bcf 100644 --- a/src/WixToolset.Core/CommandLine/BuildCommand.cs +++ b/src/WixToolset.Core/CommandLine/BuildCommand.cs | |||
@@ -7,13 +7,14 @@ namespace WixToolset.Core | |||
7 | using System.IO; | 7 | using System.IO; |
8 | using System.Linq; | 8 | using System.Linq; |
9 | using WixToolset.Data; | 9 | using WixToolset.Data; |
10 | using WixToolset.Data.Rows; | ||
10 | using WixToolset.Extensibility; | 11 | using WixToolset.Extensibility; |
11 | 12 | ||
12 | internal class BuildCommand : ICommandLineCommand | 13 | internal class BuildCommand : ICommandLineCommand |
13 | { | 14 | { |
14 | public BuildCommand(ExtensionManager extensions, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, IEnumerable<string> locFiles, IEnumerable<string> libraryFiles, string outputPath, OutputType outputType, IEnumerable<string> cultures, bool bindFiles, IEnumerable<BindPath> bindPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile, string wixProjectFile) | 15 | public BuildCommand(ExtensionManager extensions, IEnumerable<SourceFile> sources, IDictionary<string, string> preprocessorVariables, IEnumerable<string> locFiles, IEnumerable<string> libraryFiles, string outputPath, OutputType outputType, string cabCachePath, IEnumerable<string> cultures, bool bindFiles, IEnumerable<BindPath> bindPaths, string intermediateFolder, string contentsFile, string outputsFile, string builtOutputsFile, string wixProjectFile) |
15 | { | 16 | { |
16 | this.Extensions = extensions; | 17 | this.ExtensionManager = extensions; |
17 | this.LocFiles = locFiles; | 18 | this.LocFiles = locFiles; |
18 | this.LibraryFiles = libraryFiles; | 19 | this.LibraryFiles = libraryFiles; |
19 | this.PreprocessorVariables = preprocessorVariables; | 20 | this.PreprocessorVariables = preprocessorVariables; |
@@ -21,6 +22,7 @@ namespace WixToolset.Core | |||
21 | this.OutputPath = outputPath; | 22 | this.OutputPath = outputPath; |
22 | this.OutputType = outputType; | 23 | this.OutputType = outputType; |
23 | 24 | ||
25 | this.CabCachePath = cabCachePath; | ||
24 | this.Cultures = cultures; | 26 | this.Cultures = cultures; |
25 | this.BindFiles = bindFiles; | 27 | this.BindFiles = bindFiles; |
26 | this.BindPaths = bindPaths; | 28 | this.BindPaths = bindPaths; |
@@ -32,7 +34,7 @@ namespace WixToolset.Core | |||
32 | this.WixProjectFile = wixProjectFile; | 34 | this.WixProjectFile = wixProjectFile; |
33 | } | 35 | } |
34 | 36 | ||
35 | public ExtensionManager Extensions { get; } | 37 | public ExtensionManager ExtensionManager { get; } |
36 | 38 | ||
37 | public IEnumerable<string> LocFiles { get; } | 39 | public IEnumerable<string> LocFiles { get; } |
38 | 40 | ||
@@ -46,6 +48,8 @@ namespace WixToolset.Core | |||
46 | 48 | ||
47 | private OutputType OutputType { get; } | 49 | private OutputType OutputType { get; } |
48 | 50 | ||
51 | public string CabCachePath { get; } | ||
52 | |||
49 | public IEnumerable<string> Cultures { get; } | 53 | public IEnumerable<string> Cultures { get; } |
50 | 54 | ||
51 | public bool BindFiles { get; } | 55 | public bool BindFiles { get; } |
@@ -70,7 +74,9 @@ namespace WixToolset.Core | |||
70 | 74 | ||
71 | if (this.OutputType == OutputType.Library) | 75 | if (this.OutputType == OutputType.Library) |
72 | { | 76 | { |
73 | this.LibraryPhase(intermediates, tableDefinitions); | 77 | var library = this.LibraryPhase(intermediates, tableDefinitions); |
78 | |||
79 | library?.Save(this.OutputPath); | ||
74 | } | 80 | } |
75 | else | 81 | else |
76 | { | 82 | { |
@@ -105,51 +111,40 @@ namespace WixToolset.Core | |||
105 | return intermediates; | 111 | return intermediates; |
106 | } | 112 | } |
107 | 113 | ||
108 | private void LibraryPhase(IEnumerable<Intermediate> intermediates, TableDefinitionCollection tableDefinitions) | 114 | private Library LibraryPhase(IEnumerable<Intermediate> intermediates, TableDefinitionCollection tableDefinitions) |
109 | { | 115 | { |
110 | var localizations = this.LoadLocalizationFiles(tableDefinitions).ToList(); | 116 | var localizations = this.LoadLocalizationFiles(tableDefinitions).ToList(); |
111 | 117 | ||
112 | // If there was an error adding localization files, then bail. | 118 | // If there was an error adding localization files, then bail. |
113 | if (Messaging.Instance.EncounteredError) | 119 | if (Messaging.Instance.EncounteredError) |
114 | { | 120 | { |
115 | return; | 121 | return null; |
116 | } | 122 | } |
117 | 123 | ||
118 | var sections = intermediates.SelectMany(i => i.Sections).ToList(); | 124 | var resolver = CreateWixResolverWithVariables(null, null); |
119 | |||
120 | LibraryBinaryFileResolver resolver = null; | ||
121 | |||
122 | if (this.BindFiles) | ||
123 | { | ||
124 | resolver = new LibraryBinaryFileResolver(); | ||
125 | resolver.FileManagers = new List<IBinderFileManager> { new BinderFileManager() }; ; | ||
126 | resolver.VariableResolver = new WixVariableResolver(); | ||
127 | |||
128 | BinderFileManagerCore core = new BinderFileManagerCore(); | ||
129 | core.AddBindPaths(this.BindPaths, BindStage.Normal); | ||
130 | |||
131 | foreach (var fileManager in resolver.FileManagers) | ||
132 | { | ||
133 | fileManager.Core = core; | ||
134 | } | ||
135 | } | ||
136 | 125 | ||
137 | var librarian = new Librarian(); | 126 | var context = new LibraryContext(); |
127 | context.BindFiles = this.BindFiles; | ||
128 | context.BindPaths = this.BindPaths; | ||
129 | context.Extensions = this.ExtensionManager.Create<ILibrarianExtension>(); | ||
130 | context.Localizations = localizations; | ||
131 | context.Sections = intermediates.SelectMany(i => i.Sections).ToList(); | ||
132 | context.WixVariableResolver = resolver; | ||
138 | 133 | ||
139 | var library = librarian.Combine(sections, localizations, resolver); | 134 | var librarian = new Librarian(context); |
140 | 135 | ||
141 | library?.Save(this.OutputPath); | 136 | return librarian.Combine(); |
142 | } | 137 | } |
143 | 138 | ||
144 | private Output LinkPhase(IEnumerable<Intermediate> intermediates, TableDefinitionCollection tableDefinitions) | 139 | private Output LinkPhase(IEnumerable<Intermediate> intermediates, TableDefinitionCollection tableDefinitions) |
145 | { | 140 | { |
146 | var sections = intermediates.SelectMany(i => i.Sections).ToList(); | 141 | var sections = intermediates.SelectMany(i => i.Sections).ToList(); |
147 | 142 | ||
148 | sections.AddRange(SectionsFromLibraries(tableDefinitions)); | 143 | sections.AddRange(this.SectionsFromLibraries(tableDefinitions)); |
149 | 144 | ||
150 | var linker = new Linker(); | 145 | var linker = new Linker(); |
151 | 146 | ||
152 | foreach (var data in this.Extensions.Create<IExtensionData>()) | 147 | foreach (var data in this.ExtensionManager.Create<IExtensionData>()) |
153 | { | 148 | { |
154 | linker.AddExtensionData(data); | 149 | linker.AddExtensionData(data); |
155 | } | 150 | } |
@@ -159,6 +154,40 @@ namespace WixToolset.Core | |||
159 | return output; | 154 | return output; |
160 | } | 155 | } |
161 | 156 | ||
157 | private void BindPhase(Output output, TableDefinitionCollection tableDefinitions) | ||
158 | { | ||
159 | var localizations = this.LoadLocalizationFiles(tableDefinitions).ToList(); | ||
160 | |||
161 | var localizer = new Localizer(localizations); | ||
162 | |||
163 | var resolver = CreateWixResolverWithVariables(localizer, output); | ||
164 | |||
165 | var context = new BindContext(); | ||
166 | context.Messaging = Messaging.Instance; | ||
167 | context.ExtensionManager = this.ExtensionManager; | ||
168 | context.BindPaths = this.BindPaths ?? Array.Empty<BindPath>(); | ||
169 | //context.CabbingThreadCount = this.CabbingThreadCount; | ||
170 | context.CabCachePath = this.CabCachePath; | ||
171 | context.Codepage = localizer.Codepage; | ||
172 | //context.DefaultCompressionLevel = this.DefaultCompressionLevel; | ||
173 | //context.Ices = this.Ices; | ||
174 | context.IntermediateFolder = this.IntermediateFolder; | ||
175 | context.IntermediateRepresentation = output; | ||
176 | context.OutputPath = this.OutputPath; | ||
177 | context.OutputPdbPath = Path.ChangeExtension(this.OutputPath, ".wixpdb"); | ||
178 | //context.SuppressIces = this.SuppressIces; | ||
179 | context.SuppressValidation = true; | ||
180 | //context.SuppressValidation = this.SuppressValidation; | ||
181 | context.WixVariableResolver = resolver; | ||
182 | context.ContentsFile = this.ContentsFile; | ||
183 | context.OutputsFile = this.OutputsFile; | ||
184 | context.BuiltOutputsFile = this.BuiltOutputsFile; | ||
185 | context.WixprojectFile = this.WixProjectFile; | ||
186 | |||
187 | var binder = new Binder(context); | ||
188 | binder.Bind(); | ||
189 | } | ||
190 | |||
162 | private IEnumerable<Section> SectionsFromLibraries(TableDefinitionCollection tableDefinitions) | 191 | private IEnumerable<Section> SectionsFromLibraries(TableDefinitionCollection tableDefinitions) |
163 | { | 192 | { |
164 | var sections = new List<Section>(); | 193 | var sections = new List<Section>(); |
@@ -187,34 +216,6 @@ namespace WixToolset.Core | |||
187 | return sections; | 216 | return sections; |
188 | } | 217 | } |
189 | 218 | ||
190 | private void BindPhase(Output output, TableDefinitionCollection tableDefinitions) | ||
191 | { | ||
192 | var localizations = this.LoadLocalizationFiles(tableDefinitions).ToList(); | ||
193 | |||
194 | var localizer = new Localizer(localizations); | ||
195 | |||
196 | var resolver = new WixVariableResolver(localizer); | ||
197 | |||
198 | var binder = new Binder(); | ||
199 | binder.TempFilesLocation = this.IntermediateFolder; | ||
200 | binder.WixVariableResolver = resolver; | ||
201 | binder.SuppressValidation = true; | ||
202 | |||
203 | binder.ContentsFile = this.ContentsFile; | ||
204 | binder.OutputsFile = this.OutputsFile; | ||
205 | binder.BuiltOutputsFile = this.BuiltOutputsFile; | ||
206 | binder.WixprojectFile = this.WixProjectFile; | ||
207 | |||
208 | if (this.BindPaths != null) | ||
209 | { | ||
210 | binder.BindPaths.AddRange(this.BindPaths); | ||
211 | } | ||
212 | |||
213 | binder.AddExtension(new BinderFileManager()); | ||
214 | |||
215 | binder.Bind(output, this.OutputPath); | ||
216 | } | ||
217 | |||
218 | private IEnumerable<Localization> LoadLocalizationFiles(TableDefinitionCollection tableDefinitions) | 219 | private IEnumerable<Localization> LoadLocalizationFiles(TableDefinitionCollection tableDefinitions) |
219 | { | 220 | { |
220 | foreach (var loc in this.LocFiles) | 221 | foreach (var loc in this.LocFiles) |
@@ -225,30 +226,21 @@ namespace WixToolset.Core | |||
225 | } | 226 | } |
226 | } | 227 | } |
227 | 228 | ||
228 | /// <summary> | 229 | private static WixVariableResolver CreateWixResolverWithVariables(Localizer localizer, Output output) |
229 | /// File resolution mechanism to create binary library. | ||
230 | /// </summary> | ||
231 | private class LibraryBinaryFileResolver : ILibraryBinaryFileResolver | ||
232 | { | 230 | { |
233 | public IEnumerable<IBinderFileManager> FileManagers { get; set; } | 231 | var resolver = new WixVariableResolver(localizer); |
234 | |||
235 | public WixVariableResolver VariableResolver { get; set; } | ||
236 | 232 | ||
237 | public string Resolve(SourceLineNumber sourceLineNumber, string table, string path) | 233 | // Gather all the wix variables. |
234 | Table wixVariableTable = output?.Tables["WixVariable"]; | ||
235 | if (null != wixVariableTable) | ||
238 | { | 236 | { |
239 | string resolvedPath = this.VariableResolver.ResolveVariables(sourceLineNumber, path, false); | 237 | foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) |
240 | |||
241 | foreach (IBinderFileManager fileManager in this.FileManagers) | ||
242 | { | 238 | { |
243 | string finalPath = fileManager.ResolveFile(resolvedPath, table, sourceLineNumber, BindStage.Normal); | 239 | resolver.AddVariable(wixVariableRow); |
244 | if (!String.IsNullOrEmpty(finalPath)) | ||
245 | { | ||
246 | return finalPath; | ||
247 | } | ||
248 | } | 240 | } |
249 | |||
250 | return null; | ||
251 | } | 241 | } |
242 | |||
243 | return resolver; | ||
252 | } | 244 | } |
253 | } | 245 | } |
254 | } | 246 | } |
diff --git a/src/WixToolset.Core/CommandLine/CommandLine.cs b/src/WixToolset.Core/CommandLine/CommandLine.cs index a3a6831c..2f203ecb 100644 --- a/src/WixToolset.Core/CommandLine/CommandLine.cs +++ b/src/WixToolset.Core/CommandLine/CommandLine.cs | |||
@@ -6,6 +6,7 @@ namespace WixToolset.Core | |||
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
7 | using System.IO; | 7 | using System.IO; |
8 | using System.Linq; | 8 | using System.Linq; |
9 | using System.Reflection; | ||
9 | using System.Text; | 10 | using System.Text; |
10 | using System.Text.RegularExpressions; | 11 | using System.Text.RegularExpressions; |
11 | using WixToolset.Data; | 12 | using WixToolset.Data; |
@@ -71,6 +72,7 @@ namespace WixToolset.Core | |||
71 | 72 | ||
72 | var intermediateFolder = String.Empty; | 73 | var intermediateFolder = String.Empty; |
73 | 74 | ||
75 | var cabCachePath = String.Empty; | ||
74 | var cultures = new List<string>(); | 76 | var cultures = new List<string>(); |
75 | var contentsFile = String.Empty; | 77 | var contentsFile = String.Empty; |
76 | var outputsFile = String.Empty; | 78 | var outputsFile = String.Empty; |
@@ -98,6 +100,10 @@ namespace WixToolset.Core | |||
98 | cmdline.GetNextArgumentOrError(bindPaths); | 100 | cmdline.GetNextArgumentOrError(bindPaths); |
99 | return true; | 101 | return true; |
100 | 102 | ||
103 | case "cc": | ||
104 | cmdline.GetNextArgumentOrError(ref cabCachePath); | ||
105 | return true; | ||
106 | |||
101 | case "cultures": | 107 | case "cultures": |
102 | cmdline.GetNextArgumentOrError(cultures); | 108 | cmdline.GetNextArgumentOrError(cultures); |
103 | return true; | 109 | return true; |
@@ -190,12 +196,14 @@ namespace WixToolset.Core | |||
190 | { | 196 | { |
191 | case Commands.Build: | 197 | case Commands.Build: |
192 | { | 198 | { |
199 | LoadStandardBackends(cli.ExtensionManager); | ||
200 | |||
193 | var sourceFiles = GatherSourceFiles(files, outputFolder); | 201 | var sourceFiles = GatherSourceFiles(files, outputFolder); |
194 | var variables = GatherPreprocessorVariables(defines); | 202 | var variables = GatherPreprocessorVariables(defines); |
195 | var bindPathList = GatherBindPaths(bindPaths); | 203 | var bindPathList = GatherBindPaths(bindPaths); |
196 | var extensions = cli.ExtensionManager; | 204 | var extensions = cli.ExtensionManager; |
197 | var type = CalculateOutputType(outputType, outputFile); | 205 | var type = CalculateOutputType(outputType, outputFile); |
198 | return new BuildCommand(extensions, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile, wixProjectFile); | 206 | return new BuildCommand(extensions, sourceFiles, variables, locFiles, libraryFiles, outputFile, type, cabCachePath, cultures, bindFiles, bindPathList, intermediateFolder, contentsFile, outputsFile, builtOutputsFile, wixProjectFile); |
199 | } | 207 | } |
200 | 208 | ||
201 | case Commands.Compile: | 209 | case Commands.Compile: |
@@ -209,6 +217,18 @@ namespace WixToolset.Core | |||
209 | return null; | 217 | return null; |
210 | } | 218 | } |
211 | 219 | ||
220 | private static void LoadStandardBackends(ExtensionManager extensionManager) | ||
221 | { | ||
222 | var folder = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath); | ||
223 | |||
224 | foreach (var backendAssemblyName in new[] { "WixToolset.Core.Burn.dll", "WixToolset.Core.WindowsInstaller.dll" }) | ||
225 | { | ||
226 | var path = Path.Combine(folder, backendAssemblyName); | ||
227 | |||
228 | extensionManager.Load(path); | ||
229 | } | ||
230 | } | ||
231 | |||
212 | private static OutputType CalculateOutputType(string outputType, string outputFile) | 232 | private static OutputType CalculateOutputType(string outputType, string outputFile) |
213 | { | 233 | { |
214 | if (String.IsNullOrEmpty(outputType)) | 234 | if (String.IsNullOrEmpty(outputType)) |
diff --git a/src/WixToolset.Core/Common.cs b/src/WixToolset.Core/Common.cs index a2881984..28e7ee7b 100644 --- a/src/WixToolset.Core/Common.cs +++ b/src/WixToolset.Core/Common.cs | |||
@@ -17,7 +17,7 @@ namespace WixToolset | |||
17 | /// <summary> | 17 | /// <summary> |
18 | /// Common Wix utility methods and types. | 18 | /// Common Wix utility methods and types. |
19 | /// </summary> | 19 | /// </summary> |
20 | internal static class Common | 20 | public static class Common |
21 | { | 21 | { |
22 | //------------------------------------------------------------------------------------------------- | 22 | //------------------------------------------------------------------------------------------------- |
23 | // Layout of an Access Mask (from http://technet.microsoft.com/en-us/library/cc783530(WS.10).aspx) | 23 | // Layout of an Access Mask (from http://technet.microsoft.com/en-us/library/cc783530(WS.10).aspx) |
@@ -89,9 +89,7 @@ namespace WixToolset | |||
89 | // FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) | 89 | // FILE_ALL_ACCESS (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF) |
90 | internal static readonly string[] FilePermissions = { "Read", "Write", "Append", "ReadExtendedAttributes", "WriteExtendedAttributes", "Execute", "FileAllRights", "ReadAttributes", "WriteAttributes" }; | 90 | internal static readonly string[] FilePermissions = { "Read", "Write", "Append", "ReadExtendedAttributes", "WriteExtendedAttributes", "Execute", "FileAllRights", "ReadAttributes", "WriteAttributes" }; |
91 | 91 | ||
92 | internal static readonly string[] ReservedFileNames = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; | 92 | public static readonly Regex WixVariableRegex = new Regex(@"(\!|\$)\((?<namespace>loc|wix|bind|bindpath)\.(?<fullname>(?<name>[_A-Za-z][0-9A-Za-z_]+)(\.(?<scope>[_A-Za-z][0-9A-Za-z_\.]*))?)(\=(?<value>.+?))?\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); |
93 | |||
94 | internal static readonly Regex WixVariableRegex = new Regex(@"(\!|\$)\((?<namespace>loc|wix|bind|bindpath)\.(?<fullname>(?<name>[_A-Za-z][0-9A-Za-z_]+)(\.(?<scope>[_A-Za-z][0-9A-Za-z_\.]*))?)(\=(?<value>.+?))?\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.ExplicitCapture); | ||
95 | 93 | ||
96 | internal const char CustomRowFieldSeparator = '\x85'; | 94 | internal const char CustomRowFieldSeparator = '\x85'; |
97 | 95 | ||
@@ -170,15 +168,14 @@ namespace WixToolset | |||
170 | /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> | 168 | /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception> |
171 | /// <exception cref="NotSupportedException">The value doesn't not represent a valid code page name or integer value.</exception> | 169 | /// <exception cref="NotSupportedException">The value doesn't not represent a valid code page name or integer value.</exception> |
172 | /// <exception cref="WixException">The code page is invalid for summary information.</exception> | 170 | /// <exception cref="WixException">The code page is invalid for summary information.</exception> |
173 | internal static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) | 171 | public static int GetValidCodePage(string value, bool allowNoChange = false, bool onlyAnsi = false, SourceLineNumber sourceLineNumbers = null) |
174 | { | 172 | { |
175 | int codePage; | ||
176 | Encoding encoding; | ||
177 | |||
178 | try | 173 | try |
179 | { | 174 | { |
175 | Encoding encoding; | ||
176 | |||
180 | // check if a integer as a string was passed | 177 | // check if a integer as a string was passed |
181 | if (Int32.TryParse(value, out codePage)) | 178 | if (Int32.TryParse(value, out int codePage)) |
182 | { | 179 | { |
183 | if (0 == codePage) | 180 | if (0 == codePage) |
184 | { | 181 | { |
@@ -366,9 +363,9 @@ namespace WixToolset | |||
366 | /// Generate a new Windows Installer-friendly guid. | 363 | /// Generate a new Windows Installer-friendly guid. |
367 | /// </summary> | 364 | /// </summary> |
368 | /// <returns>A new guid.</returns> | 365 | /// <returns>A new guid.</returns> |
369 | internal static string GenerateGuid() | 366 | public static string GenerateGuid() |
370 | { | 367 | { |
371 | return Guid.NewGuid().ToString("B").ToUpper(CultureInfo.InvariantCulture); | 368 | return Guid.NewGuid().ToString("B").ToUpperInvariant(); |
372 | } | 369 | } |
373 | 370 | ||
374 | /// <summary> | 371 | /// <summary> |
@@ -465,7 +462,7 @@ namespace WixToolset | |||
465 | } | 462 | } |
466 | } | 463 | } |
467 | 464 | ||
468 | internal static string GetFileHash(string path) | 465 | public static string GetFileHash(string path) |
469 | { | 466 | { |
470 | using (SHA1Managed managed = new SHA1Managed()) | 467 | using (SHA1Managed managed = new SHA1Managed()) |
471 | { | 468 | { |
@@ -478,6 +475,147 @@ namespace WixToolset | |||
478 | } | 475 | } |
479 | 476 | ||
480 | /// <summary> | 477 | /// <summary> |
478 | /// Takes an id, and demodularizes it (if possible). | ||
479 | /// </summary> | ||
480 | /// <remarks> | ||
481 | /// If the output type is a module, returns a demodularized version of an id. Otherwise, returns the id. | ||
482 | /// </remarks> | ||
483 | /// <param name="outputType">The type of the output to bind.</param> | ||
484 | /// <param name="modularizationGuid">The modularization GUID.</param> | ||
485 | /// <param name="id">The id to demodularize.</param> | ||
486 | /// <returns>The demodularized id.</returns> | ||
487 | public static string Demodularize(OutputType outputType, string modularizationGuid, string id) | ||
488 | { | ||
489 | if (OutputType.Module == outputType && id.EndsWith(String.Concat(".", modularizationGuid), StringComparison.Ordinal)) | ||
490 | { | ||
491 | id = id.Substring(0, id.Length - 37); | ||
492 | } | ||
493 | |||
494 | return id; | ||
495 | } | ||
496 | |||
497 | /// <summary> | ||
498 | /// Get the source/target and short/long file names from an MSI Filename column. | ||
499 | /// </summary> | ||
500 | /// <param name="value">The Filename value.</param> | ||
501 | /// <returns>An array of strings of length 4. The contents are: short target, long target, short source, and long source.</returns> | ||
502 | /// <remarks> | ||
503 | /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings. | ||
504 | /// However, the returned array will always be of length 4. | ||
505 | /// </remarks> | ||
506 | public static string[] GetNames(string value) | ||
507 | { | ||
508 | string[] names = new string[4]; | ||
509 | int targetSeparator = value.IndexOf(":", StringComparison.Ordinal); | ||
510 | |||
511 | // split source and target | ||
512 | string sourceName = null; | ||
513 | string targetName = value; | ||
514 | if (0 <= targetSeparator) | ||
515 | { | ||
516 | sourceName = value.Substring(targetSeparator + 1); | ||
517 | targetName = value.Substring(0, targetSeparator); | ||
518 | } | ||
519 | |||
520 | // split the source short and long names | ||
521 | string sourceLongName = null; | ||
522 | if (null != sourceName) | ||
523 | { | ||
524 | int sourceLongNameSeparator = sourceName.IndexOf("|", StringComparison.Ordinal); | ||
525 | if (0 <= sourceLongNameSeparator) | ||
526 | { | ||
527 | sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); | ||
528 | sourceName = sourceName.Substring(0, sourceLongNameSeparator); | ||
529 | } | ||
530 | } | ||
531 | |||
532 | // split the target short and long names | ||
533 | int targetLongNameSeparator = targetName.IndexOf("|", StringComparison.Ordinal); | ||
534 | string targetLongName = null; | ||
535 | if (0 <= targetLongNameSeparator) | ||
536 | { | ||
537 | targetLongName = targetName.Substring(targetLongNameSeparator + 1); | ||
538 | targetName = targetName.Substring(0, targetLongNameSeparator); | ||
539 | } | ||
540 | |||
541 | // remove the long source name when its identical to the long source name | ||
542 | if (null != sourceName && sourceName == sourceLongName) | ||
543 | { | ||
544 | sourceLongName = null; | ||
545 | } | ||
546 | |||
547 | // remove the long target name when its identical to the long target name | ||
548 | if (null != targetName && targetName == targetLongName) | ||
549 | { | ||
550 | targetLongName = null; | ||
551 | } | ||
552 | |||
553 | // remove the source names when they are identical to the target names | ||
554 | if (sourceName == targetName && sourceLongName == targetLongName) | ||
555 | { | ||
556 | sourceName = null; | ||
557 | sourceLongName = null; | ||
558 | } | ||
559 | |||
560 | // target name(s) | ||
561 | if ("." != targetName) | ||
562 | { | ||
563 | names[0] = targetName; | ||
564 | } | ||
565 | |||
566 | if (null != targetLongName && "." != targetLongName) | ||
567 | { | ||
568 | names[1] = targetLongName; | ||
569 | } | ||
570 | |||
571 | // source name(s) | ||
572 | if (null != sourceName) | ||
573 | { | ||
574 | names[2] = sourceName; | ||
575 | } | ||
576 | |||
577 | if (null != sourceLongName && "." != sourceLongName) | ||
578 | { | ||
579 | names[3] = sourceLongName; | ||
580 | } | ||
581 | |||
582 | return names; | ||
583 | } | ||
584 | |||
585 | /// <summary> | ||
586 | /// Get a source/target and short/long file name from an MSI Filename column. | ||
587 | /// </summary> | ||
588 | /// <param name="value">The Filename value.</param> | ||
589 | /// <param name="source">true to get a source name; false to get a target name</param> | ||
590 | /// <param name="longName">true to get a long name; false to get a short name</param> | ||
591 | /// <returns>The name.</returns> | ||
592 | public static string GetName(string value, bool source, bool longName) | ||
593 | { | ||
594 | string[] names = GetNames(value); | ||
595 | |||
596 | if (source) | ||
597 | { | ||
598 | if (longName && null != names[3]) | ||
599 | { | ||
600 | return names[3]; | ||
601 | } | ||
602 | else if (null != names[2]) | ||
603 | { | ||
604 | return names[2]; | ||
605 | } | ||
606 | } | ||
607 | |||
608 | if (longName && null != names[1]) | ||
609 | { | ||
610 | return names[1]; | ||
611 | } | ||
612 | else | ||
613 | { | ||
614 | return names[0]; | ||
615 | } | ||
616 | } | ||
617 | |||
618 | /// <summary> | ||
481 | /// Get an attribute value. | 619 | /// Get an attribute value. |
482 | /// </summary> | 620 | /// </summary> |
483 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> | 621 | /// <param name="sourceLineNumbers">Source line information about the owner element.</param> |
diff --git a/src/WixToolset.Core/Compiler.cs b/src/WixToolset.Core/Compiler.cs index ed7cb60e..d085e788 100644 --- a/src/WixToolset.Core/Compiler.cs +++ b/src/WixToolset.Core/Compiler.cs | |||
@@ -11,11 +11,11 @@ namespace WixToolset | |||
11 | using System.IO; | 11 | using System.IO; |
12 | using System.Text.RegularExpressions; | 12 | using System.Text.RegularExpressions; |
13 | using System.Xml.Linq; | 13 | using System.Xml.Linq; |
14 | using WixToolset.Core; | ||
15 | using WixToolset.Core.Native; | ||
14 | using WixToolset.Data; | 16 | using WixToolset.Data; |
15 | using WixToolset.Data.Rows; | 17 | using WixToolset.Data.Rows; |
16 | using WixToolset.Extensibility; | 18 | using WixToolset.Extensibility; |
17 | using WixToolset.Msi; | ||
18 | using WixToolset.Core.Native; | ||
19 | using Wix = WixToolset.Data.Serialize; | 19 | using Wix = WixToolset.Data.Serialize; |
20 | 20 | ||
21 | /// <summary> | 21 | /// <summary> |
@@ -158,10 +158,7 @@ namespace WixToolset | |||
158 | [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] | 158 | [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes")] |
159 | public Intermediate Compile(XDocument source) | 159 | public Intermediate Compile(XDocument source) |
160 | { | 160 | { |
161 | if (null == source) | 161 | if (null == source) throw new ArgumentNullException(nameof(source)); |
162 | { | ||
163 | throw new ArgumentNullException("source"); | ||
164 | } | ||
165 | 162 | ||
166 | bool encounteredError = false; | 163 | bool encounteredError = false; |
167 | 164 | ||
@@ -220,9 +217,7 @@ namespace WixToolset | |||
220 | { | 217 | { |
221 | if (field.Data is string) | 218 | if (field.Data is string) |
222 | { | 219 | { |
223 | bool isDefault = false; | 220 | field.Data = this.componentIdPlaceholdersResolver.ResolveVariables(row.SourceLineNumbers, (string)field.Data, false, false, out var defaultIgnored, out var delayedIgnored); |
224 | bool delayedResolve = false; | ||
225 | field.Data = this.componentIdPlaceholdersResolver.ResolveVariables(row.SourceLineNumbers, (string)field.Data, false, false, ref isDefault, ref delayedResolve); | ||
226 | } | 221 | } |
227 | } | 222 | } |
228 | } | 223 | } |
@@ -470,7 +465,8 @@ namespace WixToolset | |||
470 | case "Advertise": | 465 | case "Advertise": |
471 | appIdAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); | 466 | appIdAdvertise = this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib); |
472 | break; | 467 | break; |
473 | case "Description": description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | 468 | case "Description": |
469 | description = this.core.GetAttributeValue(sourceLineNumbers, attrib); | ||
474 | break; | 470 | break; |
475 | case "DllSurrogate": | 471 | case "DllSurrogate": |
476 | dllSurrogate = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); | 472 | dllSurrogate = this.core.GetAttributeValue(sourceLineNumbers, attrib, EmptyRule.CanBeEmpty); |
@@ -9471,13 +9467,13 @@ namespace WixToolset | |||
9471 | targetProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib); | 9467 | targetProductName = this.core.GetAttributeValue(sourceLineNumbers, attrib); |
9472 | break; | 9468 | break; |
9473 | case "ApiPatchingSymbolNoImagehlpFlag": | 9469 | case "ApiPatchingSymbolNoImagehlpFlag": |
9474 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP : 0; | 9470 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP : 0; |
9475 | break; | 9471 | break; |
9476 | case "ApiPatchingSymbolNoFailuresFlag": | 9472 | case "ApiPatchingSymbolNoFailuresFlag": |
9477 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES : 0; | 9473 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES : 0; |
9478 | break; | 9474 | break; |
9479 | case "ApiPatchingSymbolUndecoratedTooFlag": | 9475 | case "ApiPatchingSymbolUndecoratedTooFlag": |
9480 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchAPI.PatchInterop.PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO : 0; | 9476 | apiPatchingSymbolFlags |= (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)) ? (int)PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO : 0; |
9481 | break; | 9477 | break; |
9482 | case "OptimizePatchSizeForLargeFiles": | 9478 | case "OptimizePatchSizeForLargeFiles": |
9483 | optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); | 9479 | optimizePatchSizeForLargeFiles = (YesNoType.Yes == this.core.GetAttributeYesNoValue(sourceLineNumbers, attrib)); |
@@ -11802,7 +11798,7 @@ namespace WixToolset | |||
11802 | private void ParseProductElement(XElement node) | 11798 | private void ParseProductElement(XElement node) |
11803 | { | 11799 | { |
11804 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); | 11800 | SourceLineNumber sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node); |
11805 | int codepage = 0; | 11801 | int codepage = 65001; |
11806 | string productCode = null; | 11802 | string productCode = null; |
11807 | string upgradeCode = null; | 11803 | string upgradeCode = null; |
11808 | string manufacturer = null; | 11804 | string manufacturer = null; |
diff --git a/src/WixToolset.Core/CompilerCore.cs b/src/WixToolset.Core/CompilerCore.cs index 8640a2da..8f4703f7 100644 --- a/src/WixToolset.Core/CompilerCore.cs +++ b/src/WixToolset.Core/CompilerCore.cs | |||
@@ -45,10 +45,6 @@ namespace WixToolset | |||
45 | internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; | 45 | internal static readonly XNamespace W3SchemaPrefix = "http://www.w3.org/"; |
46 | internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; | 46 | internal static readonly XNamespace WixNamespace = "http://wixtoolset.org/schemas/v4/wxs"; |
47 | 47 | ||
48 | public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB | ||
49 | public const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB | ||
50 | public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) | ||
51 | |||
52 | private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled); | 48 | private static readonly Regex AmbiguousFilename = new Regex(@"^.{6}\~\d", RegexOptions.Compiled); |
53 | 49 | ||
54 | private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * " | 50 | private const string IllegalLongFilenameCharacters = @"[\\\?|><:/\*""]"; // illegal: \ ? | > < : / * " |
@@ -67,6 +63,11 @@ namespace WixToolset | |||
67 | 63 | ||
68 | private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?<access>public|internal|protected|private)\s+)?(?<id>[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); | 64 | private static readonly Regex LegalIdentifierWithAccess = new Regex(@"^((?<access>public|internal|protected|private)\s+)?(?<id>[_A-Za-z][0-9A-Za-z_\.]*)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture); |
69 | 65 | ||
66 | public const int DefaultMaximumUncompressedMediaSize = 200; // Default value is 200 MB | ||
67 | public const int MinValueOfMaxCabSizeForLargeFileSplitting = 20; // 20 MB | ||
68 | public const int MaxValueOfMaxCabSizeForLargeFileSplitting = 2 * 1024; // 2048 MB (i.e. 2 GB) | ||
69 | |||
70 | |||
70 | // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) | 71 | // Built-in variables (from burn\engine\variable.cpp, "vrgBuiltInVariables", around line 113) |
71 | private static readonly List<String> BuiltinBundleVariables = new List<string>( | 72 | private static readonly List<String> BuiltinBundleVariables = new List<string>( |
72 | new string[] { | 73 | new string[] { |
diff --git a/src/WixToolset.Core/Data/messages.xml b/src/WixToolset.Core/Data/messages.xml index edc98147..d981e2d1 100644 --- a/src/WixToolset.Core/Data/messages.xml +++ b/src/WixToolset.Core/Data/messages.xml | |||
@@ -957,12 +957,6 @@ | |||
957 | <Parameter Type="System.String" Name="exceptionMessage" /> | 957 | <Parameter Type="System.String" Name="exceptionMessage" /> |
958 | </Instance> | 958 | </Instance> |
959 | </Message> | 959 | </Message> |
960 | <Message Id="InvalidFileName" Number="85"> | ||
961 | <Instance> | ||
962 | Invalid file name '{0}'. | ||
963 | <Parameter Type="System.String" Name="fileName" /> | ||
964 | </Instance> | ||
965 | </Message> | ||
966 | <Message Id="ReferenceLoopDetected" Number="86"> | 960 | <Message Id="ReferenceLoopDetected" Number="86"> |
967 | <Instance> | 961 | <Instance> |
968 | A circular reference of groups was detected. The infinite loop includes: {0}. Group references must form a directed acyclic graph. | 962 | A circular reference of groups was detected. The infinite loop includes: {0}. Group references must form a directed acyclic graph. |
@@ -2138,12 +2132,6 @@ | |||
2138 | This patch is not uninstallable. The 'Patch' element's attribute 'AllowRemoval' should be set to 'no'. | 2132 | This patch is not uninstallable. The 'Patch' element's attribute 'AllowRemoval' should be set to 'no'. |
2139 | </Instance> | 2133 | </Instance> |
2140 | </Message> | 2134 | </Message> |
2141 | <Message Id="PathTooLong" Number="262"> | ||
2142 | <Instance> | ||
2143 | '{0}' is too long, the fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters. | ||
2144 | <Parameter Type="System.String" Name="fileName" /> | ||
2145 | </Instance> | ||
2146 | </Message> | ||
2147 | <Message Id="FileTooLarge" Number="263"> | 2135 | <Message Id="FileTooLarge" Number="263"> |
2148 | <Instance> | 2136 | <Instance> |
2149 | '{0}' is too large, file size must be less than 2147483648. | 2137 | '{0}' is too large, file size must be less than 2147483648. |
diff --git a/src/WixToolset.Core/Decompiler.cs b/src/WixToolset.Core/Decompiler.cs index 249b5788..e72b0104 100644 --- a/src/WixToolset.Core/Decompiler.cs +++ b/src/WixToolset.Core/Decompiler.cs | |||
@@ -3,7 +3,6 @@ | |||
3 | namespace WixToolset | 3 | namespace WixToolset |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.CodeDom.Compiler; | ||
7 | using System.Collections; | 6 | using System.Collections; |
8 | using System.Collections.Generic; | 7 | using System.Collections.Generic; |
9 | using System.Collections.Specialized; | 8 | using System.Collections.Specialized; |
@@ -15,9 +14,9 @@ namespace WixToolset | |||
15 | using WixToolset.Data; | 14 | using WixToolset.Data; |
16 | using WixToolset.Data.Rows; | 15 | using WixToolset.Data.Rows; |
17 | using WixToolset.Extensibility; | 16 | using WixToolset.Extensibility; |
18 | using WixToolset.Msi; | ||
19 | using WixToolset.Core.Native; | 17 | using WixToolset.Core.Native; |
20 | using Wix = WixToolset.Data.Serialize; | 18 | using Wix = WixToolset.Data.Serialize; |
19 | using WixToolset.Core; | ||
21 | 20 | ||
22 | /// <summary> | 21 | /// <summary> |
23 | /// Decompiles an msi database into WiX source. | 22 | /// Decompiles an msi database into WiX source. |
@@ -5201,7 +5200,7 @@ namespace WixToolset | |||
5201 | 5200 | ||
5202 | directory.Id = Convert.ToString(row[0]); | 5201 | directory.Id = Convert.ToString(row[0]); |
5203 | 5202 | ||
5204 | string[] names = Installer.GetNames(Convert.ToString(row[2])); | 5203 | string[] names = Common.GetNames(Convert.ToString(row[2])); |
5205 | 5204 | ||
5206 | if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) | 5205 | if (String.Equals(directory.Id, "TARGETDIR", StringComparison.Ordinal) && !String.Equals(names[0], "SourceDir", StringComparison.Ordinal)) |
5207 | { | 5206 | { |
@@ -5319,7 +5318,7 @@ namespace WixToolset | |||
5319 | 5318 | ||
5320 | if (null != row[3]) | 5319 | if (null != row[3]) |
5321 | { | 5320 | { |
5322 | string[] names = Installer.GetNames(Convert.ToString(row[3])); | 5321 | string[] names = Common.GetNames(Convert.ToString(row[3])); |
5323 | if (null != names[0] && null != names[1]) | 5322 | if (null != names[0] && null != names[1]) |
5324 | { | 5323 | { |
5325 | copyFile.DestinationShortName = names[0]; | 5324 | copyFile.DestinationShortName = names[0]; |
@@ -5788,7 +5787,7 @@ namespace WixToolset | |||
5788 | 5787 | ||
5789 | file.Id = fileRow.File; | 5788 | file.Id = fileRow.File; |
5790 | 5789 | ||
5791 | string[] names = Installer.GetNames(fileRow.FileName); | 5790 | string[] names = Common.GetNames(fileRow.FileName); |
5792 | if (null != names[0] && null != names[1]) | 5791 | if (null != names[0] && null != names[1]) |
5793 | { | 5792 | { |
5794 | file.ShortName = names[0]; | 5793 | file.ShortName = names[0]; |
@@ -5974,7 +5973,7 @@ namespace WixToolset | |||
5974 | 5973 | ||
5975 | iniFile.Id = Convert.ToString(row[0]); | 5974 | iniFile.Id = Convert.ToString(row[0]); |
5976 | 5975 | ||
5977 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | 5976 | string[] names = Common.GetNames(Convert.ToString(row[1])); |
5978 | 5977 | ||
5979 | if (null != names[0]) | 5978 | if (null != names[0]) |
5980 | { | 5979 | { |
@@ -6044,7 +6043,7 @@ namespace WixToolset | |||
6044 | 6043 | ||
6045 | iniFileSearch.Id = Convert.ToString(row[0]); | 6044 | iniFileSearch.Id = Convert.ToString(row[0]); |
6046 | 6045 | ||
6047 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | 6046 | string[] names = Common.GetNames(Convert.ToString(row[1])); |
6048 | if (null != names[0] && null != names[1]) | 6047 | if (null != names[0] && null != names[1]) |
6049 | { | 6048 | { |
6050 | iniFileSearch.ShortName = names[0]; | 6049 | iniFileSearch.ShortName = names[0]; |
@@ -6681,7 +6680,7 @@ namespace WixToolset | |||
6681 | 6680 | ||
6682 | if (null != row[3]) | 6681 | if (null != row[3]) |
6683 | { | 6682 | { |
6684 | string[] names = Installer.GetNames(Convert.ToString(row[3])); | 6683 | string[] names = Common.GetNames(Convert.ToString(row[3])); |
6685 | if (null != names[0] && null != names[1]) | 6684 | if (null != names[0] && null != names[1]) |
6686 | { | 6685 | { |
6687 | copyFile.DestinationShortName = names[0]; | 6686 | copyFile.DestinationShortName = names[0]; |
@@ -8007,7 +8006,7 @@ namespace WixToolset | |||
8007 | 8006 | ||
8008 | removeFile.Id = Convert.ToString(row[0]); | 8007 | removeFile.Id = Convert.ToString(row[0]); |
8009 | 8008 | ||
8010 | string[] names = Installer.GetNames(Convert.ToString(row[2])); | 8009 | string[] names = Common.GetNames(Convert.ToString(row[2])); |
8011 | if (null != names[0] && null != names[1]) | 8010 | if (null != names[0] && null != names[1]) |
8012 | { | 8011 | { |
8013 | removeFile.ShortName = names[0]; | 8012 | removeFile.ShortName = names[0]; |
@@ -8062,7 +8061,7 @@ namespace WixToolset | |||
8062 | 8061 | ||
8063 | iniFile.Id = Convert.ToString(row[0]); | 8062 | iniFile.Id = Convert.ToString(row[0]); |
8064 | 8063 | ||
8065 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | 8064 | string[] names = Common.GetNames(Convert.ToString(row[1])); |
8066 | if (null != names[0] && null != names[1]) | 8065 | if (null != names[0] && null != names[1]) |
8067 | { | 8066 | { |
8068 | iniFile.ShortName = names[0]; | 8067 | iniFile.ShortName = names[0]; |
@@ -8531,7 +8530,7 @@ namespace WixToolset | |||
8531 | 8530 | ||
8532 | shortcut.Directory = Convert.ToString(row[1]); | 8531 | shortcut.Directory = Convert.ToString(row[1]); |
8533 | 8532 | ||
8534 | string[] names = Installer.GetNames(Convert.ToString(row[2])); | 8533 | string[] names = Common.GetNames(Convert.ToString(row[2])); |
8535 | if (null != names[0] && null != names[1]) | 8534 | if (null != names[0] && null != names[1]) |
8536 | { | 8535 | { |
8537 | shortcut.ShortName = names[0]; | 8536 | shortcut.ShortName = names[0]; |
@@ -8654,7 +8653,7 @@ namespace WixToolset | |||
8654 | 8653 | ||
8655 | fileSearch.Id = Convert.ToString(row[0]); | 8654 | fileSearch.Id = Convert.ToString(row[0]); |
8656 | 8655 | ||
8657 | string[] names = Installer.GetNames(Convert.ToString(row[1])); | 8656 | string[] names = Common.GetNames(Convert.ToString(row[1])); |
8658 | if (null != names[0]) | 8657 | if (null != names[0]) |
8659 | { | 8658 | { |
8660 | // it is permissable to just have a long name | 8659 | // it is permissable to just have a long name |
diff --git a/src/WixToolset.Core/Differ.cs b/src/WixToolset.Core/Differ.cs deleted file mode 100644 index 71a64327..00000000 --- a/src/WixToolset.Core/Differ.cs +++ /dev/null | |||
@@ -1,621 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using WixToolset.Data; | ||
10 | using WixToolset.Data.Rows; | ||
11 | using WixToolset.Extensibility; | ||
12 | using WixToolset.Msi; | ||
13 | |||
14 | /// <summary> | ||
15 | /// Creates a transform by diffing two outputs. | ||
16 | /// </summary> | ||
17 | public sealed class Differ : IMessageHandler | ||
18 | { | ||
19 | private List<IInspectorExtension> inspectorExtensions; | ||
20 | private bool showPedanticMessages; | ||
21 | private bool suppressKeepingSpecialRows; | ||
22 | private bool preserveUnchangedRows; | ||
23 | private const char sectionDelimiter = '/'; | ||
24 | private SummaryInformationStreams transformSummaryInfo; | ||
25 | |||
26 | /// <summary> | ||
27 | /// Instantiates a new Differ class. | ||
28 | /// </summary> | ||
29 | public Differ() | ||
30 | { | ||
31 | this.inspectorExtensions = new List<IInspectorExtension>(); | ||
32 | } | ||
33 | |||
34 | /// <summary> | ||
35 | /// Gets or sets the option to show pedantic messages. | ||
36 | /// </summary> | ||
37 | /// <value>The option to show pedantic messages.</value> | ||
38 | public bool ShowPedanticMessages | ||
39 | { | ||
40 | get { return this.showPedanticMessages; } | ||
41 | set { this.showPedanticMessages = value; } | ||
42 | } | ||
43 | |||
44 | /// <summary> | ||
45 | /// Gets or sets the option to suppress keeping special rows. | ||
46 | /// </summary> | ||
47 | /// <value>The option to suppress keeping special rows.</value> | ||
48 | public bool SuppressKeepingSpecialRows | ||
49 | { | ||
50 | get { return this.suppressKeepingSpecialRows; } | ||
51 | set { this.suppressKeepingSpecialRows = value; } | ||
52 | } | ||
53 | |||
54 | /// <summary> | ||
55 | /// Gets or sets the flag to determine if all rows, even unchanged ones will be persisted in the output. | ||
56 | /// </summary> | ||
57 | /// <value>The option to keep all rows including unchanged rows.</value> | ||
58 | public bool PreserveUnchangedRows | ||
59 | { | ||
60 | get { return this.preserveUnchangedRows; } | ||
61 | set { this.preserveUnchangedRows = value; } | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Adds an extension. | ||
66 | /// </summary> | ||
67 | /// <param name="extension">The extension to add.</param> | ||
68 | public void AddExtension(IInspectorExtension extension) | ||
69 | { | ||
70 | this.inspectorExtensions.Add(extension); | ||
71 | } | ||
72 | |||
73 | /// <summary> | ||
74 | /// Creates a transform by diffing two outputs. | ||
75 | /// </summary> | ||
76 | /// <param name="targetOutput">The target output.</param> | ||
77 | /// <param name="updatedOutput">The updated output.</param> | ||
78 | /// <returns>The transform.</returns> | ||
79 | public Output Diff(Output targetOutput, Output updatedOutput) | ||
80 | { | ||
81 | return Diff(targetOutput, updatedOutput, 0); | ||
82 | } | ||
83 | |||
84 | /// <summary> | ||
85 | /// Creates a transform by diffing two outputs. | ||
86 | /// </summary> | ||
87 | /// <param name="targetOutput">The target output.</param> | ||
88 | /// <param name="updatedOutput">The updated output.</param> | ||
89 | /// <param name="validationFlags"></param> | ||
90 | /// <returns>The transform.</returns> | ||
91 | public Output Diff(Output targetOutput, Output updatedOutput, TransformFlags validationFlags) | ||
92 | { | ||
93 | Output transform = new Output(null); | ||
94 | transform.Type = OutputType.Transform; | ||
95 | transform.Codepage = updatedOutput.Codepage; | ||
96 | this.transformSummaryInfo = new SummaryInformationStreams(); | ||
97 | |||
98 | // compare the codepages | ||
99 | if (targetOutput.Codepage != updatedOutput.Codepage && 0 == (TransformFlags.ErrorChangeCodePage & validationFlags)) | ||
100 | { | ||
101 | this.OnMessage(WixErrors.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); | ||
102 | if (null != updatedOutput.SourceLineNumbers) | ||
103 | { | ||
104 | this.OnMessage(WixErrors.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | // compare the output types | ||
109 | if (targetOutput.Type != updatedOutput.Type) | ||
110 | { | ||
111 | throw new WixException(WixErrors.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); | ||
112 | } | ||
113 | |||
114 | // compare the contents of the tables | ||
115 | foreach (Table targetTable in targetOutput.Tables) | ||
116 | { | ||
117 | Table updatedTable = updatedOutput.Tables[targetTable.Name]; | ||
118 | TableOperation operation = TableOperation.None; | ||
119 | |||
120 | List<Row> rows = this.CompareTables(targetOutput, targetTable, updatedTable, out operation); | ||
121 | |||
122 | if (TableOperation.Drop == operation) | ||
123 | { | ||
124 | Table droppedTable = transform.EnsureTable(targetTable.Definition); | ||
125 | droppedTable.Operation = TableOperation.Drop; | ||
126 | } | ||
127 | else if (TableOperation.None == operation) | ||
128 | { | ||
129 | Table modified = transform.EnsureTable(updatedTable.Definition); | ||
130 | rows.ForEach(r => modified.Rows.Add(r)); | ||
131 | } | ||
132 | } | ||
133 | |||
134 | // added tables | ||
135 | foreach (Table updatedTable in updatedOutput.Tables) | ||
136 | { | ||
137 | if (null == targetOutput.Tables[updatedTable.Name]) | ||
138 | { | ||
139 | Table addedTable = transform.EnsureTable(updatedTable.Definition); | ||
140 | addedTable.Operation = TableOperation.Add; | ||
141 | |||
142 | foreach (Row updatedRow in updatedTable.Rows) | ||
143 | { | ||
144 | updatedRow.Operation = RowOperation.Add; | ||
145 | updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; | ||
146 | addedTable.Rows.Add(updatedRow); | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | // set summary information properties | ||
152 | if (!this.suppressKeepingSpecialRows) | ||
153 | { | ||
154 | Table summaryInfoTable = transform.Tables["_SummaryInformation"]; | ||
155 | this.UpdateTransformSummaryInformationTable(summaryInfoTable, validationFlags); | ||
156 | } | ||
157 | |||
158 | // inspect the transform | ||
159 | InspectorCore inspectorCore = new InspectorCore(); | ||
160 | foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) | ||
161 | { | ||
162 | inspectorExtension.Core = inspectorCore; | ||
163 | inspectorExtension.InspectOutput(transform); | ||
164 | |||
165 | // reset | ||
166 | inspectorExtension.Core = null; | ||
167 | } | ||
168 | |||
169 | return transform; | ||
170 | } | ||
171 | |||
172 | /// <summary> | ||
173 | /// Sends a message to the message delegate if there is one. | ||
174 | /// </summary> | ||
175 | /// <param name="mea">Message event arguments.</param> | ||
176 | public void OnMessage(MessageEventArgs e) | ||
177 | { | ||
178 | Messaging.Instance.OnMessage(e); | ||
179 | } | ||
180 | |||
181 | /// <summary> | ||
182 | /// Add a row to the <paramref name="index"/> using the primary key. | ||
183 | /// </summary> | ||
184 | /// <param name="index">The indexed rows.</param> | ||
185 | /// <param name="row">The row to index.</param> | ||
186 | private void AddIndexedRow(IDictionary index, Row row) | ||
187 | { | ||
188 | string primaryKey = row.GetPrimaryKey('/'); | ||
189 | if (null != primaryKey) | ||
190 | { | ||
191 | // Overriding WixActionRows have a primary key defined and take precedence in the index. | ||
192 | if (row is WixActionRow) | ||
193 | { | ||
194 | WixActionRow currentRow = (WixActionRow)row; | ||
195 | if (index.Contains(primaryKey)) | ||
196 | { | ||
197 | // If the current row is not overridable, see if the indexed row is. | ||
198 | if (!currentRow.Overridable) | ||
199 | { | ||
200 | WixActionRow indexedRow = index[primaryKey] as WixActionRow; | ||
201 | if (null != indexedRow && indexedRow.Overridable) | ||
202 | { | ||
203 | // The indexed key is overridable and should be replaced | ||
204 | // (not removed and re-added which results in two Array.Copy | ||
205 | // operations for SortedList, or may be re-hashing in other | ||
206 | // implementations of IDictionary). | ||
207 | index[primaryKey] = currentRow; | ||
208 | } | ||
209 | } | ||
210 | |||
211 | // If we got this far, the row does not need to be indexed. | ||
212 | return; | ||
213 | } | ||
214 | } | ||
215 | |||
216 | // Nothing else should be added more than once. | ||
217 | if (!index.Contains(primaryKey)) | ||
218 | { | ||
219 | index.Add(primaryKey, row); | ||
220 | } | ||
221 | else if (this.showPedanticMessages) | ||
222 | { | ||
223 | this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, row.Table.Name)); | ||
224 | } | ||
225 | } | ||
226 | else // use the string representation of the row as its primary key (it may not be unique) | ||
227 | { | ||
228 | // this is provided for compatibility with unreal tables with no primary key | ||
229 | // all real tables must specify at least one column as the primary key | ||
230 | primaryKey = row.ToString(); | ||
231 | index[primaryKey] = row; | ||
232 | } | ||
233 | } | ||
234 | |||
235 | private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow) | ||
236 | { | ||
237 | Row comparedRow = null; | ||
238 | keepRow = false; | ||
239 | operation = RowOperation.None; | ||
240 | |||
241 | if (null == targetRow ^ null == updatedRow) | ||
242 | { | ||
243 | if (null == targetRow) | ||
244 | { | ||
245 | operation = updatedRow.Operation = RowOperation.Add; | ||
246 | comparedRow = updatedRow; | ||
247 | } | ||
248 | else if (null == updatedRow) | ||
249 | { | ||
250 | operation = targetRow.Operation = RowOperation.Delete; | ||
251 | targetRow.SectionId = targetRow.SectionId + sectionDelimiter; | ||
252 | comparedRow = targetRow; | ||
253 | keepRow = true; | ||
254 | } | ||
255 | } | ||
256 | else // possibly modified | ||
257 | { | ||
258 | updatedRow.Operation = RowOperation.None; | ||
259 | if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) | ||
260 | { | ||
261 | // ignore rows that shouldn't be in a transform | ||
262 | if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0])) | ||
263 | { | ||
264 | updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; | ||
265 | comparedRow = updatedRow; | ||
266 | keepRow = true; | ||
267 | operation = RowOperation.Modify; | ||
268 | } | ||
269 | } | ||
270 | else | ||
271 | { | ||
272 | if (this.preserveUnchangedRows) | ||
273 | { | ||
274 | keepRow = true; | ||
275 | } | ||
276 | |||
277 | for (int i = 0; i < updatedRow.Fields.Length; i++) | ||
278 | { | ||
279 | ColumnDefinition columnDefinition = updatedRow.Fields[i].Column; | ||
280 | |||
281 | if (!columnDefinition.PrimaryKey) | ||
282 | { | ||
283 | bool modified = false; | ||
284 | |||
285 | if (i >= targetRow.Fields.Length) | ||
286 | { | ||
287 | columnDefinition.Added = true; | ||
288 | modified = true; | ||
289 | } | ||
290 | else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) | ||
291 | { | ||
292 | if (null == targetRow[i] ^ null == updatedRow[i]) | ||
293 | { | ||
294 | modified = true; | ||
295 | } | ||
296 | else if (null != targetRow[i] && null != updatedRow[i]) | ||
297 | { | ||
298 | modified = ((int)targetRow[i] != (int)updatedRow[i]); | ||
299 | } | ||
300 | } | ||
301 | else if (ColumnType.Preserved == columnDefinition.Type) | ||
302 | { | ||
303 | updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data; | ||
304 | |||
305 | // keep rows containing preserved fields so the historical data is available to the binder | ||
306 | keepRow = !this.suppressKeepingSpecialRows; | ||
307 | } | ||
308 | else if (ColumnType.Object == columnDefinition.Type) | ||
309 | { | ||
310 | ObjectField targetObjectField = (ObjectField)targetRow.Fields[i]; | ||
311 | ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i]; | ||
312 | |||
313 | updatedObjectField.PreviousEmbeddedFileIndex = targetObjectField.EmbeddedFileIndex; | ||
314 | updatedObjectField.PreviousBaseUri = targetObjectField.BaseUri; | ||
315 | |||
316 | // always keep a copy of the previous data even if they are identical | ||
317 | // This makes diff.wixmst clean and easier to control patch logic | ||
318 | updatedObjectField.PreviousData = (string)targetObjectField.Data; | ||
319 | |||
320 | // always remember the unresolved data for target build | ||
321 | updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData; | ||
322 | |||
323 | // keep rows containing object fields so the files can be compared in the binder | ||
324 | keepRow = !this.suppressKeepingSpecialRows; | ||
325 | } | ||
326 | else | ||
327 | { | ||
328 | modified = ((string)targetRow[i] != (string)updatedRow[i]); | ||
329 | } | ||
330 | |||
331 | if (modified) | ||
332 | { | ||
333 | if (null != updatedRow.Fields[i].PreviousData) | ||
334 | { | ||
335 | updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString(); | ||
336 | } | ||
337 | |||
338 | updatedRow.Fields[i].Modified = true; | ||
339 | operation = updatedRow.Operation = RowOperation.Modify; | ||
340 | keepRow = true; | ||
341 | } | ||
342 | } | ||
343 | } | ||
344 | |||
345 | if (keepRow) | ||
346 | { | ||
347 | comparedRow = updatedRow; | ||
348 | comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; | ||
349 | } | ||
350 | } | ||
351 | } | ||
352 | |||
353 | return comparedRow; | ||
354 | } | ||
355 | |||
356 | private List<Row> CompareTables(Output targetOutput, Table targetTable, Table updatedTable, out TableOperation operation) | ||
357 | { | ||
358 | List<Row> rows = new List<Row>(); | ||
359 | operation = TableOperation.None; | ||
360 | |||
361 | // dropped tables | ||
362 | if (null == updatedTable ^ null == targetTable) | ||
363 | { | ||
364 | if (null == targetTable) | ||
365 | { | ||
366 | operation = TableOperation.Add; | ||
367 | rows.AddRange(updatedTable.Rows); | ||
368 | } | ||
369 | else if (null == updatedTable) | ||
370 | { | ||
371 | operation = TableOperation.Drop; | ||
372 | } | ||
373 | } | ||
374 | else // possibly modified tables | ||
375 | { | ||
376 | SortedList updatedPrimaryKeys = new SortedList(); | ||
377 | SortedList targetPrimaryKeys = new SortedList(); | ||
378 | |||
379 | // compare the table definitions | ||
380 | if (0 != targetTable.Definition.CompareTo(updatedTable.Definition)) | ||
381 | { | ||
382 | // continue to the next table; may be more mismatches | ||
383 | this.OnMessage(WixErrors.DatabaseSchemaMismatch(targetOutput.SourceLineNumbers, targetTable.Name)); | ||
384 | } | ||
385 | else | ||
386 | { | ||
387 | this.IndexPrimaryKeys(targetTable, targetPrimaryKeys, updatedTable, updatedPrimaryKeys); | ||
388 | |||
389 | // diff the target and updated rows | ||
390 | foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys) | ||
391 | { | ||
392 | string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key; | ||
393 | bool keepRow = false; | ||
394 | RowOperation rowOperation = RowOperation.None; | ||
395 | |||
396 | Row compared = this.CompareRows(targetTable, targetPrimaryKeyEntry.Value as Row, updatedPrimaryKeys[targetPrimaryKey] as Row, out rowOperation, out keepRow); | ||
397 | |||
398 | if (keepRow) | ||
399 | { | ||
400 | rows.Add(compared); | ||
401 | } | ||
402 | } | ||
403 | |||
404 | // find the inserted rows | ||
405 | foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys) | ||
406 | { | ||
407 | string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key; | ||
408 | |||
409 | if (!targetPrimaryKeys.Contains(updatedPrimaryKey)) | ||
410 | { | ||
411 | Row updatedRow = (Row)updatedPrimaryKeyEntry.Value; | ||
412 | |||
413 | updatedRow.Operation = RowOperation.Add; | ||
414 | updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; | ||
415 | rows.Add(updatedRow); | ||
416 | } | ||
417 | } | ||
418 | } | ||
419 | } | ||
420 | |||
421 | return rows; | ||
422 | } | ||
423 | |||
424 | private void IndexPrimaryKeys(Table targetTable, SortedList targetPrimaryKeys, Table updatedTable, SortedList updatedPrimaryKeys) | ||
425 | { | ||
426 | // index the target rows | ||
427 | foreach (Row row in targetTable.Rows) | ||
428 | { | ||
429 | this.AddIndexedRow(targetPrimaryKeys, row); | ||
430 | |||
431 | if ("Property" == targetTable.Name) | ||
432 | { | ||
433 | if ("ProductCode" == (string)row[0]) | ||
434 | { | ||
435 | this.transformSummaryInfo.TargetProductCode = (string)row[1]; | ||
436 | if ("*" == this.transformSummaryInfo.TargetProductCode) | ||
437 | { | ||
438 | this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers)); | ||
439 | } | ||
440 | } | ||
441 | else if ("ProductVersion" == (string)row[0]) | ||
442 | { | ||
443 | this.transformSummaryInfo.TargetProductVersion = (string)row[1]; | ||
444 | } | ||
445 | else if ("UpgradeCode" == (string)row[0]) | ||
446 | { | ||
447 | this.transformSummaryInfo.TargetUpgradeCode = (string)row[1]; | ||
448 | } | ||
449 | } | ||
450 | else if ("_SummaryInformation" == targetTable.Name) | ||
451 | { | ||
452 | if (1 == (int)row[0]) // PID_CODEPAGE | ||
453 | { | ||
454 | this.transformSummaryInfo.TargetSummaryInfoCodepage = (string)row[1]; | ||
455 | } | ||
456 | else if (7 == (int)row[0]) // PID_TEMPLATE | ||
457 | { | ||
458 | this.transformSummaryInfo.TargetPlatformAndLanguage = (string)row[1]; | ||
459 | } | ||
460 | else if (14 == (int)row[0]) // PID_PAGECOUNT | ||
461 | { | ||
462 | this.transformSummaryInfo.TargetMinimumVersion = (string)row[1]; | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | |||
467 | // index the updated rows | ||
468 | foreach (Row row in updatedTable.Rows) | ||
469 | { | ||
470 | this.AddIndexedRow(updatedPrimaryKeys, row); | ||
471 | |||
472 | if ("Property" == updatedTable.Name) | ||
473 | { | ||
474 | if ("ProductCode" == (string)row[0]) | ||
475 | { | ||
476 | this.transformSummaryInfo.UpdatedProductCode = (string)row[1]; | ||
477 | if ("*" == this.transformSummaryInfo.UpdatedProductCode) | ||
478 | { | ||
479 | this.OnMessage(WixErrors.ProductCodeInvalidForTransform(row.SourceLineNumbers)); | ||
480 | } | ||
481 | } | ||
482 | else if ("ProductVersion" == (string)row[0]) | ||
483 | { | ||
484 | this.transformSummaryInfo.UpdatedProductVersion = (string)row[1]; | ||
485 | } | ||
486 | } | ||
487 | else if ("_SummaryInformation" == updatedTable.Name) | ||
488 | { | ||
489 | if (1 == (int)row[0]) // PID_CODEPAGE | ||
490 | { | ||
491 | this.transformSummaryInfo.UpdatedSummaryInfoCodepage = (string)row[1]; | ||
492 | } | ||
493 | else if (7 == (int)row[0]) // PID_TEMPLATE | ||
494 | { | ||
495 | this.transformSummaryInfo.UpdatedPlatformAndLanguage = (string)row[1]; | ||
496 | } | ||
497 | else if (14 == (int)row[0]) // PID_PAGECOUNT | ||
498 | { | ||
499 | this.transformSummaryInfo.UpdatedMinimumVersion = (string)row[1]; | ||
500 | } | ||
501 | } | ||
502 | } | ||
503 | } | ||
504 | |||
505 | private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) | ||
506 | { | ||
507 | // calculate the minimum version of MSI required to process the transform | ||
508 | int targetMin; | ||
509 | int updatedMin; | ||
510 | int minimumVersion = 100; | ||
511 | |||
512 | if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin)) | ||
513 | { | ||
514 | minimumVersion = Math.Max(targetMin, updatedMin); | ||
515 | } | ||
516 | |||
517 | Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count); | ||
518 | foreach (Row row in summaryInfoTable.Rows) | ||
519 | { | ||
520 | summaryRows[row[0]] = row; | ||
521 | |||
522 | if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) | ||
523 | { | ||
524 | row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; | ||
525 | row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; | ||
526 | } | ||
527 | else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0]) | ||
528 | { | ||
529 | row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; | ||
530 | } | ||
531 | else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) | ||
532 | { | ||
533 | row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; | ||
534 | } | ||
535 | else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) | ||
536 | { | ||
537 | row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); | ||
538 | } | ||
539 | else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0]) | ||
540 | { | ||
541 | row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); | ||
542 | } | ||
543 | else if ((int)SummaryInformation.Transform.Security == (int)row[0]) | ||
544 | { | ||
545 | row[1] = "4"; | ||
546 | } | ||
547 | } | ||
548 | |||
549 | if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) | ||
550 | { | ||
551 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
552 | summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; | ||
553 | summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; | ||
554 | } | ||
555 | |||
556 | if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) | ||
557 | { | ||
558 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
559 | summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; | ||
560 | summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; | ||
561 | } | ||
562 | |||
563 | if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) | ||
564 | { | ||
565 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
566 | summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
567 | summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); | ||
568 | } | ||
569 | |||
570 | if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement)) | ||
571 | { | ||
572 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
573 | summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; | ||
574 | summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); | ||
575 | } | ||
576 | |||
577 | if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) | ||
578 | { | ||
579 | Row summaryRow = summaryInfoTable.CreateRow(null); | ||
580 | summaryRow[0] = (int)SummaryInformation.Transform.Security; | ||
581 | summaryRow[1] = "4"; | ||
582 | } | ||
583 | } | ||
584 | |||
585 | private class SummaryInformationStreams | ||
586 | { | ||
587 | public string TargetSummaryInfoCodepage | ||
588 | { get; set; } | ||
589 | |||
590 | public string TargetPlatformAndLanguage | ||
591 | { get; set; } | ||
592 | |||
593 | public string TargetProductCode | ||
594 | { get; set; } | ||
595 | |||
596 | public string TargetProductVersion | ||
597 | { get; set; } | ||
598 | |||
599 | public string TargetUpgradeCode | ||
600 | { get; set; } | ||
601 | |||
602 | public string TargetMinimumVersion | ||
603 | { get; set; } | ||
604 | |||
605 | public string UpdatedSummaryInfoCodepage | ||
606 | { get; set; } | ||
607 | |||
608 | public string UpdatedPlatformAndLanguage | ||
609 | { get; set; } | ||
610 | |||
611 | public string UpdatedProductCode | ||
612 | { get; set; } | ||
613 | |||
614 | public string UpdatedProductVersion | ||
615 | { get; set; } | ||
616 | |||
617 | public string UpdatedMinimumVersion | ||
618 | { get; set; } | ||
619 | } | ||
620 | } | ||
621 | } | ||
diff --git a/src/WixToolset.Core/Extensibility/HeatExtension.cs b/src/WixToolset.Core/Extensibility/HeatExtension.cs index 5e292220..48e1a93b 100644 --- a/src/WixToolset.Core/Extensibility/HeatExtension.cs +++ b/src/WixToolset.Core/Extensibility/HeatExtension.cs | |||
@@ -3,14 +3,10 @@ | |||
3 | namespace WixToolset.Extensibility | 3 | namespace WixToolset.Extensibility |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Collections.Generic; | ||
7 | using System.IO; | 6 | using System.IO; |
8 | using System.Reflection; | 7 | using System.Reflection; |
9 | using WixToolset; | ||
10 | using WixToolset.Data; | 8 | using WixToolset.Data; |
11 | using WixToolset.Extensibilty; | ||
12 | using WixToolset.Tools; | 9 | using WixToolset.Tools; |
13 | using Wix = WixToolset.Data.Serialize; | ||
14 | 10 | ||
15 | /// <summary> | 11 | /// <summary> |
16 | /// A command line option. | 12 | /// A command line option. |
diff --git a/src/WixToolset.Core/Extensibility/IHeatCore.cs b/src/WixToolset.Core/Extensibility/IHeatCore.cs index bc853b24..dbfc8929 100644 --- a/src/WixToolset.Core/Extensibility/IHeatCore.cs +++ b/src/WixToolset.Core/Extensibility/IHeatCore.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.Extensibilty | 3 | namespace WixToolset.Extensibility |
4 | { | 4 | { |
5 | using WixToolset.Data; | 5 | using WixToolset.Data; |
6 | 6 | ||
diff --git a/src/WixToolset.Core/Extensibility/ValidatorExtension.cs b/src/WixToolset.Core/Extensibility/ValidatorExtension.cs deleted file mode 100644 index 44ec3106..00000000 --- a/src/WixToolset.Core/Extensibility/ValidatorExtension.cs +++ /dev/null | |||
@@ -1,299 +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.Extensibility | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using WixToolset.Data; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Base class for creating a validator extension. This default implementation | ||
11 | /// will fire and event with the ICE name and description. | ||
12 | /// </summary> | ||
13 | public class ValidatorExtension : IMessageHandler | ||
14 | { | ||
15 | private string databaseFile; | ||
16 | private Hashtable indexedSourceLineNumbers; | ||
17 | private Output output; | ||
18 | private SourceLineNumber sourceLineNumbers; | ||
19 | |||
20 | /// <summary> | ||
21 | /// Instantiate a new <see cref="ValidatorExtension"/>. | ||
22 | /// </summary> | ||
23 | public ValidatorExtension() | ||
24 | { | ||
25 | } | ||
26 | |||
27 | /// <summary> | ||
28 | /// Gets or sets the path to the database to validate. | ||
29 | /// </summary> | ||
30 | /// <value>The path to the database to validate.</value> | ||
31 | public string DatabaseFile | ||
32 | { | ||
33 | get { return this.databaseFile; } | ||
34 | set { this.databaseFile = value; } | ||
35 | } | ||
36 | |||
37 | /// <summary> | ||
38 | /// Gets or sets the <see cref="Output"/> for finding source line information. | ||
39 | /// </summary> | ||
40 | /// <value>The <see cref="Output"/> for finding source line information.</value> | ||
41 | public Output Output | ||
42 | { | ||
43 | get { return this.output; } | ||
44 | set { this.output = value; } | ||
45 | } | ||
46 | |||
47 | /// <summary> | ||
48 | /// Called at the beginning of the validation of a database file. | ||
49 | /// </summary> | ||
50 | /// <remarks> | ||
51 | /// <para>The <see cref="Validator"/> will set | ||
52 | /// <see cref="DatabaseFile"/> before calling InitializeValidator.</para> | ||
53 | /// <para><b>Notes to Inheritors:</b> When overriding | ||
54 | /// <b>InitializeValidator</b> in a derived class, be sure to call | ||
55 | /// the base class's <b>InitializeValidator</b> to thoroughly | ||
56 | /// initialize the extension.</para> | ||
57 | /// </remarks> | ||
58 | public virtual void InitializeValidator() | ||
59 | { | ||
60 | if (this.databaseFile != null) | ||
61 | { | ||
62 | this.sourceLineNumbers = new SourceLineNumber(databaseFile); | ||
63 | } | ||
64 | } | ||
65 | |||
66 | /// <summary> | ||
67 | /// Called at the end of the validation of a database file. | ||
68 | /// </summary> | ||
69 | /// <remarks> | ||
70 | /// <para>The default implementation will nullify source lines.</para> | ||
71 | /// <para><b>Notes to Inheritors:</b> When overriding | ||
72 | /// <b>FinalizeValidator</b> in a derived class, be sure to call | ||
73 | /// the base class's <b>FinalizeValidator</b> to thoroughly | ||
74 | /// finalize the extension.</para> | ||
75 | /// </remarks> | ||
76 | public virtual void FinalizeValidator() | ||
77 | { | ||
78 | this.sourceLineNumbers = null; | ||
79 | } | ||
80 | |||
81 | /// <summary> | ||
82 | /// Logs a message from the <see cref="Validator"/>. | ||
83 | /// </summary> | ||
84 | /// <param name="message">A <see cref="String"/> of tab-delmited tokens | ||
85 | /// in the validation message.</param> | ||
86 | public virtual void Log(string message) | ||
87 | { | ||
88 | this.Log(message, null); | ||
89 | } | ||
90 | |||
91 | /// <summary> | ||
92 | /// Logs a message from the <see cref="Validator"/>. | ||
93 | /// </summary> | ||
94 | /// <param name="message">A <see cref="String"/> of tab-delmited tokens | ||
95 | /// in the validation message.</param> | ||
96 | /// <param name="action">The name of the action to which the message | ||
97 | /// belongs.</param> | ||
98 | /// <exception cref="ArgumentNullException">The message cannot be null. | ||
99 | /// </exception> | ||
100 | /// <exception cref="WixException">The message does not contain four (4) | ||
101 | /// or more tab-delimited tokens.</exception> | ||
102 | /// <remarks> | ||
103 | /// <para><paramref name="message"/> a tab-delimited set of tokens, | ||
104 | /// formatted according to Windows Installer guidelines for ICE | ||
105 | /// message. The following table lists what each token by index | ||
106 | /// should mean.</para> | ||
107 | /// <para><paramref name="action"/> a name that represents the ICE | ||
108 | /// action that was executed (e.g. 'ICE08').</para> | ||
109 | /// <list type="table"> | ||
110 | /// <listheader> | ||
111 | /// <term>Index</term> | ||
112 | /// <description>Description</description> | ||
113 | /// </listheader> | ||
114 | /// <item> | ||
115 | /// <term>0</term> | ||
116 | /// <description>Name of the ICE.</description> | ||
117 | /// </item> | ||
118 | /// <item> | ||
119 | /// <term>1</term> | ||
120 | /// <description>Message type. See the following list.</description> | ||
121 | /// </item> | ||
122 | /// <item> | ||
123 | /// <term>2</term> | ||
124 | /// <description>Detailed description.</description> | ||
125 | /// </item> | ||
126 | /// <item> | ||
127 | /// <term>3</term> | ||
128 | /// <description>Help URL or location.</description> | ||
129 | /// </item> | ||
130 | /// <item> | ||
131 | /// <term>4</term> | ||
132 | /// <description>Table name.</description> | ||
133 | /// </item> | ||
134 | /// <item> | ||
135 | /// <term>5</term> | ||
136 | /// <description>Column name.</description> | ||
137 | /// </item> | ||
138 | /// <item> | ||
139 | /// <term>6</term> | ||
140 | /// <description>This and remaining fields are primary keys | ||
141 | /// to identify a row.</description> | ||
142 | /// </item> | ||
143 | /// </list> | ||
144 | /// <para>The message types are one of the following value.</para> | ||
145 | /// <list type="table"> | ||
146 | /// <listheader> | ||
147 | /// <term>Value</term> | ||
148 | /// <description>Message Type</description> | ||
149 | /// </listheader> | ||
150 | /// <item> | ||
151 | /// <term>0</term> | ||
152 | /// <description>Failure message reporting the failure of the | ||
153 | /// ICE custom action.</description> | ||
154 | /// </item> | ||
155 | /// <item> | ||
156 | /// <term>1</term> | ||
157 | /// <description>Error message reporting database authoring that | ||
158 | /// case incorrect behavior.</description> | ||
159 | /// </item> | ||
160 | /// <item> | ||
161 | /// <term>2</term> | ||
162 | /// <description>Warning message reporting database authoring that | ||
163 | /// causes incorrect behavior in certain cases. Warnings can also | ||
164 | /// report unexpected side-effects of database authoring. | ||
165 | /// </description> | ||
166 | /// </item> | ||
167 | /// <item> | ||
168 | /// <term>3</term> | ||
169 | /// <description>Informational message.</description> | ||
170 | /// </item> | ||
171 | /// </list> | ||
172 | /// </remarks> | ||
173 | public virtual void Log(string message, string action) | ||
174 | { | ||
175 | if (message == null) | ||
176 | { | ||
177 | throw new ArgumentNullException("message"); | ||
178 | } | ||
179 | |||
180 | string[] messageParts = message.Split('\t'); | ||
181 | if (3 > messageParts.Length) | ||
182 | { | ||
183 | if (null == action) | ||
184 | { | ||
185 | throw new WixException(WixErrors.UnexpectedExternalUIMessage(message)); | ||
186 | } | ||
187 | else | ||
188 | { | ||
189 | throw new WixException(WixErrors.UnexpectedExternalUIMessage(message, action)); | ||
190 | } | ||
191 | } | ||
192 | |||
193 | SourceLineNumber messageSourceLineNumbers = null; | ||
194 | if (6 < messageParts.Length) | ||
195 | { | ||
196 | string[] primaryKeys = new string[messageParts.Length - 6]; | ||
197 | |||
198 | Array.Copy(messageParts, 6, primaryKeys, 0, primaryKeys.Length); | ||
199 | |||
200 | messageSourceLineNumbers = this.GetSourceLineNumbers(messageParts[4], primaryKeys); | ||
201 | } | ||
202 | else // use the file name as the source line information | ||
203 | { | ||
204 | messageSourceLineNumbers = this.sourceLineNumbers; | ||
205 | } | ||
206 | |||
207 | switch (messageParts[1]) | ||
208 | { | ||
209 | case "0": | ||
210 | case "1": | ||
211 | this.OnMessage(WixErrors.ValidationError(messageSourceLineNumbers, messageParts[0], messageParts[2])); | ||
212 | break; | ||
213 | case "2": | ||
214 | this.OnMessage(WixWarnings.ValidationWarning(messageSourceLineNumbers, messageParts[0], messageParts[2])); | ||
215 | break; | ||
216 | case "3": | ||
217 | this.OnMessage(WixVerboses.ValidationInfo(messageParts[0], messageParts[2])); | ||
218 | break; | ||
219 | default: | ||
220 | throw new WixException(WixErrors.InvalidValidatorMessageType(messageParts[1])); | ||
221 | } | ||
222 | } | ||
223 | |||
224 | /// <summary> | ||
225 | /// Gets the source line information (if available) for a row by its table name and primary key. | ||
226 | /// </summary> | ||
227 | /// <param name="tableName">The table name of the row.</param> | ||
228 | /// <param name="primaryKeys">The primary keys of the row.</param> | ||
229 | /// <returns>The source line number information if found; null otherwise.</returns> | ||
230 | protected SourceLineNumber GetSourceLineNumbers(string tableName, string[] primaryKeys) | ||
231 | { | ||
232 | // source line information only exists if an output file was supplied | ||
233 | if (null != this.output) | ||
234 | { | ||
235 | // index the source line information if it hasn't been indexed already | ||
236 | if (null == this.indexedSourceLineNumbers) | ||
237 | { | ||
238 | this.indexedSourceLineNumbers = new Hashtable(); | ||
239 | |||
240 | // index each real table | ||
241 | foreach (Table table in this.output.Tables) | ||
242 | { | ||
243 | // skip unreal tables | ||
244 | if (table.Definition.Unreal) | ||
245 | { | ||
246 | continue; | ||
247 | } | ||
248 | |||
249 | // index each row | ||
250 | foreach (Row row in table.Rows) | ||
251 | { | ||
252 | // skip rows that don't contain source line information | ||
253 | if (null == row.SourceLineNumbers) | ||
254 | { | ||
255 | continue; | ||
256 | } | ||
257 | |||
258 | // index the row using its table name and primary key | ||
259 | string primaryKey = row.GetPrimaryKey(';'); | ||
260 | if (null != primaryKey) | ||
261 | { | ||
262 | string key = String.Concat(table.Name, ":", primaryKey); | ||
263 | |||
264 | if (this.indexedSourceLineNumbers.ContainsKey(key)) | ||
265 | { | ||
266 | this.OnMessage(WixWarnings.DuplicatePrimaryKey(row.SourceLineNumbers, primaryKey, table.Name)); | ||
267 | } | ||
268 | else | ||
269 | { | ||
270 | this.indexedSourceLineNumbers.Add(key, row.SourceLineNumbers); | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | |||
277 | return (SourceLineNumber)this.indexedSourceLineNumbers[String.Concat(tableName, ":", String.Join(";", primaryKeys))]; | ||
278 | } | ||
279 | |||
280 | // use the file name as the source line information | ||
281 | return this.sourceLineNumbers; | ||
282 | } | ||
283 | |||
284 | /// <summary> | ||
285 | /// Sends a message to the <see cref="Message"/> delegate if there is one. | ||
286 | /// </summary> | ||
287 | /// <param name="e">Message event arguments.</param> | ||
288 | /// <remarks> | ||
289 | /// <para><b>Notes to Inheritors:</b> When overriding <b>OnMessage</b> | ||
290 | /// in a derived class, be sure to call the base class's | ||
291 | /// <b>OnMessage</b> method so that registered delegates recieve | ||
292 | /// the event.</para> | ||
293 | /// </remarks> | ||
294 | public virtual void OnMessage(MessageEventArgs e) | ||
295 | { | ||
296 | Messaging.Instance.OnMessage(e); | ||
297 | } | ||
298 | } | ||
299 | } | ||
diff --git a/src/WixToolset.Core/ExtensionManager.cs b/src/WixToolset.Core/ExtensionManager.cs index 45cb65ec..7e40571b 100644 --- a/src/WixToolset.Core/ExtensionManager.cs +++ b/src/WixToolset.Core/ExtensionManager.cs | |||
@@ -8,8 +8,9 @@ namespace WixToolset | |||
8 | using System.Linq; | 8 | using System.Linq; |
9 | using System.Reflection; | 9 | using System.Reflection; |
10 | using WixToolset.Data; | 10 | using WixToolset.Data; |
11 | using WixToolset.Extensibility; | ||
11 | 12 | ||
12 | public class ExtensionManager | 13 | public class ExtensionManager : IExtensionManager |
13 | { | 14 | { |
14 | private List<Assembly> extensionAssemblies = new List<Assembly>(); | 15 | private List<Assembly> extensionAssemblies = new List<Assembly>(); |
15 | 16 | ||
@@ -67,8 +68,7 @@ namespace WixToolset | |||
67 | /// <returns>Extensions created of the specified type.</returns> | 68 | /// <returns>Extensions created of the specified type.</returns> |
68 | public IEnumerable<T> Create<T>() where T : class | 69 | public IEnumerable<T> Create<T>() where T : class |
69 | { | 70 | { |
70 | var extensionType = typeof(T); | 71 | var types = this.extensionAssemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && typeof(T).IsAssignableFrom(t))); |
71 | var types = this.extensionAssemblies.SelectMany(a => a.GetTypes().Where(t => !t.IsAbstract && !t.IsInterface && extensionType.IsAssignableFrom(t))); | ||
72 | return types.Select(t => (T)Activator.CreateInstance(t)).ToList(); | 72 | return types.Select(t => (T)Activator.CreateInstance(t)).ToList(); |
73 | } | 73 | } |
74 | 74 | ||
diff --git a/src/WixToolset.Core/HeatCore.cs b/src/WixToolset.Core/HeatCore.cs index 5c5defe8..01233c40 100644 --- a/src/WixToolset.Core/HeatCore.cs +++ b/src/WixToolset.Core/HeatCore.cs | |||
@@ -2,11 +2,8 @@ | |||
2 | 2 | ||
3 | namespace WixToolset.Tools | 3 | namespace WixToolset.Tools |
4 | { | 4 | { |
5 | using System; | ||
6 | using System.Reflection; | ||
7 | using WixToolset.Data; | 5 | using WixToolset.Data; |
8 | using WixToolset.Extensibilty; | 6 | using WixToolset.Extensibility; |
9 | using Wix = WixToolset.Data.Serialize; | ||
10 | 7 | ||
11 | /// <summary> | 8 | /// <summary> |
12 | /// The WiX Toolset Harvester application core. | 9 | /// The WiX Toolset Harvester application core. |
diff --git a/src/WixToolset.Core/IncribeContext.cs b/src/WixToolset.Core/IncribeContext.cs new file mode 100644 index 00000000..604ba5d1 --- /dev/null +++ b/src/WixToolset.Core/IncribeContext.cs | |||
@@ -0,0 +1,20 @@ | |||
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 | ||
4 | { | ||
5 | using WixToolset.Data; | ||
6 | using WixToolset.Extensibility; | ||
7 | |||
8 | internal class InscribeContext : IInscribeContext | ||
9 | { | ||
10 | public Messaging Messaging { get; } = Messaging.Instance; | ||
11 | |||
12 | public string IntermediateFolder { get; set; } | ||
13 | |||
14 | public string InputFilePath { get; set; } | ||
15 | |||
16 | public string SignedEngineFile { get; set; } | ||
17 | |||
18 | public string OutputFile { get; set; } | ||
19 | } | ||
20 | } | ||
diff --git a/src/WixToolset.Core/Inscriber.cs b/src/WixToolset.Core/Inscriber.cs index 5b467ec1..f01e0629 100644 --- a/src/WixToolset.Core/Inscriber.cs +++ b/src/WixToolset.Core/Inscriber.cs | |||
@@ -2,17 +2,8 @@ | |||
2 | 2 | ||
3 | namespace WixToolset | 3 | namespace WixToolset |
4 | { | 4 | { |
5 | using System; | ||
6 | using System.CodeDom.Compiler; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.IO; | 5 | using System.IO; |
10 | using System.Runtime.InteropServices; | ||
11 | using System.Security.Cryptography.X509Certificates; | ||
12 | using WixToolset.Bind.Bundles; | ||
13 | using WixToolset.Data; | 6 | using WixToolset.Data; |
14 | using WixToolset.Msi; | ||
15 | using WixToolset.Core.Native; | ||
16 | 7 | ||
17 | /// <summary> | 8 | /// <summary> |
18 | /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. | 9 | /// Converts a wixout representation of an MSM database into a ComponentGroup the form of WiX source. |
@@ -81,41 +72,41 @@ namespace WixToolset | |||
81 | /// <returns>True if bundle was updated.</returns> | 72 | /// <returns>True if bundle was updated.</returns> |
82 | public bool InscribeBundleEngine(string bundleFile, string outputFile) | 73 | public bool InscribeBundleEngine(string bundleFile, string outputFile) |
83 | { | 74 | { |
84 | string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_unsigned.exe"); | 75 | //string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_unsigned.exe"); |
85 | 76 | ||
86 | using (BurnReader reader = BurnReader.Open(bundleFile)) | 77 | //using (BurnReader reader = BurnReader.Open(bundleFile)) |
87 | using (FileStream writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) | 78 | //using (FileStream writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) |
88 | { | 79 | //{ |
89 | reader.Stream.Seek(0, SeekOrigin.Begin); | 80 | // reader.Stream.Seek(0, SeekOrigin.Begin); |
90 | 81 | ||
91 | byte[] buffer = new byte[4 * 1024]; | 82 | // byte[] buffer = new byte[4 * 1024]; |
92 | int total = 0; | 83 | // int total = 0; |
93 | int read = 0; | 84 | // int read = 0; |
94 | do | 85 | // do |
95 | { | 86 | // { |
96 | read = Math.Min(buffer.Length, (int)reader.EngineSize - total); | 87 | // read = Math.Min(buffer.Length, (int)reader.EngineSize - total); |
97 | 88 | ||
98 | read = reader.Stream.Read(buffer, 0, read); | 89 | // read = reader.Stream.Read(buffer, 0, read); |
99 | writer.Write(buffer, 0, read); | 90 | // writer.Write(buffer, 0, read); |
100 | 91 | ||
101 | total += read; | 92 | // total += read; |
102 | } while (total < reader.EngineSize && 0 < read); | 93 | // } while (total < reader.EngineSize && 0 < read); |
103 | 94 | ||
104 | if (total != reader.EngineSize) | 95 | // if (total != reader.EngineSize) |
105 | { | 96 | // { |
106 | throw new InvalidOperationException("Failed to copy engine out of bundle."); | 97 | // throw new InvalidOperationException("Failed to copy engine out of bundle."); |
107 | } | 98 | // } |
108 | 99 | ||
109 | // TODO: update writer with detached container signatures. | 100 | // // TODO: update writer with detached container signatures. |
110 | } | 101 | //} |
111 | 102 | ||
112 | Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); | 103 | //Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); |
113 | if (File.Exists(outputFile)) | 104 | //if (File.Exists(outputFile)) |
114 | { | 105 | //{ |
115 | File.Delete(outputFile); | 106 | // File.Delete(outputFile); |
116 | } | 107 | //} |
117 | File.Move(tempFile, outputFile); | 108 | //File.Move(tempFile, outputFile); |
118 | WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); | 109 | //WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); |
119 | 110 | ||
120 | return true; | 111 | return true; |
121 | } | 112 | } |
@@ -129,36 +120,37 @@ namespace WixToolset | |||
129 | /// <returns>True if bundle was updated.</returns> | 120 | /// <returns>True if bundle was updated.</returns> |
130 | public bool InscribeBundle(string bundleFile, string signedEngineFile, string outputFile) | 121 | public bool InscribeBundle(string bundleFile, string signedEngineFile, string outputFile) |
131 | { | 122 | { |
132 | bool inscribed = false; | 123 | //bool inscribed = false; |
133 | string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_signed.exe"); | 124 | //string tempFile = Path.Combine(this.TempFilesLocation, "bundle_engine_signed.exe"); |
134 | 125 | ||
135 | using (BurnReader reader = BurnReader.Open(bundleFile)) | 126 | //using (BurnReader reader = BurnReader.Open(bundleFile)) |
136 | { | 127 | //{ |
137 | File.Copy(signedEngineFile, tempFile, true); | 128 | // File.Copy(signedEngineFile, tempFile, true); |
138 | 129 | ||
139 | // If there was an attached container on the original (unsigned) bundle, put it back. | 130 | // // If there was an attached container on the original (unsigned) bundle, put it back. |
140 | if (reader.AttachedContainerSize > 0) | 131 | // if (reader.AttachedContainerSize > 0) |
141 | { | 132 | // { |
142 | reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); | 133 | // reader.Stream.Seek(reader.AttachedContainerAddress, SeekOrigin.Begin); |
143 | 134 | ||
144 | using (BurnWriter writer = BurnWriter.Open(tempFile)) | 135 | // using (BurnWriter writer = BurnWriter.Open(tempFile)) |
145 | { | 136 | // { |
146 | writer.RememberThenResetSignature(); | 137 | // writer.RememberThenResetSignature(); |
147 | writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); | 138 | // writer.AppendContainer(reader.Stream, reader.AttachedContainerSize, BurnCommon.Container.Attached); |
148 | inscribed = true; | 139 | // inscribed = true; |
149 | } | 140 | // } |
150 | } | 141 | // } |
151 | } | 142 | //} |
152 | 143 | ||
153 | Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); | 144 | //Directory.CreateDirectory(Path.GetDirectoryName(outputFile)); |
154 | if (File.Exists(outputFile)) | 145 | //if (File.Exists(outputFile)) |
155 | { | 146 | //{ |
156 | File.Delete(outputFile); | 147 | // File.Delete(outputFile); |
157 | } | 148 | //} |
158 | File.Move(tempFile, outputFile); | 149 | //File.Move(tempFile, outputFile); |
159 | WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); | 150 | //WixToolset.Core.Native.NativeMethods.ResetAcls(new string[] { outputFile }, 1); |
160 | 151 | ||
161 | return inscribed; | 152 | //return inscribed; |
153 | return false; | ||
162 | } | 154 | } |
163 | 155 | ||
164 | /// <summary> | 156 | /// <summary> |
@@ -170,256 +162,257 @@ namespace WixToolset | |||
170 | /// <returns>True if database is updated.</returns> | 162 | /// <returns>True if database is updated.</returns> |
171 | public bool InscribeDatabase(string databaseFile, string outputFile, bool tidy) | 163 | public bool InscribeDatabase(string databaseFile, string outputFile, bool tidy) |
172 | { | 164 | { |
173 | // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered | 165 | //// Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered |
174 | bool foundUnsignedExternals = false; | 166 | //bool foundUnsignedExternals = false; |
175 | bool shouldCommit = false; | 167 | //bool shouldCommit = false; |
176 | 168 | ||
177 | FileAttributes attributes = File.GetAttributes(databaseFile); | 169 | //FileAttributes attributes = File.GetAttributes(databaseFile); |
178 | if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) | 170 | //if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) |
179 | { | 171 | //{ |
180 | this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile)); | 172 | // this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile)); |
181 | return shouldCommit; | 173 | // return shouldCommit; |
182 | } | 174 | //} |
183 | 175 | ||
184 | using (Database database = new Database(databaseFile, OpenDatabase.Transact)) | 176 | //using (Database database = new Database(databaseFile, OpenDatabase.Transact)) |
185 | { | 177 | //{ |
186 | // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content | 178 | // // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content |
187 | int codepage = 1252; | 179 | // int codepage = 1252; |
188 | 180 | ||
189 | // list of certificates for this database (hash/identifier) | 181 | // // list of certificates for this database (hash/identifier) |
190 | Dictionary<string, string> certificates = new Dictionary<string, string>(); | 182 | // Dictionary<string, string> certificates = new Dictionary<string, string>(); |
191 | 183 | ||
192 | // Reset the in-memory tables for this new database | 184 | // // Reset the in-memory tables for this new database |
193 | Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]); | 185 | // Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]); |
194 | Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]); | 186 | // Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]); |
195 | 187 | ||
196 | // If any digital signature records exist that are not of the media type, preserve them | 188 | // // If any digital signature records exist that are not of the media type, preserve them |
197 | if (database.TableExists("MsiDigitalSignature")) | 189 | // if (database.TableExists("MsiDigitalSignature")) |
198 | { | 190 | // { |
199 | using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) | 191 | // using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) |
200 | { | 192 | // { |
201 | while (true) | 193 | // while (true) |
202 | { | 194 | // { |
203 | using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) | 195 | // using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) |
204 | { | 196 | // { |
205 | if (null == digitalSignatureRecord) | 197 | // if (null == digitalSignatureRecord) |
206 | { | 198 | // { |
207 | break; | 199 | // break; |
208 | } | 200 | // } |
209 | 201 | ||
210 | Row digitalSignatureRow = null; | 202 | // Row digitalSignatureRow = null; |
211 | digitalSignatureRow = digitalSignatureTable.CreateRow(null); | 203 | // digitalSignatureRow = digitalSignatureTable.CreateRow(null); |
212 | 204 | ||
213 | string table = digitalSignatureRecord.GetString(0); | 205 | // string table = digitalSignatureRecord.GetString(0); |
214 | string signObject = digitalSignatureRecord.GetString(1); | 206 | // string signObject = digitalSignatureRecord.GetString(1); |
215 | 207 | ||
216 | digitalSignatureRow[0] = table; | 208 | // digitalSignatureRow[0] = table; |
217 | digitalSignatureRow[1] = signObject; | 209 | // digitalSignatureRow[1] = signObject; |
218 | digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); | 210 | // digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); |
219 | 211 | ||
220 | if (false == digitalSignatureRecord.IsNull(3)) | 212 | // if (false == digitalSignatureRecord.IsNull(3)) |
221 | { | 213 | // { |
222 | // Export to a file, because the MSI API's require us to provide a file path on disk | 214 | // // Export to a file, because the MSI API's require us to provide a file path on disk |
223 | string hashPath = Path.Combine(this.TempFilesLocation, "MsiDigitalSignature"); | 215 | // string hashPath = Path.Combine(this.TempFilesLocation, "MsiDigitalSignature"); |
224 | string hashFileName = string.Concat(table, ".", signObject, ".bin"); | 216 | // string hashFileName = string.Concat(table, ".", signObject, ".bin"); |
225 | 217 | ||
226 | Directory.CreateDirectory(hashPath); | 218 | // Directory.CreateDirectory(hashPath); |
227 | hashPath = Path.Combine(hashPath, hashFileName); | 219 | // hashPath = Path.Combine(hashPath, hashFileName); |
228 | 220 | ||
229 | using (FileStream fs = File.Create(hashPath)) | 221 | // using (FileStream fs = File.Create(hashPath)) |
230 | { | 222 | // { |
231 | int bytesRead; | 223 | // int bytesRead; |
232 | byte[] buffer = new byte[1024 * 4]; | 224 | // byte[] buffer = new byte[1024 * 4]; |
233 | 225 | ||
234 | while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) | 226 | // while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) |
235 | { | 227 | // { |
236 | fs.Write(buffer, 0, bytesRead); | 228 | // fs.Write(buffer, 0, bytesRead); |
237 | } | 229 | // } |
238 | } | 230 | // } |
239 | 231 | ||
240 | digitalSignatureRow[3] = hashFileName; | 232 | // digitalSignatureRow[3] = hashFileName; |
241 | } | 233 | // } |
242 | } | 234 | // } |
243 | } | 235 | // } |
244 | } | 236 | // } |
245 | } | 237 | // } |
246 | 238 | ||
247 | // If any digital certificates exist, extract and preserve them | 239 | // // If any digital certificates exist, extract and preserve them |
248 | if (database.TableExists("MsiDigitalCertificate")) | 240 | // if (database.TableExists("MsiDigitalCertificate")) |
249 | { | 241 | // { |
250 | using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) | 242 | // using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) |
251 | { | 243 | // { |
252 | while (true) | 244 | // while (true) |
253 | { | 245 | // { |
254 | using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) | 246 | // using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) |
255 | { | 247 | // { |
256 | if (null == digitalCertificateRecord) | 248 | // if (null == digitalCertificateRecord) |
257 | { | 249 | // { |
258 | break; | 250 | // break; |
259 | } | 251 | // } |
260 | 252 | ||
261 | string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate | 253 | // string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate |
262 | 254 | ||
263 | // Export to a file, because the MSI API's require us to provide a file path on disk | 255 | // // Export to a file, because the MSI API's require us to provide a file path on disk |
264 | string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); | 256 | // string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); |
265 | Directory.CreateDirectory(certPath); | 257 | // Directory.CreateDirectory(certPath); |
266 | certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); | 258 | // certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); |
267 | 259 | ||
268 | using (FileStream fs = File.Create(certPath)) | 260 | // using (FileStream fs = File.Create(certPath)) |
269 | { | 261 | // { |
270 | int bytesRead; | 262 | // int bytesRead; |
271 | byte[] buffer = new byte[1024 * 4]; | 263 | // byte[] buffer = new byte[1024 * 4]; |
272 | 264 | ||
273 | while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) | 265 | // while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) |
274 | { | 266 | // { |
275 | fs.Write(buffer, 0, bytesRead); | 267 | // fs.Write(buffer, 0, bytesRead); |
276 | } | 268 | // } |
277 | } | 269 | // } |
278 | 270 | ||
279 | // Add it to our "add to MsiDigitalCertificate" table dictionary | 271 | // // Add it to our "add to MsiDigitalCertificate" table dictionary |
280 | Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); | 272 | // Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); |
281 | digitalCertificateRow[0] = certificateId; | 273 | // digitalCertificateRow[0] = certificateId; |
282 | 274 | ||
283 | // Now set the file path on disk where this binary stream will be picked up at import time | 275 | // // Now set the file path on disk where this binary stream will be picked up at import time |
284 | digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); | 276 | // digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); |
285 | 277 | ||
286 | // Load the cert to get it's thumbprint | 278 | // // Load the cert to get it's thumbprint |
287 | X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); | 279 | // X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); |
288 | X509Certificate2 cert2 = new X509Certificate2(cert); | 280 | // X509Certificate2 cert2 = new X509Certificate2(cert); |
289 | 281 | ||
290 | certificates.Add(cert2.Thumbprint, certificateId); | 282 | // certificates.Add(cert2.Thumbprint, certificateId); |
291 | } | 283 | // } |
292 | } | 284 | // } |
293 | } | 285 | // } |
294 | } | 286 | // } |
295 | 287 | ||
296 | using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) | 288 | // using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) |
297 | { | 289 | // { |
298 | while (true) | 290 | // while (true) |
299 | { | 291 | // { |
300 | using (Record mediaRecord = mediaView.Fetch()) | 292 | // using (Record mediaRecord = mediaView.Fetch()) |
301 | { | 293 | // { |
302 | if (null == mediaRecord) | 294 | // if (null == mediaRecord) |
303 | { | 295 | // { |
304 | break; | 296 | // break; |
305 | } | 297 | // } |
306 | 298 | ||
307 | X509Certificate2 cert2 = null; | 299 | // X509Certificate2 cert2 = null; |
308 | Row digitalSignatureRow = null; | 300 | // Row digitalSignatureRow = null; |
309 | 301 | ||
310 | string cabName = mediaRecord.GetString(4); // get the name of the cab | 302 | // string cabName = mediaRecord.GetString(4); // get the name of the cab |
311 | // If there is no cabinet or it's an internal cab, skip it. | 303 | // // If there is no cabinet or it's an internal cab, skip it. |
312 | if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) | 304 | // if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) |
313 | { | 305 | // { |
314 | continue; | 306 | // continue; |
315 | } | 307 | // } |
316 | 308 | ||
317 | string cabId = mediaRecord.GetString(1); // get the ID of the cab | 309 | // string cabId = mediaRecord.GetString(1); // get the ID of the cab |
318 | string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName); | 310 | // string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName); |
319 | 311 | ||
320 | // If the cabs aren't there, throw an error but continue to catch the other errors | 312 | // // If the cabs aren't there, throw an error but continue to catch the other errors |
321 | if (!File.Exists(cabPath)) | 313 | // if (!File.Exists(cabPath)) |
322 | { | 314 | // { |
323 | this.OnMessage(WixErrors.WixFileNotFound(cabPath)); | 315 | // this.OnMessage(WixErrors.WixFileNotFound(cabPath)); |
324 | continue; | 316 | // continue; |
325 | } | 317 | // } |
326 | 318 | ||
327 | try | 319 | // try |
328 | { | 320 | // { |
329 | // Get the certificate from the cab | 321 | // // Get the certificate from the cab |
330 | X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); | 322 | // X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); |
331 | cert2 = new X509Certificate2(signedFileCert); | 323 | // cert2 = new X509Certificate2(signedFileCert); |
332 | } | 324 | // } |
333 | catch (System.Security.Cryptography.CryptographicException e) | 325 | // catch (System.Security.Cryptography.CryptographicException e) |
334 | { | 326 | // { |
335 | uint HResult = unchecked((uint)Marshal.GetHRForException(e)); | 327 | // uint HResult = unchecked((uint)Marshal.GetHRForException(e)); |
336 | 328 | ||
337 | // If the file has no cert, continue, but flag that we found at least one so we can later give a warning | 329 | // // If the file has no cert, continue, but flag that we found at least one so we can later give a warning |
338 | if (0x80092009 == HResult) // CRYPT_E_NO_MATCH | 330 | // if (0x80092009 == HResult) // CRYPT_E_NO_MATCH |
339 | { | 331 | // { |
340 | foundUnsignedExternals = true; | 332 | // foundUnsignedExternals = true; |
341 | continue; | 333 | // continue; |
342 | } | 334 | // } |
343 | 335 | ||
344 | // todo: exactly which HRESULT corresponds to this issue? | 336 | // // todo: exactly which HRESULT corresponds to this issue? |
345 | // If it's one of these exact platforms, warn the user that it may be due to their OS. | 337 | // // If it's one of these exact platforms, warn the user that it may be due to their OS. |
346 | if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 | 338 | // if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 |
347 | (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP | 339 | // (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP |
348 | { | 340 | // { |
349 | this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); | 341 | // this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); |
350 | } | 342 | // } |
351 | else // otherwise, generic error | 343 | // else // otherwise, generic error |
352 | { | 344 | // { |
353 | this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); | 345 | // this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); |
354 | } | 346 | // } |
355 | } | 347 | // } |
356 | 348 | ||
357 | // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added | 349 | // // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added |
358 | if (!certificates.ContainsKey(cert2.Thumbprint)) | 350 | // if (!certificates.ContainsKey(cert2.Thumbprint)) |
359 | { | 351 | // { |
360 | // generate a stable identifier | 352 | // // generate a stable identifier |
361 | string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); | 353 | // string certificateGeneratedId = Common.GenerateIdentifier("cer", cert2.Thumbprint); |
362 | 354 | ||
363 | // Add it to our "add to MsiDigitalCertificate" table dictionary | 355 | // // Add it to our "add to MsiDigitalCertificate" table dictionary |
364 | Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); | 356 | // Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); |
365 | digitalCertificateRow[0] = certificateGeneratedId; | 357 | // digitalCertificateRow[0] = certificateGeneratedId; |
366 | 358 | ||
367 | // Export to a file, because the MSI API's require us to provide a file path on disk | 359 | // // Export to a file, because the MSI API's require us to provide a file path on disk |
368 | string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); | 360 | // string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); |
369 | Directory.CreateDirectory(certPath); | 361 | // Directory.CreateDirectory(certPath); |
370 | certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); | 362 | // certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); |
371 | File.Delete(certPath); | 363 | // File.Delete(certPath); |
372 | 364 | ||
373 | using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) | 365 | // using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) |
374 | { | 366 | // { |
375 | writer.Write(cert2.RawData); | 367 | // writer.Write(cert2.RawData); |
376 | writer.Close(); | 368 | // writer.Close(); |
377 | } | 369 | // } |
378 | 370 | ||
379 | // Now set the file path on disk where this binary stream will be picked up at import time | 371 | // // Now set the file path on disk where this binary stream will be picked up at import time |
380 | digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); | 372 | // digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); |
381 | 373 | ||
382 | certificates.Add(cert2.Thumbprint, certificateGeneratedId); | 374 | // certificates.Add(cert2.Thumbprint, certificateGeneratedId); |
383 | } | 375 | // } |
384 | 376 | ||
385 | digitalSignatureRow = digitalSignatureTable.CreateRow(null); | 377 | // digitalSignatureRow = digitalSignatureTable.CreateRow(null); |
386 | 378 | ||
387 | digitalSignatureRow[0] = "Media"; | 379 | // digitalSignatureRow[0] = "Media"; |
388 | digitalSignatureRow[1] = cabId; | 380 | // digitalSignatureRow[1] = cabId; |
389 | digitalSignatureRow[2] = certificates[cert2.Thumbprint]; | 381 | // digitalSignatureRow[2] = certificates[cert2.Thumbprint]; |
390 | } | 382 | // } |
391 | } | 383 | // } |
392 | } | 384 | // } |
393 | 385 | ||
394 | if (digitalCertificateTable.Rows.Count > 0) | 386 | // if (digitalCertificateTable.Rows.Count > 0) |
395 | { | 387 | // { |
396 | database.ImportTable(codepage, digitalCertificateTable, this.TempFilesLocation, true); | 388 | // database.ImportTable(codepage, digitalCertificateTable, this.TempFilesLocation, true); |
397 | shouldCommit = true; | 389 | // shouldCommit = true; |
398 | } | 390 | // } |
399 | 391 | ||
400 | if (digitalSignatureTable.Rows.Count > 0) | 392 | // if (digitalSignatureTable.Rows.Count > 0) |
401 | { | 393 | // { |
402 | database.ImportTable(codepage, digitalSignatureTable, this.TempFilesLocation, true); | 394 | // database.ImportTable(codepage, digitalSignatureTable, this.TempFilesLocation, true); |
403 | shouldCommit = true; | 395 | // shouldCommit = true; |
404 | } | 396 | // } |
405 | 397 | ||
406 | // TODO: if we created the table(s), then we should add the _Validation records for them. | 398 | // // TODO: if we created the table(s), then we should add the _Validation records for them. |
407 | 399 | ||
408 | certificates = null; | 400 | // certificates = null; |
409 | 401 | ||
410 | // If we did find external cabs but none of them were signed, give a warning | 402 | // // If we did find external cabs but none of them were signed, give a warning |
411 | if (foundUnsignedExternals) | 403 | // if (foundUnsignedExternals) |
412 | { | 404 | // { |
413 | this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile)); | 405 | // this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile)); |
414 | } | 406 | // } |
415 | 407 | ||
416 | if (shouldCommit) | 408 | // if (shouldCommit) |
417 | { | 409 | // { |
418 | database.Commit(); | 410 | // database.Commit(); |
419 | } | 411 | // } |
420 | } | 412 | //} |
421 | 413 | ||
422 | return shouldCommit; | 414 | //return shouldCommit; |
415 | return false; | ||
423 | } | 416 | } |
424 | 417 | ||
425 | /// <summary> | 418 | /// <summary> |
diff --git a/src/WixToolset.Core/Librarian.cs b/src/WixToolset.Core/Librarian.cs index 66a8c32d..092d81dc 100644 --- a/src/WixToolset.Core/Librarian.cs +++ b/src/WixToolset.Core/Librarian.cs | |||
@@ -1,10 +1,11 @@ | |||
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 | 3 | namespace WixToolset.Core |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
7 | using System.Linq; | 7 | using System.Linq; |
8 | using WixToolset.Core.Bind; | ||
8 | using WixToolset.Data; | 9 | using WixToolset.Data; |
9 | using WixToolset.Link; | 10 | using WixToolset.Link; |
10 | 11 | ||
@@ -13,20 +14,41 @@ namespace WixToolset | |||
13 | /// </summary> | 14 | /// </summary> |
14 | public sealed class Librarian | 15 | public sealed class Librarian |
15 | { | 16 | { |
17 | public Librarian(LibraryContext context) | ||
18 | { | ||
19 | this.Context = context; | ||
20 | } | ||
21 | |||
22 | private LibraryContext Context { get; } | ||
23 | |||
16 | /// <summary> | 24 | /// <summary> |
17 | /// Create a library by combining several intermediates (objects). | 25 | /// Create a library by combining several intermediates (objects). |
18 | /// </summary> | 26 | /// </summary> |
19 | /// <param name="sections">The sections to combine into a library.</param> | 27 | /// <param name="sections">The sections to combine into a library.</param> |
20 | /// <returns>Returns the new library.</returns> | 28 | /// <returns>Returns the new library.</returns> |
21 | public Library Combine(IEnumerable<Section> sections, IEnumerable<Localization> localizations, ILibraryBinaryFileResolver resolver) | 29 | public Library Combine() |
22 | { | 30 | { |
23 | var localizationsByCulture = CollateLocalizations(localizations); | 31 | foreach (var extension in this.Context.Extensions) |
32 | { | ||
33 | extension.PreCombine(this.Context); | ||
34 | } | ||
35 | |||
36 | var fileResolver = new FileResolver(this.Context.BindPaths, this.Context.Extensions); | ||
24 | 37 | ||
25 | var embedFilePaths = ResolveFilePathsToEmbed(sections, resolver); | 38 | var localizationsByCulture = CollateLocalizations(this.Context.Localizations); |
26 | 39 | ||
27 | var library = new Library(sections, localizationsByCulture, embedFilePaths); | 40 | var embedFilePaths = ResolveFilePathsToEmbed(this.Context.Sections, fileResolver); |
28 | 41 | ||
29 | return this.Validate(library); | 42 | var library = new Library(this.Context.Sections, localizationsByCulture, embedFilePaths); |
43 | |||
44 | this.Validate(library); | ||
45 | |||
46 | foreach (var extension in this.Context.Extensions) | ||
47 | { | ||
48 | extension.PostCombine(library); | ||
49 | } | ||
50 | |||
51 | return library; | ||
30 | } | 52 | } |
31 | 53 | ||
32 | /// <summary> | 54 | /// <summary> |
@@ -70,12 +92,12 @@ namespace WixToolset | |||
70 | return localizationsByCulture; | 92 | return localizationsByCulture; |
71 | } | 93 | } |
72 | 94 | ||
73 | private static List<string> ResolveFilePathsToEmbed(IEnumerable<Section> sections, ILibraryBinaryFileResolver resolver) | 95 | private List<string> ResolveFilePathsToEmbed(IEnumerable<Section> sections, FileResolver fileResolver) |
74 | { | 96 | { |
75 | var embedFilePaths = new List<string>(); | 97 | var embedFilePaths = new List<string>(); |
76 | 98 | ||
77 | // Resolve paths to files that are to be embedded in the library. | 99 | // Resolve paths to files that are to be embedded in the library. |
78 | if (null != resolver) | 100 | if (this.Context.BindFiles) |
79 | { | 101 | { |
80 | foreach (Table table in sections.SelectMany(s => s.Tables)) | 102 | foreach (Table table in sections.SelectMany(s => s.Tables)) |
81 | { | 103 | { |
@@ -85,7 +107,10 @@ namespace WixToolset | |||
85 | { | 107 | { |
86 | if (null != objectField.Data) | 108 | if (null != objectField.Data) |
87 | { | 109 | { |
88 | string file = resolver.Resolve(row.SourceLineNumbers, table.Name, (string)objectField.Data); | 110 | string resolvedPath = this.Context.WixVariableResolver.ResolveVariables(row.SourceLineNumbers, (string)objectField.Data, false); |
111 | |||
112 | string file = fileResolver.Resolve(row.SourceLineNumbers, table.Name, resolvedPath); | ||
113 | |||
89 | if (!String.IsNullOrEmpty(file)) | 114 | if (!String.IsNullOrEmpty(file)) |
90 | { | 115 | { |
91 | // File was successfully resolved so track the embedded index as the embedded file index. | 116 | // File was successfully resolved so track the embedded index as the embedded file index. |
diff --git a/src/WixToolset.Core/LibraryContext.cs b/src/WixToolset.Core/LibraryContext.cs new file mode 100644 index 00000000..36e38739 --- /dev/null +++ b/src/WixToolset.Core/LibraryContext.cs | |||
@@ -0,0 +1,23 @@ | |||
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 | ||
4 | { | ||
5 | using System.Collections.Generic; | ||
6 | using WixToolset.Data; | ||
7 | using WixToolset.Extensibility; | ||
8 | |||
9 | public class LibraryContext : ILibraryContext | ||
10 | { | ||
11 | public bool BindFiles { get; set; } | ||
12 | |||
13 | public IEnumerable<BindPath> BindPaths { get; set; } | ||
14 | |||
15 | public IEnumerable<ILibrarianExtension> Extensions { get; set; } | ||
16 | |||
17 | public IEnumerable<Localization> Localizations { get; set; } | ||
18 | |||
19 | public IEnumerable<Section> Sections { get; set; } | ||
20 | |||
21 | public IBindVariableResolver WixVariableResolver { get; set; } | ||
22 | } | ||
23 | } | ||
diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs index 1e5b6e96..c1c9f848 100644 --- a/src/WixToolset.Core/Linker.cs +++ b/src/WixToolset.Core/Linker.cs | |||
@@ -78,7 +78,7 @@ namespace WixToolset | |||
78 | /// Gets or sets the Wix variable resolver. | 78 | /// Gets or sets the Wix variable resolver. |
79 | /// </summary> | 79 | /// </summary> |
80 | /// <value>The Wix variable resolver.</value> | 80 | /// <value>The Wix variable resolver.</value> |
81 | public WixVariableResolver WixVariableResolver { get; set; } | 81 | internal IBindVariableResolver WixVariableResolver { get; set; } |
82 | 82 | ||
83 | /// <summary> | 83 | /// <summary> |
84 | /// Adds an extension. | 84 | /// Adds an extension. |
diff --git a/src/WixToolset.Core/Localizer.cs b/src/WixToolset.Core/Localizer.cs index 63ead24a..72d0955b 100644 --- a/src/WixToolset.Core/Localizer.cs +++ b/src/WixToolset.Core/Localizer.cs | |||
@@ -8,11 +8,12 @@ namespace WixToolset | |||
8 | using WixToolset.Data; | 8 | using WixToolset.Data; |
9 | using WixToolset.Data.Rows; | 9 | using WixToolset.Data.Rows; |
10 | using WixToolset.Core.Native; | 10 | using WixToolset.Core.Native; |
11 | using WixToolset.Extensibility; | ||
11 | 12 | ||
12 | /// <summary> | 13 | /// <summary> |
13 | /// Parses localization files and localizes database values. | 14 | /// Parses localization files and localizes database values. |
14 | /// </summary> | 15 | /// </summary> |
15 | public sealed class Localizer | 16 | public sealed class Localizer : ILocalizer |
16 | { | 17 | { |
17 | public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; | 18 | public static readonly XNamespace WxlNamespace = "http://wixtoolset.org/schemas/v4/wxl"; |
18 | private static string XmlElementName = "WixLocalization"; | 19 | private static string XmlElementName = "WixLocalization"; |
@@ -55,7 +56,28 @@ namespace WixToolset | |||
55 | /// Gets the codepage. | 56 | /// Gets the codepage. |
56 | /// </summary> | 57 | /// </summary> |
57 | /// <value>The codepage.</value> | 58 | /// <value>The codepage.</value> |
58 | public int Codepage { get; private set; } | 59 | public int Codepage { get; } |
60 | |||
61 | /// <summary> | ||
62 | /// Get a localized data value. | ||
63 | /// </summary> | ||
64 | /// <param name="id">The name of the localization variable.</param> | ||
65 | /// <returns>The localized data value or null if it wasn't found.</returns> | ||
66 | public string GetLocalizedValue(string id) | ||
67 | { | ||
68 | return this.variables.TryGetValue(id, out var wixVariableRow) ? wixVariableRow.Value : null; | ||
69 | } | ||
70 | |||
71 | /// <summary> | ||
72 | /// Get a localized control. | ||
73 | /// </summary> | ||
74 | /// <param name="dialog">The optional id of the control's dialog.</param> | ||
75 | /// <param name="control">The id of the control.</param> | ||
76 | /// <returns>The localized control or null if it wasn't found.</returns> | ||
77 | public LocalizedControl GetLocalizedControl(string dialog, string control) | ||
78 | { | ||
79 | return this.localizedControls.TryGetValue(LocalizedControl.GetKey(dialog, control), out var localizedControl) ? localizedControl : null; | ||
80 | } | ||
59 | 81 | ||
60 | /// <summary> | 82 | /// <summary> |
61 | /// Loads a localization file from a path on disk. | 83 | /// Loads a localization file from a path on disk. |
@@ -97,36 +119,13 @@ namespace WixToolset | |||
97 | } | 119 | } |
98 | 120 | ||
99 | /// <summary> | 121 | /// <summary> |
100 | /// Get a localized data value. | ||
101 | /// </summary> | ||
102 | /// <param name="id">The name of the localization variable.</param> | ||
103 | /// <returns>The localized data value or null if it wasn't found.</returns> | ||
104 | public string GetLocalizedValue(string id) | ||
105 | { | ||
106 | return this.variables.TryGetValue(id, out var wixVariableRow) ? wixVariableRow.Value : null; | ||
107 | } | ||
108 | |||
109 | /// <summary> | ||
110 | /// Get a localized control. | ||
111 | /// </summary> | ||
112 | /// <param name="dialog">The optional id of the control's dialog.</param> | ||
113 | /// <param name="control">The id of the control.</param> | ||
114 | /// <returns>The localized control or null if it wasn't found.</returns> | ||
115 | public LocalizedControl GetLocalizedControl(string dialog, string control) | ||
116 | { | ||
117 | LocalizedControl localizedControl; | ||
118 | return this.localizedControls.TryGetValue(LocalizedControl.GetKey(dialog, control), out localizedControl) ? localizedControl : null; | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// Adds a WixVariableRow to a dictionary while performing the expected override checks. | 122 | /// Adds a WixVariableRow to a dictionary while performing the expected override checks. |
123 | /// </summary> | 123 | /// </summary> |
124 | /// <param name="variables">Dictionary of variable rows.</param> | 124 | /// <param name="variables">Dictionary of variable rows.</param> |
125 | /// <param name="wixVariableRow">Row to add to the variables dictionary.</param> | 125 | /// <param name="wixVariableRow">Row to add to the variables dictionary.</param> |
126 | private static void AddWixVariable(IDictionary<string, WixVariableRow> variables, WixVariableRow wixVariableRow) | 126 | private static void AddWixVariable(IDictionary<string, WixVariableRow> variables, WixVariableRow wixVariableRow) |
127 | { | 127 | { |
128 | WixVariableRow existingWixVariableRow; | 128 | if (!variables.TryGetValue(wixVariableRow.Id, out var existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) |
129 | if (!variables.TryGetValue(wixVariableRow.Id, out existingWixVariableRow) || (existingWixVariableRow.Overridable && !wixVariableRow.Overridable)) | ||
130 | { | 129 | { |
131 | variables[wixVariableRow.Id] = wixVariableRow; | 130 | variables[wixVariableRow.Id] = wixVariableRow; |
132 | } | 131 | } |
diff --git a/src/WixToolset.Core/MergeMod/NativeMethods.cs b/src/WixToolset.Core/MergeMod/NativeMethods.cs deleted file mode 100644 index daf259b4..00000000 --- a/src/WixToolset.Core/MergeMod/NativeMethods.cs +++ /dev/null | |||
@@ -1,508 +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 | #if false | ||
3 | namespace WixToolset.MergeMod | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Runtime.CompilerServices; | ||
8 | using System.Runtime.InteropServices; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Errors returned by merge operations. | ||
12 | /// </summary> | ||
13 | [Guid("0ADDA825-2C26-11D2-AD65-00A0C9AF11A6")] | ||
14 | internal enum MsmErrorType | ||
15 | { | ||
16 | /// <summary> | ||
17 | /// A request was made to open a module with a language not supported by the module. | ||
18 | /// No more general language is supported by the module. | ||
19 | /// Adds msmErrorLanguageUnsupported to the Type property and the requested language | ||
20 | /// to the Language Property (Error Object). All Error object properties are empty. | ||
21 | /// The OpenModule function returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). | ||
22 | /// </summary> | ||
23 | msmErrorLanguageUnsupported = 1, | ||
24 | |||
25 | /// <summary> | ||
26 | /// A request was made to open a module with a supported language but the module has | ||
27 | /// an invalid language transform. Adds msmErrorLanguageFailed to the Type property | ||
28 | /// and the applied transform's language to the Language Property of the Error object. | ||
29 | /// This may not be the requested language if a more general language was used. | ||
30 | /// All other properties of the Error object are empty. The OpenModule function | ||
31 | /// returns ERROR_INSTALL_LANGUAGE_UNSUPPORTED (as HRESULT). | ||
32 | /// </summary> | ||
33 | msmErrorLanguageFailed = 2, | ||
34 | |||
35 | /// <summary> | ||
36 | /// The module cannot be merged because it excludes, or is excluded by, another module | ||
37 | /// in the database. Adds msmErrorExclusion to the Type property of the Error object. | ||
38 | /// The ModuleKeys property or DatabaseKeys property contains the primary keys of the | ||
39 | /// excluded module's row in the ModuleExclusion table. If an existing module excludes | ||
40 | /// the module being merged, the excluded module's ModuleSignature information is added | ||
41 | /// to ModuleKeys. If the module being merged excludes an existing module, DatabaseKeys | ||
42 | /// contains the excluded module's ModuleSignature information. All other properties | ||
43 | /// are empty (or -1). | ||
44 | /// </summary> | ||
45 | msmErrorExclusion = 3, | ||
46 | |||
47 | /// <summary> | ||
48 | /// Merge conflict during merge. The value of the Type property is set to | ||
49 | /// msmErrorTableMerge. The DatabaseTable property and DatabaseKeys property contain | ||
50 | /// the table name and primary keys of the conflicting row in the database. The | ||
51 | /// ModuleTable property and ModuleKeys property contain the table name and primary keys | ||
52 | /// of the conflicting row in the module. The ModuleTable and ModuleKeys entries may be | ||
53 | /// null if the row does not exist in the database. For example, if the conflict is in a | ||
54 | /// generated FeatureComponents table entry. On Windows Installer version 2.0, when | ||
55 | /// merging a configurable merge module, configuration may cause these properties to | ||
56 | /// refer to rows that do not exist in the module. | ||
57 | /// </summary> | ||
58 | msmErrorTableMerge = 4, | ||
59 | |||
60 | /// <summary> | ||
61 | /// There was a problem resequencing a sequence table to contain the necessary merged | ||
62 | /// actions. The Type property is set to msmErrorResequenceMerge. The DatabaseTable | ||
63 | /// and DatabaseKeys properties contain the sequence table name and primary keys | ||
64 | /// (action name) of the conflicting row. The ModuleTable and ModuleKeys properties | ||
65 | /// contain the sequence table name and primary key (action name) of the conflicting row. | ||
66 | /// On Windows Installer version 2.0, when merging a configurable merge module, | ||
67 | /// configuration may cause these properties to refer to rows that do not exist in the module. | ||
68 | /// </summary> | ||
69 | msmErrorResequenceMerge = 5, | ||
70 | |||
71 | /// <summary> | ||
72 | /// Not used. | ||
73 | /// </summary> | ||
74 | msmErrorFileCreate = 6, | ||
75 | |||
76 | /// <summary> | ||
77 | /// There was a problem creating a directory to extract a file to disk. The Path property | ||
78 | /// contains the directory that could not be created. All other properties are empty or -1. | ||
79 | /// Not available with Windows Installer version 1.0. | ||
80 | /// </summary> | ||
81 | msmErrorDirCreate = 7, | ||
82 | |||
83 | /// <summary> | ||
84 | /// A feature name is required to complete the merge, but no feature name was provided. | ||
85 | /// The Type property is set to msmErrorFeatureRequired. The DatabaseTable and DatabaseKeys | ||
86 | /// contain the table name and primary keys of the conflicting row. The ModuleTable and | ||
87 | /// ModuleKeys properties contain the table name and primary keys of the row cannot be merged. | ||
88 | /// On Windows Installer version 2.0, when merging a configurable merge module, configuration | ||
89 | /// may cause these properties to refer to rows that do not exist in the module. | ||
90 | /// If the failure is in a generated FeatureComponents table, the DatabaseTable and | ||
91 | /// DatabaseKeys properties are empty and the ModuleTable and ModuleKeys properties refer to | ||
92 | /// the row in the Component table causing the failure. | ||
93 | /// </summary> | ||
94 | msmErrorFeatureRequired = 8, | ||
95 | |||
96 | /// <summary> | ||
97 | /// Available with Window Installer version 2.0. Substitution of a Null value into a | ||
98 | /// non-nullable column. This enters msmErrorBadNullSubstitution in the Type property and | ||
99 | /// enters "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row | ||
100 | /// into the ModuleTable property and ModuleKeys property. All other properties of the Error | ||
101 | /// object are set to an empty string or -1. This error causes the immediate failure of the | ||
102 | /// merge and the MergeEx function to return E_FAIL. | ||
103 | /// </summary> | ||
104 | msmErrorBadNullSubstitution = 9, | ||
105 | |||
106 | /// <summary> | ||
107 | /// Available with Window Installer version 2.0. Substitution of Text Format Type or Integer | ||
108 | /// Format Type into a Binary Type data column. This type of error returns | ||
109 | /// msmErrorBadSubstitutionType in the Type property and enters "ModuleSubstitution" and the | ||
110 | /// keys from the ModuleSubstitution table for this row into the ModuleTable property. | ||
111 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
112 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
113 | /// </summary> | ||
114 | msmErrorBadSubstitutionType = 10, | ||
115 | |||
116 | /// <summary> | ||
117 | /// Available with Window Installer Version 2.0. A row in the ModuleSubstitution table | ||
118 | /// references a configuration item not defined in the ModuleConfiguration table. | ||
119 | /// This type of error returns msmErrorMissingConfigItem in the Type property and enters | ||
120 | /// "ModuleSubstitution" and the keys from the ModuleSubstitution table for this row into | ||
121 | /// the ModuleTable property. All other properties of the Error object are set to an empty | ||
122 | /// string or -1. This error causes the immediate failure of the merge and the MergeEx | ||
123 | /// function to return E_FAIL. | ||
124 | /// </summary> | ||
125 | msmErrorMissingConfigItem = 11, | ||
126 | |||
127 | /// <summary> | ||
128 | /// Available with Window Installer version 2.0. The authoring tool has returned a Null | ||
129 | /// value for an item marked with the msmConfigItemNonNullable attribute. An error of this | ||
130 | /// type returns msmErrorBadNullResponse in the Type property and enters "ModuleSubstitution" | ||
131 | /// and the keys from the ModuleSubstitution table for for the item into the ModuleTable property. | ||
132 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
133 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
134 | /// </summary> | ||
135 | msmErrorBadNullResponse = 12, | ||
136 | |||
137 | /// <summary> | ||
138 | /// Available with Window Installer version 2.0. The authoring tool returned a failure code | ||
139 | /// (not S_OK or S_FALSE) when asked for data. An error of this type will return | ||
140 | /// msmErrorDataRequestFailed in the Type property and enters "ModuleSubstitution" | ||
141 | /// and the keys from the ModuleSubstitution table for the item into the ModuleTable property. | ||
142 | /// All other properties of the Error object are set to an empty string or -1. This error | ||
143 | /// causes the immediate failure of the merge and the MergeEx function to return E_FAIL. | ||
144 | /// </summary> | ||
145 | msmErrorDataRequestFailed = 13, | ||
146 | |||
147 | /// <summary> | ||
148 | /// Available with Windows Installer 2.0 and later versions. Indicates that an attempt was | ||
149 | /// made to merge a 64-bit module into a package that was not a 64-bit package. An error of | ||
150 | /// this type returns msmErrorPlatformMismatch in the Type property. All other properties of | ||
151 | /// the error object are set to an empty string or -1. This error causes the immediate failure | ||
152 | /// of the merge and causes the Merge function or MergeEx function to return E_FAIL. | ||
153 | /// </summary> | ||
154 | msmErrorPlatformMismatch = 14, | ||
155 | } | ||
156 | |||
157 | /// <summary> | ||
158 | /// IMsmMerge2 interface. | ||
159 | /// </summary> | ||
160 | [ComImport, Guid("351A72AB-21CB-47ab-B7AA-C4D7B02EA305")] | ||
161 | internal interface IMsmMerge2 | ||
162 | { | ||
163 | /// <summary> | ||
164 | /// The OpenDatabase method of the Merge object opens a Windows Installer installation | ||
165 | /// database, located at a specified path, that is to be merged with a module. | ||
166 | /// </summary> | ||
167 | /// <param name="path">Path to the database being opened.</param> | ||
168 | void OpenDatabase(string path); | ||
169 | |||
170 | /// <summary> | ||
171 | /// The OpenModule method of the Merge object opens a Windows Installer merge module | ||
172 | /// in read-only mode. A module must be opened before it can be merged with an installation database. | ||
173 | /// </summary> | ||
174 | /// <param name="fileName">Fully qualified file name pointing to a merge module.</param> | ||
175 | /// <param name="language">A valid language identifier (LANGID).</param> | ||
176 | void OpenModule(string fileName, short language); | ||
177 | |||
178 | /// <summary> | ||
179 | /// The CloseDatabase method of the Merge object closes the currently open Windows Installer database. | ||
180 | /// </summary> | ||
181 | /// <param name="commit">true if changes should be saved, false otherwise.</param> | ||
182 | void CloseDatabase(bool commit); | ||
183 | |||
184 | /// <summary> | ||
185 | /// The CloseModule method of the Merge object closes the currently open Windows Installer merge module. | ||
186 | /// </summary> | ||
187 | void CloseModule(); | ||
188 | |||
189 | /// <summary> | ||
190 | /// The OpenLog method of the Merge object opens a log file that receives progress and error messages. | ||
191 | /// If the log file already exists, the installer appends new messages. If the log file does not exist, | ||
192 | /// the installer creates a log file. | ||
193 | /// </summary> | ||
194 | /// <param name="fileName">Fully qualified filename pointing to a file to open or create.</param> | ||
195 | void OpenLog(string fileName); | ||
196 | |||
197 | /// <summary> | ||
198 | /// The CloseLog method of the Merge object closes the current log file. | ||
199 | /// </summary> | ||
200 | void CloseLog(); | ||
201 | |||
202 | /// <summary> | ||
203 | /// The Log method of the Merge object writes a text string to the currently open log file. | ||
204 | /// </summary> | ||
205 | /// <param name="message">The text string to display.</param> | ||
206 | void Log(string message); | ||
207 | |||
208 | /// <summary> | ||
209 | /// Gets the errors from the last merge operation. | ||
210 | /// </summary> | ||
211 | /// <value>The errors from the last merge operation.</value> | ||
212 | IMsmErrors Errors | ||
213 | { | ||
214 | get; | ||
215 | } | ||
216 | |||
217 | /// <summary> | ||
218 | /// Gets a collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database. | ||
219 | /// </summary> | ||
220 | /// <value>A collection of Dependency objects that enumerates a set of unsatisfied dependencies for the current database.</value> | ||
221 | object Dependencies | ||
222 | { | ||
223 | get; | ||
224 | } | ||
225 | |||
226 | /// <summary> | ||
227 | /// The Merge method of the Merge object executes a merge of the current database and current | ||
228 | /// module. The merge attaches the components in the module to the feature identified by Feature. | ||
229 | /// The root of the module's directory tree is redirected to the location given by RedirectDir. | ||
230 | /// </summary> | ||
231 | /// <param name="feature">The name of a feature in the database.</param> | ||
232 | /// <param name="redirectDir">The key of an entry in the Directory table of the database. | ||
233 | /// This parameter may be NULL or an empty string.</param> | ||
234 | void Merge(string feature, string redirectDir); | ||
235 | |||
236 | /// <summary> | ||
237 | /// The Connect method of the Merge object connects a module to an additional feature. | ||
238 | /// The module must have already been merged into the database or will be merged into the database. | ||
239 | /// The feature must exist before calling this function. | ||
240 | /// </summary> | ||
241 | /// <param name="feature">The name of a feature already existing in the database.</param> | ||
242 | void Connect(string feature); | ||
243 | |||
244 | /// <summary> | ||
245 | /// The ExtractCAB method of the Merge object extracts the embedded .cab file from a module and | ||
246 | /// saves it as the specified file. The installer creates this file if it does not already exist | ||
247 | /// and overwritten if it does exist. | ||
248 | /// </summary> | ||
249 | /// <param name="fileName">The fully qualified destination file.</param> | ||
250 | void ExtractCAB(string fileName); | ||
251 | |||
252 | /// <summary> | ||
253 | /// The ExtractFiles method of the Merge object extracts the embedded .cab file from a module | ||
254 | /// and then writes those files to the destination directory. | ||
255 | /// </summary> | ||
256 | /// <param name="path">The fully qualified destination directory.</param> | ||
257 | void ExtractFiles(string path); | ||
258 | |||
259 | /// <summary> | ||
260 | /// The MergeEx method of the Merge object is equivalent to the Merge function, except that it | ||
261 | /// takes an extra argument. The Merge method executes a merge of the current database and | ||
262 | /// current module. The merge attaches the components in the module to the feature identified | ||
263 | /// by Feature. The root of the module's directory tree is redirected to the location given by RedirectDir. | ||
264 | /// </summary> | ||
265 | /// <param name="feature">The name of a feature in the database.</param> | ||
266 | /// <param name="redirectDir">The key of an entry in the Directory table of the database. This parameter may | ||
267 | /// be NULL or an empty string.</param> | ||
268 | /// <param name="configuration">The pConfiguration argument is an interface implemented by the client. The argument may | ||
269 | /// be NULL. The presence of this argument indicates that the client is capable of supporting the configuration | ||
270 | /// functionality, but does not obligate the client to provide configuration data for any specific configurable item.</param> | ||
271 | void MergeEx(string feature, string redirectDir, IMsmConfigureModule configuration); | ||
272 | |||
273 | /// <summary> | ||
274 | /// The ExtractFilesEx method of the Merge object extracts the embedded .cab file from a module and | ||
275 | /// then writes those files to the destination directory. | ||
276 | /// </summary> | ||
277 | /// <param name="path">The fully qualified destination directory.</param> | ||
278 | /// <param name="longFileNames">Set to specify using long file names for path segments and final file names.</param> | ||
279 | /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted. | ||
280 | /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param> | ||
281 | void ExtractFilesEx(string path, bool longFileNames, ref IntPtr filePaths); | ||
282 | |||
283 | /// <summary> | ||
284 | /// Gets a collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table. | ||
285 | /// </summary> | ||
286 | /// <value>A collection ConfigurableItem objects, each of which represents a single row from the ModuleConfiguration table.</value> | ||
287 | /// <remarks>Semantically, each interface in the enumerator represents an item that can be configured by the module consumer. | ||
288 | /// The collection is a read-only collection and implements the standard read-only collection interfaces of Item(), Count() and _NewEnum(). | ||
289 | /// The IEnumMsmConfigItems enumerator implements Next(), Skip(), Reset(), and Clone() with the standard semantics.</remarks> | ||
290 | object ConfigurableItems | ||
291 | { | ||
292 | get; | ||
293 | } | ||
294 | |||
295 | /// <summary> | ||
296 | /// The CreateSourceImage method of the Merge object allows the client to extract the files from a module to | ||
297 | /// a source image on disk after a merge, taking into account changes to the module that might have been made | ||
298 | /// during module configuration. The list of files to be extracted is taken from the file table of the module | ||
299 | /// during the merge process. The list of files consists of every file successfully copied from the file table | ||
300 | /// of the module to the target database. File table entries that were not copied due to primary key conflicts | ||
301 | /// with existing rows in the database are not a part of this list. At image creation time, the directory for | ||
302 | /// each of these files comes from the open (post-merge) database. The path specified in the Path parameter is | ||
303 | /// the root of the source image for the install. fLongFileNames determines whether or not long file names are | ||
304 | /// used for both path segments and final file names. The function fails if no database is open, no module is | ||
305 | /// open, or no merge has been performed. | ||
306 | /// </summary> | ||
307 | /// <param name="path">The path of the root of the source image for the install.</param> | ||
308 | /// <param name="longFileNames">Determines whether or not long file names are used for both path segments and final file names. </param> | ||
309 | /// <param name="filePaths">This is a list of fully-qualified paths for the files that were successfully extracted. | ||
310 | /// The list is empty if no files can be extracted. This argument may be null. No list is provided if pFilePaths is null.</param> | ||
311 | void CreateSourceImage(string path, bool longFileNames, ref IntPtr filePaths); | ||
312 | |||
313 | /// <summary> | ||
314 | /// The get_ModuleFiles function implements the ModuleFiles property of the GetFiles object. This function | ||
315 | /// returns the primary keys in the File table of the currently open module. The primary keys are returned | ||
316 | /// as a collection of strings. The module must be opened by a call to the OpenModule function before calling get_ModuleFiles. | ||
317 | /// </summary> | ||
318 | IMsmStrings ModuleFiles | ||
319 | { | ||
320 | get; | ||
321 | } | ||
322 | } | ||
323 | |||
324 | /// <summary> | ||
325 | /// Collection of merge errors. | ||
326 | /// </summary> | ||
327 | [ComImport, Guid("0ADDA82A-2C26-11D2-AD65-00A0C9AF11A6")] | ||
328 | internal interface IMsmErrors | ||
329 | { | ||
330 | /// <summary> | ||
331 | /// Gets the IMsmError at the specified index. | ||
332 | /// </summary> | ||
333 | /// <param name="index">The one-based index of the IMsmError to get.</param> | ||
334 | IMsmError this[int index] | ||
335 | { | ||
336 | get; | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// Gets the count of IMsmErrors in this collection. | ||
341 | /// </summary> | ||
342 | /// <value>The count of IMsmErrors in this collection.</value> | ||
343 | int Count | ||
344 | { | ||
345 | get; | ||
346 | } | ||
347 | } | ||
348 | |||
349 | /// <summary> | ||
350 | /// A merge error. | ||
351 | /// </summary> | ||
352 | [ComImport, Guid("0ADDA828-2C26-11D2-AD65-00A0C9AF11A6")] | ||
353 | internal interface IMsmError | ||
354 | { | ||
355 | /// <summary> | ||
356 | /// Gets the type of merge error. | ||
357 | /// </summary> | ||
358 | /// <value>The type of merge error.</value> | ||
359 | MsmErrorType Type | ||
360 | { | ||
361 | get; | ||
362 | } | ||
363 | |||
364 | /// <summary> | ||
365 | /// Gets the path information from the merge error. | ||
366 | /// </summary> | ||
367 | /// <value>The path information from the merge error.</value> | ||
368 | string Path | ||
369 | { | ||
370 | get; | ||
371 | } | ||
372 | |||
373 | /// <summary> | ||
374 | /// Gets the language information from the merge error. | ||
375 | /// </summary> | ||
376 | /// <value>The language information from the merge error.</value> | ||
377 | short Language | ||
378 | { | ||
379 | get; | ||
380 | } | ||
381 | |||
382 | /// <summary> | ||
383 | /// Gets the database table from the merge error. | ||
384 | /// </summary> | ||
385 | /// <value>The database table from the merge error.</value> | ||
386 | string DatabaseTable | ||
387 | { | ||
388 | get; | ||
389 | } | ||
390 | |||
391 | /// <summary> | ||
392 | /// Gets the collection of database keys from the merge error. | ||
393 | /// </summary> | ||
394 | /// <value>The collection of database keys from the merge error.</value> | ||
395 | IMsmStrings DatabaseKeys | ||
396 | { | ||
397 | get; | ||
398 | } | ||
399 | |||
400 | /// <summary> | ||
401 | /// Gets the module table from the merge error. | ||
402 | /// </summary> | ||
403 | /// <value>The module table from the merge error.</value> | ||
404 | string ModuleTable | ||
405 | { | ||
406 | get; | ||
407 | } | ||
408 | |||
409 | /// <summary> | ||
410 | /// Gets the collection of module keys from the merge error. | ||
411 | /// </summary> | ||
412 | /// <value>The collection of module keys from the merge error.</value> | ||
413 | IMsmStrings ModuleKeys | ||
414 | { | ||
415 | get; | ||
416 | } | ||
417 | } | ||
418 | |||
419 | /// <summary> | ||
420 | /// A collection of strings. | ||
421 | /// </summary> | ||
422 | [ComImport, Guid("0ADDA827-2C26-11D2-AD65-00A0C9AF11A6")] | ||
423 | internal interface IMsmStrings | ||
424 | { | ||
425 | /// <summary> | ||
426 | /// Gets the string at the specified index. | ||
427 | /// </summary> | ||
428 | /// <param name="index">The one-based index of the string to get.</param> | ||
429 | string this[int index] | ||
430 | { | ||
431 | get; | ||
432 | } | ||
433 | |||
434 | /// <summary> | ||
435 | /// Gets the count of strings in this collection. | ||
436 | /// </summary> | ||
437 | /// <value>The count of strings in this collection.</value> | ||
438 | int Count | ||
439 | { | ||
440 | get; | ||
441 | } | ||
442 | } | ||
443 | |||
444 | /// <summary> | ||
445 | /// Callback for configurable merge modules. | ||
446 | /// </summary> | ||
447 | [ComImport, Guid("AC013209-18A7-4851-8A21-2353443D70A0"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] | ||
448 | internal interface IMsmConfigureModule | ||
449 | { | ||
450 | /// <summary> | ||
451 | /// Callback to retrieve text data for configurable merge modules. | ||
452 | /// </summary> | ||
453 | /// <param name="name">Name of the data to be retrieved.</param> | ||
454 | /// <param name="configData">The data corresponding to the name.</param> | ||
455 | /// <returns>The error code (HRESULT).</returns> | ||
456 | [PreserveSig] | ||
457 | int ProvideTextData([In, MarshalAs(UnmanagedType.BStr)] string name, [MarshalAs(UnmanagedType.BStr)] out string configData); | ||
458 | |||
459 | /// <summary> | ||
460 | /// Callback to retrieve integer data for configurable merge modules. | ||
461 | /// </summary> | ||
462 | /// <param name="name">Name of the data to be retrieved.</param> | ||
463 | /// <param name="configData">The data corresponding to the name.</param> | ||
464 | /// <returns>The error code (HRESULT).</returns> | ||
465 | [PreserveSig] | ||
466 | int ProvideIntegerData([In, MarshalAs(UnmanagedType.BStr)] string name, out int configData); | ||
467 | } | ||
468 | |||
469 | /// <summary> | ||
470 | /// Merge merge modules into an MSI file. | ||
471 | /// </summary> | ||
472 | [ComImport, Guid("F94985D5-29F9-4743-9805-99BC3F35B678")] | ||
473 | internal class MsmMerge2 | ||
474 | { | ||
475 | } | ||
476 | |||
477 | /// <summary> | ||
478 | /// Defines the standard COM IClassFactory interface. | ||
479 | /// </summary> | ||
480 | [ComImport, Guid("00000001-0000-0000-C000-000000000046")] | ||
481 | [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
482 | internal interface IClassFactory | ||
483 | { | ||
484 | [return:MarshalAs(UnmanagedType.IUnknown)] | ||
485 | object CreateInstance(IntPtr unkOuter, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); | ||
486 | } | ||
487 | |||
488 | /// <summary> | ||
489 | /// Contains native methods for merge operations. | ||
490 | /// </summary> | ||
491 | internal class NativeMethods | ||
492 | { | ||
493 | [DllImport("mergemod.dll", EntryPoint="DllGetClassObject", PreserveSig=false)] | ||
494 | [return: MarshalAs(UnmanagedType.IUnknown)] | ||
495 | private static extern object MergeModGetClassObject([MarshalAs(UnmanagedType.LPStruct)] Guid clsid, [MarshalAs(UnmanagedType.LPStruct)] Guid iid); | ||
496 | |||
497 | /// <summary> | ||
498 | /// Load the merge object directly from a local mergemod.dll without going through COM registration. | ||
499 | /// </summary> | ||
500 | /// <returns>Merge interface.</returns> | ||
501 | internal static IMsmMerge2 GetMsmMerge() | ||
502 | { | ||
503 | IClassFactory classFactory = (IClassFactory) MergeModGetClassObject(typeof(MsmMerge2).GUID, typeof(IClassFactory).GUID); | ||
504 | return (IMsmMerge2) classFactory.CreateInstance(IntPtr.Zero, typeof(IMsmMerge2).GUID); | ||
505 | } | ||
506 | } | ||
507 | } | ||
508 | #endif \ No newline at end of file | ||
diff --git a/src/WixToolset.Core/Msi/Database.cs b/src/WixToolset.Core/Msi/Database.cs deleted file mode 100644 index 801ebdde..00000000 --- a/src/WixToolset.Core/Msi/Database.cs +++ /dev/null | |||
@@ -1,303 +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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Globalization; | ||
8 | using System.IO; | ||
9 | using System.Text; | ||
10 | using System.Threading; | ||
11 | using WixToolset.Data; | ||
12 | using WixToolset.Core.Native; | ||
13 | |||
14 | /// <summary> | ||
15 | /// Wrapper class for managing MSI API database handles. | ||
16 | /// </summary> | ||
17 | internal sealed class Database : MsiHandle | ||
18 | { | ||
19 | private const int STG_E_LOCKVIOLATION = unchecked((int)0x80030021); | ||
20 | |||
21 | /// <summary> | ||
22 | /// Constructor that opens an MSI database. | ||
23 | /// </summary> | ||
24 | /// <param name="path">Path to the database to be opened.</param> | ||
25 | /// <param name="type">Persist mode to use when opening the database.</param> | ||
26 | public Database(string path, OpenDatabase type) | ||
27 | { | ||
28 | uint handle = 0; | ||
29 | int error = MsiInterop.MsiOpenDatabase(path, new IntPtr((int)type), out handle); | ||
30 | if (0 != error) | ||
31 | { | ||
32 | throw new MsiException(error); | ||
33 | } | ||
34 | this.Handle = handle; | ||
35 | } | ||
36 | |||
37 | public void ApplyTransform(string transformFile) | ||
38 | { | ||
39 | // get the curret validation bits | ||
40 | TransformErrorConditions conditions = TransformErrorConditions.None; | ||
41 | using (SummaryInformation summaryInfo = new SummaryInformation(transformFile)) | ||
42 | { | ||
43 | string value = summaryInfo.GetProperty((int)SummaryInformation.Transform.ValidationFlags); | ||
44 | try | ||
45 | { | ||
46 | int validationFlags = Int32.Parse(value, CultureInfo.InvariantCulture); | ||
47 | conditions = (TransformErrorConditions)(validationFlags & 0xffff); | ||
48 | } | ||
49 | catch (FormatException) | ||
50 | { | ||
51 | // fallback to default of None | ||
52 | } | ||
53 | } | ||
54 | |||
55 | this.ApplyTransform(transformFile, conditions); | ||
56 | } | ||
57 | |||
58 | /// <summary> | ||
59 | /// Applies a transform to this database. | ||
60 | /// </summary> | ||
61 | /// <param name="transformFile">Path to the transform file being applied.</param> | ||
62 | /// <param name="errorConditions">Specifies the error conditions that are to be suppressed.</param> | ||
63 | public void ApplyTransform(string transformFile, TransformErrorConditions errorConditions) | ||
64 | { | ||
65 | int error = MsiInterop.MsiDatabaseApplyTransform(this.Handle, transformFile, errorConditions); | ||
66 | if (0 != error) | ||
67 | { | ||
68 | throw new MsiException(error); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | /// <summary> | ||
73 | /// Commits changes made to the database. | ||
74 | /// </summary> | ||
75 | public void Commit() | ||
76 | { | ||
77 | // Retry this call 3 times to deal with an MSI internal locking problem. | ||
78 | const int retryWait = 300; | ||
79 | const int retryLimit = 3; | ||
80 | int error = 0; | ||
81 | |||
82 | for (int i = 1; i <= retryLimit; ++i) | ||
83 | { | ||
84 | error = MsiInterop.MsiDatabaseCommit(this.Handle); | ||
85 | |||
86 | if (0 == error) | ||
87 | { | ||
88 | return; | ||
89 | } | ||
90 | else | ||
91 | { | ||
92 | MsiException exception = new MsiException(error); | ||
93 | |||
94 | // We need to see if the error code is contained in any of the strings in ErrorInfo. | ||
95 | // Join the array together and search for the error code to cover the string array. | ||
96 | if (!String.Join(", ", exception.ErrorInfo).Contains(STG_E_LOCKVIOLATION.ToString())) | ||
97 | { | ||
98 | break; | ||
99 | } | ||
100 | |||
101 | Console.Error.WriteLine(String.Format("Failed to create the database. Info: {0}. Retrying ({1} of {2})", String.Join(", ", exception.ErrorInfo), i, retryLimit)); | ||
102 | Thread.Sleep(retryWait); | ||
103 | } | ||
104 | } | ||
105 | |||
106 | throw new MsiException(error); | ||
107 | } | ||
108 | |||
109 | /// <summary> | ||
110 | /// Creates and populates the summary information stream of an existing transform file. | ||
111 | /// </summary> | ||
112 | /// <param name="referenceDatabase">Required database that does not include the changes.</param> | ||
113 | /// <param name="transformFile">The name of the generated transform file.</param> | ||
114 | /// <param name="errorConditions">Required error conditions that should be suppressed when the transform is applied.</param> | ||
115 | /// <param name="validations">Required when the transform is applied to a database; | ||
116 | /// shows which properties should be validated to verify that this transform can be applied to the database.</param> | ||
117 | public void CreateTransformSummaryInfo(Database referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations) | ||
118 | { | ||
119 | int error = MsiInterop.MsiCreateTransformSummaryInfo(this.Handle, referenceDatabase.Handle, transformFile, errorConditions, validations); | ||
120 | if (0 != error) | ||
121 | { | ||
122 | throw new MsiException(error); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | /// <summary> | ||
127 | /// Imports an installer text archive table (idt file) into an open database. | ||
128 | /// </summary> | ||
129 | /// <param name="idtPath">Specifies the path to the file to import.</param> | ||
130 | /// <exception cref="WixInvalidIdtException">Attempted to import an IDT file with an invalid format or unsupported data.</exception> | ||
131 | /// <exception cref="MsiException">Another error occured while importing the IDT file.</exception> | ||
132 | public void Import(string idtPath) | ||
133 | { | ||
134 | string folderPath = Path.GetFullPath(Path.GetDirectoryName(idtPath)); | ||
135 | string fileName = Path.GetFileName(idtPath); | ||
136 | |||
137 | int error = MsiInterop.MsiDatabaseImport(this.Handle, folderPath, fileName); | ||
138 | if (1627 == error) // ERROR_FUNCTION_FAILED | ||
139 | { | ||
140 | throw new WixInvalidIdtException(idtPath); | ||
141 | } | ||
142 | else if (0 != error) | ||
143 | { | ||
144 | throw new MsiException(error); | ||
145 | } | ||
146 | } | ||
147 | |||
148 | /// <summary> | ||
149 | /// Exports an installer table from an open database to a text archive file (idt file). | ||
150 | /// </summary> | ||
151 | /// <param name="tableName">Specifies the name of the table to export.</param> | ||
152 | /// <param name="folderPath">Specifies the name of the folder that contains archive files. If null or empty string, uses current directory.</param> | ||
153 | /// <param name="fileName">Specifies the name of the exported table archive file.</param> | ||
154 | public void Export(string tableName, string folderPath, string fileName) | ||
155 | { | ||
156 | if (null == folderPath || 0 == folderPath.Length) | ||
157 | { | ||
158 | folderPath = System.Environment.CurrentDirectory; | ||
159 | } | ||
160 | |||
161 | int error = MsiInterop.MsiDatabaseExport(this.Handle, tableName, folderPath, fileName); | ||
162 | if (0 != error) | ||
163 | { | ||
164 | throw new MsiException(error); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | /// <summary> | ||
169 | /// Creates a transform that, when applied to the reference database, results in this database. | ||
170 | /// </summary> | ||
171 | /// <param name="referenceDatabase">Required database that does not include the changes.</param> | ||
172 | /// <param name="transformFile">The name of the generated transform file. This is optional.</param> | ||
173 | /// <returns>true if a transform is generated; false if a transform is not generated because | ||
174 | /// there are no differences between the two databases.</returns> | ||
175 | public bool GenerateTransform(Database referenceDatabase, string transformFile) | ||
176 | { | ||
177 | int error = MsiInterop.MsiDatabaseGenerateTransform(this.Handle, referenceDatabase.Handle, transformFile, 0, 0); | ||
178 | if (0 != error && 0xE8 != error) // ERROR_NO_DATA(0xE8) means no differences were found | ||
179 | { | ||
180 | throw new MsiException(error); | ||
181 | } | ||
182 | |||
183 | return (0xE8 != error); | ||
184 | } | ||
185 | |||
186 | /// <summary> | ||
187 | /// Merges two databases together. | ||
188 | /// </summary> | ||
189 | /// <param name="mergeDatabase">The database to merge into the base database.</param> | ||
190 | /// <param name="tableName">The name of the table to receive merge conflict information.</param> | ||
191 | public void Merge(Database mergeDatabase, string tableName) | ||
192 | { | ||
193 | int error = MsiInterop.MsiDatabaseMerge(this.Handle, mergeDatabase.Handle, tableName); | ||
194 | if (0 != error) | ||
195 | { | ||
196 | throw new MsiException(error); | ||
197 | } | ||
198 | } | ||
199 | |||
200 | /// <summary> | ||
201 | /// Prepares a database query and creates a <see cref="View">View</see> object. | ||
202 | /// </summary> | ||
203 | /// <param name="query">Specifies a SQL query string for querying the database.</param> | ||
204 | /// <returns>A view object is returned if the query was successful.</returns> | ||
205 | public View OpenView(string query) | ||
206 | { | ||
207 | return new View(this, query); | ||
208 | } | ||
209 | |||
210 | /// <summary> | ||
211 | /// Prepares and executes a database query and creates a <see cref="View">View</see> object. | ||
212 | /// </summary> | ||
213 | /// <param name="query">Specifies a SQL query string for querying the database.</param> | ||
214 | /// <returns>A view object is returned if the query was successful.</returns> | ||
215 | public View OpenExecuteView(string query) | ||
216 | { | ||
217 | View view = new View(this, query); | ||
218 | |||
219 | view.Execute(); | ||
220 | return view; | ||
221 | } | ||
222 | |||
223 | /// <summary> | ||
224 | /// Verifies the existence or absence of a table. | ||
225 | /// </summary> | ||
226 | /// <param name="tableName">Table name to to verify the existence of.</param> | ||
227 | /// <returns>Returns true if the table exists, false if it does not.</returns> | ||
228 | public bool TableExists(string tableName) | ||
229 | { | ||
230 | int result = MsiInterop.MsiDatabaseIsTablePersistent(this.Handle, tableName); | ||
231 | return MsiInterop.MSICONDITIONTRUE == result; | ||
232 | } | ||
233 | |||
234 | /// <summary> | ||
235 | /// Returns a <see cref="Record">Record</see> containing the names of all the primary | ||
236 | /// key columns for a specified table. | ||
237 | /// </summary> | ||
238 | /// <param name="tableName">Specifies the name of the table from which to obtain | ||
239 | /// primary key names.</param> | ||
240 | /// <returns>Returns a <see cref="Record">Record</see> containing the names of all the | ||
241 | /// primary key columns for a specified table.</returns> | ||
242 | public Record PrimaryKeys(string tableName) | ||
243 | { | ||
244 | uint recordHandle; | ||
245 | int error = MsiInterop.MsiDatabaseGetPrimaryKeys(this.Handle, tableName, out recordHandle); | ||
246 | if (0 != error) | ||
247 | { | ||
248 | throw new MsiException(error); | ||
249 | } | ||
250 | |||
251 | return new Record(recordHandle); | ||
252 | } | ||
253 | |||
254 | /// <summary> | ||
255 | /// Imports a table into the database. | ||
256 | /// </summary> | ||
257 | /// <param name="codepage">Codepage of the database to import table to.</param> | ||
258 | /// <param name="table">Table to import into database.</param> | ||
259 | /// <param name="baseDirectory">The base directory where intermediate files are created.</param> | ||
260 | /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> | ||
261 | public void ImportTable(int codepage, Table table, string baseDirectory, bool keepAddedColumns) | ||
262 | { | ||
263 | // write out the table to an IDT file | ||
264 | string idtPath = Path.Combine(baseDirectory, String.Concat(table.Name, ".idt")); | ||
265 | Encoding encoding; | ||
266 | |||
267 | // If UTF8 encoding, use the UTF8-specific constructor to avoid writing | ||
268 | // the byte order mark at the beginning of the file | ||
269 | if (Encoding.UTF8.CodePage == codepage) | ||
270 | { | ||
271 | encoding = new UTF8Encoding(false, true); | ||
272 | } | ||
273 | else | ||
274 | { | ||
275 | if (0 == codepage) | ||
276 | { | ||
277 | codepage = Encoding.ASCII.CodePage; | ||
278 | } | ||
279 | |||
280 | encoding = Encoding.GetEncoding(codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback()); | ||
281 | } | ||
282 | |||
283 | using (StreamWriter idtWriter = new StreamWriter(idtPath, false, encoding)) | ||
284 | { | ||
285 | table.ToIdtDefinition(idtWriter, keepAddedColumns); | ||
286 | } | ||
287 | |||
288 | // try to import the table into the MSI | ||
289 | try | ||
290 | { | ||
291 | this.Import(idtPath); | ||
292 | } | ||
293 | catch (WixInvalidIdtException) | ||
294 | { | ||
295 | table.ValidateRows(); | ||
296 | |||
297 | // If ValidateRows finds anything it doesn't like, it throws. Otherwise, we'll | ||
298 | // throw WixInvalidIdtException here which is caught in light and turns off tidy. | ||
299 | throw new WixInvalidIdtException(idtPath, table.Name); | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | } | ||
diff --git a/src/WixToolset.Core/Msi/Installer.cs b/src/WixToolset.Core/Msi/Installer.cs deleted file mode 100644 index 3beb26f4..00000000 --- a/src/WixToolset.Core/Msi/Installer.cs +++ /dev/null | |||
@@ -1,484 +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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics; | ||
7 | using System.Text; | ||
8 | using WixToolset.Core.Native; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Windows Installer message types. | ||
12 | /// </summary> | ||
13 | [Flags] | ||
14 | internal enum InstallMessage | ||
15 | { | ||
16 | /// <summary> | ||
17 | /// Premature termination, possibly fatal out of memory. | ||
18 | /// </summary> | ||
19 | FatalExit = 0x00000000, | ||
20 | |||
21 | /// <summary> | ||
22 | /// Formatted error message, [1] is message number in Error table. | ||
23 | /// </summary> | ||
24 | Error = 0x01000000, | ||
25 | |||
26 | /// <summary> | ||
27 | /// Formatted warning message, [1] is message number in Error table. | ||
28 | /// </summary> | ||
29 | Warning = 0x02000000, | ||
30 | |||
31 | /// <summary> | ||
32 | /// User request message, [1] is message number in Error table. | ||
33 | /// </summary> | ||
34 | User = 0x03000000, | ||
35 | |||
36 | /// <summary> | ||
37 | /// Informative message for log, not to be displayed. | ||
38 | /// </summary> | ||
39 | Info = 0x04000000, | ||
40 | |||
41 | /// <summary> | ||
42 | /// List of files in use that need to be replaced. | ||
43 | /// </summary> | ||
44 | FilesInUse = 0x05000000, | ||
45 | |||
46 | /// <summary> | ||
47 | /// Request to determine a valid source location. | ||
48 | /// </summary> | ||
49 | ResolveSource = 0x06000000, | ||
50 | |||
51 | /// <summary> | ||
52 | /// Insufficient disk space message. | ||
53 | /// </summary> | ||
54 | OutOfDiskSpace = 0x07000000, | ||
55 | |||
56 | /// <summary> | ||
57 | /// Progress: start of action, [1] action name, [2] description, [3] template for ACTIONDATA messages. | ||
58 | /// </summary> | ||
59 | ActionStart = 0x08000000, | ||
60 | |||
61 | /// <summary> | ||
62 | /// Action data. Record fields correspond to the template of ACTIONSTART message. | ||
63 | /// </summary> | ||
64 | ActionData = 0x09000000, | ||
65 | |||
66 | /// <summary> | ||
67 | /// Progress bar information. See the description of record fields below. | ||
68 | /// </summary> | ||
69 | Progress = 0x0A000000, | ||
70 | |||
71 | /// <summary> | ||
72 | /// To enable the Cancel button set [1] to 2 and [2] to 1. To disable the Cancel button set [1] to 2 and [2] to 0. | ||
73 | /// </summary> | ||
74 | CommonData = 0x0B000000, | ||
75 | |||
76 | /// <summary> | ||
77 | /// Sent prior to UI initialization, no string data. | ||
78 | /// </summary> | ||
79 | Initilize = 0x0C000000, | ||
80 | |||
81 | /// <summary> | ||
82 | /// Sent after UI termination, no string data. | ||
83 | /// </summary> | ||
84 | Terminate = 0x0D000000, | ||
85 | |||
86 | /// <summary> | ||
87 | /// Sent prior to display or authored dialog or wizard. | ||
88 | /// </summary> | ||
89 | ShowDialog = 0x0E000000 | ||
90 | } | ||
91 | |||
92 | /// <summary> | ||
93 | /// Windows Installer log modes. | ||
94 | /// </summary> | ||
95 | [Flags] | ||
96 | internal enum InstallLogModes | ||
97 | { | ||
98 | /// <summary> | ||
99 | /// Premature termination of installation. | ||
100 | /// </summary> | ||
101 | FatalExit = (1 << ((int)InstallMessage.FatalExit >> 24)), | ||
102 | |||
103 | /// <summary> | ||
104 | /// The error messages are logged. | ||
105 | /// </summary> | ||
106 | Error = (1 << ((int)InstallMessage.Error >> 24)), | ||
107 | |||
108 | /// <summary> | ||
109 | /// The warning messages are logged. | ||
110 | /// </summary> | ||
111 | Warning = (1 << ((int)InstallMessage.Warning >> 24)), | ||
112 | |||
113 | /// <summary> | ||
114 | /// The user requests are logged. | ||
115 | /// </summary> | ||
116 | User = (1 << ((int)InstallMessage.User >> 24)), | ||
117 | |||
118 | /// <summary> | ||
119 | /// The status messages that are not displayed are logged. | ||
120 | /// </summary> | ||
121 | Info = (1 << ((int)InstallMessage.Info >> 24)), | ||
122 | |||
123 | /// <summary> | ||
124 | /// Request to determine a valid source location. | ||
125 | /// </summary> | ||
126 | ResolveSource = (1 << ((int)InstallMessage.ResolveSource >> 24)), | ||
127 | |||
128 | /// <summary> | ||
129 | /// The was insufficient disk space. | ||
130 | /// </summary> | ||
131 | OutOfDiskSpace = (1 << ((int)InstallMessage.OutOfDiskSpace >> 24)), | ||
132 | |||
133 | /// <summary> | ||
134 | /// The start of new installation actions are logged. | ||
135 | /// </summary> | ||
136 | ActionStart = (1 << ((int)InstallMessage.ActionStart >> 24)), | ||
137 | |||
138 | /// <summary> | ||
139 | /// The data record with the installation action is logged. | ||
140 | /// </summary> | ||
141 | ActionData = (1 << ((int)InstallMessage.ActionData >> 24)), | ||
142 | |||
143 | /// <summary> | ||
144 | /// The parameters for user-interface initialization are logged. | ||
145 | /// </summary> | ||
146 | CommonData = (1 << ((int)InstallMessage.CommonData >> 24)), | ||
147 | |||
148 | /// <summary> | ||
149 | /// Logs the property values at termination. | ||
150 | /// </summary> | ||
151 | PropertyDump = (1 << ((int)InstallMessage.Progress >> 24)), | ||
152 | |||
153 | /// <summary> | ||
154 | /// Sends large amounts of information to a log file not generally useful to users. | ||
155 | /// May be used for technical support. | ||
156 | /// </summary> | ||
157 | Verbose = (1 << ((int)InstallMessage.Initilize >> 24)), | ||
158 | |||
159 | /// <summary> | ||
160 | /// Sends extra debugging information, such as handle creation information, to the log file. | ||
161 | /// </summary> | ||
162 | ExtraDebug = (1 << ((int)InstallMessage.Terminate >> 24)), | ||
163 | |||
164 | /// <summary> | ||
165 | /// Progress bar information. This message includes information on units so far and total number of units. | ||
166 | /// See MsiProcessMessage for an explanation of the message format. | ||
167 | /// This message is only sent to an external user interface and is not logged. | ||
168 | /// </summary> | ||
169 | Progress = (1 << ((int)InstallMessage.Progress >> 24)), | ||
170 | |||
171 | /// <summary> | ||
172 | /// If this is not a quiet installation, then the basic UI has been initialized. | ||
173 | /// If this is a full UI installation, the full UI is not yet initialized. | ||
174 | /// This message is only sent to an external user interface and is not logged. | ||
175 | /// </summary> | ||
176 | Initialize = (1 << ((int)InstallMessage.Initilize >> 24)), | ||
177 | |||
178 | /// <summary> | ||
179 | /// If a full UI is being used, the full UI has ended. | ||
180 | /// If this is not a quiet installation, the basic UI has not yet ended. | ||
181 | /// This message is only sent to an external user interface and is not logged. | ||
182 | /// </summary> | ||
183 | Terminate = (1 << ((int)InstallMessage.Terminate >> 24)), | ||
184 | |||
185 | /// <summary> | ||
186 | /// Sent prior to display of the full UI dialog. | ||
187 | /// This message is only sent to an external user interface and is not logged. | ||
188 | /// </summary> | ||
189 | ShowDialog = (1 << ((int)InstallMessage.ShowDialog >> 24)), | ||
190 | |||
191 | /// <summary> | ||
192 | /// Files in use information. When this message is received, a FilesInUse Dialog should be displayed. | ||
193 | /// </summary> | ||
194 | FilesInUse = (1 << ((int)InstallMessage.FilesInUse >> 24)) | ||
195 | } | ||
196 | |||
197 | /// <summary> | ||
198 | /// Windows Installer UI levels. | ||
199 | /// </summary> | ||
200 | [Flags] | ||
201 | internal enum InstallUILevels | ||
202 | { | ||
203 | /// <summary> | ||
204 | /// No change in the UI level. However, if phWnd is not Null, the parent window can change. | ||
205 | /// </summary> | ||
206 | NoChange = 0, | ||
207 | |||
208 | /// <summary> | ||
209 | /// The installer chooses an appropriate user interface level. | ||
210 | /// </summary> | ||
211 | Default = 1, | ||
212 | |||
213 | /// <summary> | ||
214 | /// Completely silent installation. | ||
215 | /// </summary> | ||
216 | None = 2, | ||
217 | |||
218 | /// <summary> | ||
219 | /// Simple progress and error handling. | ||
220 | /// </summary> | ||
221 | Basic = 3, | ||
222 | |||
223 | /// <summary> | ||
224 | /// Authored user interface with wizard dialog boxes suppressed. | ||
225 | /// </summary> | ||
226 | Reduced = 4, | ||
227 | |||
228 | /// <summary> | ||
229 | /// Authored user interface with wizards, progress, and errors. | ||
230 | /// </summary> | ||
231 | Full = 5, | ||
232 | |||
233 | /// <summary> | ||
234 | /// If combined with the Basic value, the installer shows simple progress dialog boxes but | ||
235 | /// does not display a Cancel button on the dialog. This prevents users from canceling the install. | ||
236 | /// Available with Windows Installer version 2.0. | ||
237 | /// </summary> | ||
238 | HideCancel = 0x20, | ||
239 | |||
240 | /// <summary> | ||
241 | /// If combined with the Basic value, the installer shows simple progress | ||
242 | /// dialog boxes but does not display any modal dialog boxes or error dialog boxes. | ||
243 | /// </summary> | ||
244 | ProgressOnly = 0x40, | ||
245 | |||
246 | /// <summary> | ||
247 | /// If combined with any above value, the installer displays a modal dialog | ||
248 | /// box at the end of a successful installation or if there has been an error. | ||
249 | /// No dialog box is displayed if the user cancels. | ||
250 | /// </summary> | ||
251 | EndDialog = 0x80, | ||
252 | |||
253 | /// <summary> | ||
254 | /// If this value is combined with the None value, the installer displays only the dialog | ||
255 | /// boxes used for source resolution. No other dialog boxes are shown. This value has no | ||
256 | /// effect if the UI level is not INSTALLUILEVEL_NONE. It is used with an external user | ||
257 | /// interface designed to handle all of the UI except for source resolution. In this case, | ||
258 | /// the installer handles source resolution. This value is only available with Windows Installer 2.0 and later. | ||
259 | /// </summary> | ||
260 | SourceResOnly = 0x100 | ||
261 | } | ||
262 | |||
263 | /// <summary> | ||
264 | /// Represents the Windows Installer, provides wrappers to | ||
265 | /// create the top-level objects and access their methods. | ||
266 | /// </summary> | ||
267 | internal sealed class Installer | ||
268 | { | ||
269 | /// <summary> | ||
270 | /// Protect the constructor. | ||
271 | /// </summary> | ||
272 | private Installer() | ||
273 | { | ||
274 | } | ||
275 | |||
276 | /// <summary> | ||
277 | /// Takes the path to a file and returns a 128-bit hash of that file. | ||
278 | /// </summary> | ||
279 | /// <param name="filePath">Path to file that is to be hashed.</param> | ||
280 | /// <param name="options">The value in this column must be 0. This parameter is reserved for future use.</param> | ||
281 | /// <param name="hash">Int array that receives the returned file hash information.</param> | ||
282 | internal static void GetFileHash(string filePath, int options, out int[] hash) | ||
283 | { | ||
284 | MsiInterop.MSIFILEHASHINFO hashInterop = new MsiInterop.MSIFILEHASHINFO(); | ||
285 | hashInterop.FileHashInfoSize = 20; | ||
286 | |||
287 | int error = MsiInterop.MsiGetFileHash(filePath, Convert.ToUInt32(options), hashInterop); | ||
288 | if (0 != error) | ||
289 | { | ||
290 | throw new MsiException(error); | ||
291 | } | ||
292 | |||
293 | Debug.Assert(20 == hashInterop.FileHashInfoSize); | ||
294 | |||
295 | hash = new int[4]; | ||
296 | hash[0] = hashInterop.Data0; | ||
297 | hash[1] = hashInterop.Data1; | ||
298 | hash[2] = hashInterop.Data2; | ||
299 | hash[3] = hashInterop.Data3; | ||
300 | } | ||
301 | |||
302 | /// <summary> | ||
303 | /// Returns the version string and language string in the format that the installer | ||
304 | /// expects to find them in the database. If you just want version information, set | ||
305 | /// lpLangBuf and pcchLangBuf to zero. If you just want language information, set | ||
306 | /// lpVersionBuf and pcchVersionBuf to zero. | ||
307 | /// </summary> | ||
308 | /// <param name="filePath">Specifies the path to the file.</param> | ||
309 | /// <param name="version">Returns the file version. Set to 0 for language information only.</param> | ||
310 | /// <param name="language">Returns the file language. Set to 0 for version information only.</param> | ||
311 | internal static void GetFileVersion(string filePath, out string version, out string language) | ||
312 | { | ||
313 | int versionLength = 20; | ||
314 | int languageLength = 20; | ||
315 | StringBuilder versionBuffer = new StringBuilder(versionLength); | ||
316 | StringBuilder languageBuffer = new StringBuilder(languageLength); | ||
317 | |||
318 | int error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); | ||
319 | if (234 == error) | ||
320 | { | ||
321 | versionBuffer.EnsureCapacity(++versionLength); | ||
322 | languageBuffer.EnsureCapacity(++languageLength); | ||
323 | error = MsiInterop.MsiGetFileVersion(filePath, versionBuffer, ref versionLength, languageBuffer, ref languageLength); | ||
324 | } | ||
325 | else if (1006 == error) | ||
326 | { | ||
327 | // file has no version or language, so no error | ||
328 | error = 0; | ||
329 | } | ||
330 | |||
331 | if (0 != error) | ||
332 | { | ||
333 | throw new MsiException(error); | ||
334 | } | ||
335 | |||
336 | version = versionBuffer.ToString(); | ||
337 | language = languageBuffer.ToString(); | ||
338 | } | ||
339 | |||
340 | /// <summary> | ||
341 | /// Enables an external user-interface handler. | ||
342 | /// </summary> | ||
343 | /// <param name="installUIHandler">Specifies a callback function.</param> | ||
344 | /// <param name="messageFilter">Specifies which messages to handle using the external message handler.</param> | ||
345 | /// <param name="context">Pointer to an application context that is passed to the callback function.</param> | ||
346 | /// <returns>The return value is the previously set external handler, or null if there was no previously set handler.</returns> | ||
347 | internal static InstallUIHandler SetExternalUI(InstallUIHandler installUIHandler, int messageFilter, IntPtr context) | ||
348 | { | ||
349 | return MsiInterop.MsiSetExternalUI(installUIHandler, messageFilter, context); | ||
350 | } | ||
351 | |||
352 | /// <summary> | ||
353 | /// Enables the installer's internal user interface. | ||
354 | /// </summary> | ||
355 | /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param> | ||
356 | /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created.</param> | ||
357 | /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns> | ||
358 | internal static int SetInternalUI(int uiLevel, ref IntPtr hwnd) | ||
359 | { | ||
360 | return MsiInterop.MsiSetInternalUI(uiLevel, ref hwnd); | ||
361 | } | ||
362 | |||
363 | /// <summary> | ||
364 | /// Get the source/target and short/long file names from an MSI Filename column. | ||
365 | /// </summary> | ||
366 | /// <param name="value">The Filename value.</param> | ||
367 | /// <returns>An array of strings of length 4. The contents are: short target, long target, short source, and long source.</returns> | ||
368 | /// <remarks> | ||
369 | /// If any particular file name part is not parsed, its set to null in the appropriate location of the returned array of strings. | ||
370 | /// However, the returned array will always be of length 4. | ||
371 | /// </remarks> | ||
372 | internal static string[] GetNames(string value) | ||
373 | { | ||
374 | string[] names = new string[4]; | ||
375 | int targetSeparator = value.IndexOf(":", StringComparison.Ordinal); | ||
376 | |||
377 | // split source and target | ||
378 | string sourceName = null; | ||
379 | string targetName = value; | ||
380 | if (0 <= targetSeparator) | ||
381 | { | ||
382 | sourceName = value.Substring(targetSeparator + 1); | ||
383 | targetName = value.Substring(0, targetSeparator); | ||
384 | } | ||
385 | |||
386 | // split the source short and long names | ||
387 | string sourceLongName = null; | ||
388 | if (null != sourceName) | ||
389 | { | ||
390 | int sourceLongNameSeparator = sourceName.IndexOf("|", StringComparison.Ordinal); | ||
391 | if (0 <= sourceLongNameSeparator) | ||
392 | { | ||
393 | sourceLongName = sourceName.Substring(sourceLongNameSeparator + 1); | ||
394 | sourceName = sourceName.Substring(0, sourceLongNameSeparator); | ||
395 | } | ||
396 | } | ||
397 | |||
398 | // split the target short and long names | ||
399 | int targetLongNameSeparator = targetName.IndexOf("|", StringComparison.Ordinal); | ||
400 | string targetLongName = null; | ||
401 | if (0 <= targetLongNameSeparator) | ||
402 | { | ||
403 | targetLongName = targetName.Substring(targetLongNameSeparator + 1); | ||
404 | targetName = targetName.Substring(0, targetLongNameSeparator); | ||
405 | } | ||
406 | |||
407 | // remove the long source name when its identical to the long source name | ||
408 | if (null != sourceName && sourceName == sourceLongName) | ||
409 | { | ||
410 | sourceLongName = null; | ||
411 | } | ||
412 | |||
413 | // remove the long target name when its identical to the long target name | ||
414 | if (null != targetName && targetName == targetLongName) | ||
415 | { | ||
416 | targetLongName = null; | ||
417 | } | ||
418 | |||
419 | // remove the source names when they are identical to the target names | ||
420 | if (sourceName == targetName && sourceLongName == targetLongName) | ||
421 | { | ||
422 | sourceName = null; | ||
423 | sourceLongName = null; | ||
424 | } | ||
425 | |||
426 | // target name(s) | ||
427 | if ("." != targetName) | ||
428 | { | ||
429 | names[0] = targetName; | ||
430 | } | ||
431 | |||
432 | if (null != targetLongName && "." != targetLongName) | ||
433 | { | ||
434 | names[1] = targetLongName; | ||
435 | } | ||
436 | |||
437 | // source name(s) | ||
438 | if (null != sourceName) | ||
439 | { | ||
440 | names[2] = sourceName; | ||
441 | } | ||
442 | |||
443 | if (null != sourceLongName && "." != sourceLongName) | ||
444 | { | ||
445 | names[3] = sourceLongName; | ||
446 | } | ||
447 | |||
448 | return names; | ||
449 | } | ||
450 | |||
451 | /// <summary> | ||
452 | /// Get a source/target and short/long file name from an MSI Filename column. | ||
453 | /// </summary> | ||
454 | /// <param name="value">The Filename value.</param> | ||
455 | /// <param name="source">true to get a source name; false to get a target name</param> | ||
456 | /// <param name="longName">true to get a long name; false to get a short name</param> | ||
457 | /// <returns>The name.</returns> | ||
458 | internal static string GetName(string value, bool source, bool longName) | ||
459 | { | ||
460 | string[] names = GetNames(value); | ||
461 | |||
462 | if (source) | ||
463 | { | ||
464 | if (longName && null != names[3]) | ||
465 | { | ||
466 | return names[3]; | ||
467 | } | ||
468 | else if (null != names[2]) | ||
469 | { | ||
470 | return names[2]; | ||
471 | } | ||
472 | } | ||
473 | |||
474 | if (longName && null != names[1]) | ||
475 | { | ||
476 | return names[1]; | ||
477 | } | ||
478 | else | ||
479 | { | ||
480 | return names[0]; | ||
481 | } | ||
482 | } | ||
483 | } | ||
484 | } | ||
diff --git a/src/WixToolset.Core/Msi/Interop/MsiInterop.cs b/src/WixToolset.Core/Msi/Interop/MsiInterop.cs deleted file mode 100644 index 054289ee..00000000 --- a/src/WixToolset.Core/Msi/Interop/MsiInterop.cs +++ /dev/null | |||
@@ -1,697 +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 | #if false | ||
3 | namespace WixToolset.Msi.Interop | ||
4 | { | ||
5 | using System; | ||
6 | using System.Text; | ||
7 | using System.Runtime.InteropServices; | ||
8 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; | ||
9 | |||
10 | /// <summary> | ||
11 | /// A callback function that the installer calls for progress notification and error messages. | ||
12 | /// </summary> | ||
13 | /// <param name="context">Pointer to an application context. | ||
14 | /// This parameter can be used for error checking.</param> | ||
15 | /// <param name="messageType">Specifies a combination of one message box style, | ||
16 | /// one message box icon type, one default button, and one installation message type.</param> | ||
17 | /// <param name="message">Specifies the message text.</param> | ||
18 | /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns> | ||
19 | internal delegate int InstallUIHandler(IntPtr context, uint messageType, [MarshalAs(UnmanagedType.LPWStr)] string message); | ||
20 | |||
21 | /// <summary> | ||
22 | /// Class exposing static functions and structs from MSI API. | ||
23 | /// </summary> | ||
24 | internal sealed class MsiInterop | ||
25 | { | ||
26 | // Patching constants | ||
27 | internal const int MsiMaxStreamNameLength = 62; // http://msdn2.microsoft.com/library/aa370551.aspx | ||
28 | |||
29 | // Component.Attributes | ||
30 | internal const int MsidbComponentAttributesLocalOnly = 0; | ||
31 | internal const int MsidbComponentAttributesSourceOnly = 1; | ||
32 | internal const int MsidbComponentAttributesOptional = 2; | ||
33 | internal const int MsidbComponentAttributesRegistryKeyPath = 4; | ||
34 | internal const int MsidbComponentAttributesSharedDllRefCount = 8; | ||
35 | internal const int MsidbComponentAttributesPermanent = 16; | ||
36 | internal const int MsidbComponentAttributesODBCDataSource = 32; | ||
37 | internal const int MsidbComponentAttributesTransitive = 64; | ||
38 | internal const int MsidbComponentAttributesNeverOverwrite = 128; | ||
39 | internal const int MsidbComponentAttributes64bit = 256; | ||
40 | internal const int MsidbComponentAttributesDisableRegistryReflection = 512; | ||
41 | internal const int MsidbComponentAttributesUninstallOnSupersedence = 1024; | ||
42 | internal const int MsidbComponentAttributesShared = 2048; | ||
43 | |||
44 | // BBControl.Attributes & Control.Attributes | ||
45 | internal const int MsidbControlAttributesVisible = 0x00000001; | ||
46 | internal const int MsidbControlAttributesEnabled = 0x00000002; | ||
47 | internal const int MsidbControlAttributesSunken = 0x00000004; | ||
48 | internal const int MsidbControlAttributesIndirect = 0x00000008; | ||
49 | internal const int MsidbControlAttributesInteger = 0x00000010; | ||
50 | internal const int MsidbControlAttributesRTLRO = 0x00000020; | ||
51 | internal const int MsidbControlAttributesRightAligned = 0x00000040; | ||
52 | internal const int MsidbControlAttributesLeftScroll = 0x00000080; | ||
53 | internal const int MsidbControlAttributesBiDi = MsidbControlAttributesRTLRO | MsidbControlAttributesRightAligned | MsidbControlAttributesLeftScroll; | ||
54 | |||
55 | // Text controls | ||
56 | internal const int MsidbControlAttributesTransparent = 0x00010000; | ||
57 | internal const int MsidbControlAttributesNoPrefix = 0x00020000; | ||
58 | internal const int MsidbControlAttributesNoWrap = 0x00040000; | ||
59 | internal const int MsidbControlAttributesFormatSize = 0x00080000; | ||
60 | internal const int MsidbControlAttributesUsersLanguage = 0x00100000; | ||
61 | |||
62 | // Edit controls | ||
63 | internal const int MsidbControlAttributesMultiline = 0x00010000; | ||
64 | internal const int MsidbControlAttributesPasswordInput = 0x00200000; | ||
65 | |||
66 | // ProgressBar controls | ||
67 | internal const int MsidbControlAttributesProgress95 = 0x00010000; | ||
68 | |||
69 | // VolumeSelectCombo and DirectoryCombo controls | ||
70 | internal const int MsidbControlAttributesRemovableVolume = 0x00010000; | ||
71 | internal const int MsidbControlAttributesFixedVolume = 0x00020000; | ||
72 | internal const int MsidbControlAttributesRemoteVolume = 0x00040000; | ||
73 | internal const int MsidbControlAttributesCDROMVolume = 0x00080000; | ||
74 | internal const int MsidbControlAttributesRAMDiskVolume = 0x00100000; | ||
75 | internal const int MsidbControlAttributesFloppyVolume = 0x00200000; | ||
76 | |||
77 | // VolumeCostList controls | ||
78 | internal const int MsidbControlShowRollbackCost = 0x00400000; | ||
79 | |||
80 | // ListBox and ComboBox controls | ||
81 | internal const int MsidbControlAttributesSorted = 0x00010000; | ||
82 | internal const int MsidbControlAttributesComboList = 0x00020000; | ||
83 | |||
84 | // picture button controls | ||
85 | internal const int MsidbControlAttributesImageHandle = 0x00010000; | ||
86 | internal const int MsidbControlAttributesPushLike = 0x00020000; | ||
87 | internal const int MsidbControlAttributesBitmap = 0x00040000; | ||
88 | internal const int MsidbControlAttributesIcon = 0x00080000; | ||
89 | internal const int MsidbControlAttributesFixedSize = 0x00100000; | ||
90 | internal const int MsidbControlAttributesIconSize16 = 0x00200000; | ||
91 | internal const int MsidbControlAttributesIconSize32 = 0x00400000; | ||
92 | internal const int MsidbControlAttributesIconSize48 = 0x00600000; | ||
93 | internal const int MsidbControlAttributesElevationShield = 0x00800000; | ||
94 | |||
95 | // RadioButton controls | ||
96 | internal const int MsidbControlAttributesHasBorder = 0x01000000; | ||
97 | |||
98 | // CustomAction.Type | ||
99 | // executable types | ||
100 | internal const int MsidbCustomActionTypeDll = 0x00000001; // Target = entry point name | ||
101 | internal const int MsidbCustomActionTypeExe = 0x00000002; // Target = command line args | ||
102 | internal const int MsidbCustomActionTypeTextData = 0x00000003; // Target = text string to be formatted and set into property | ||
103 | internal const int MsidbCustomActionTypeJScript = 0x00000005; // Target = entry point name; null if none to call | ||
104 | internal const int MsidbCustomActionTypeVBScript = 0x00000006; // Target = entry point name; null if none to call | ||
105 | internal const int MsidbCustomActionTypeInstall = 0x00000007; // Target = property list for nested engine initialization | ||
106 | internal const int MsidbCustomActionTypeSourceBits = 0x00000030; | ||
107 | internal const int MsidbCustomActionTypeTargetBits = 0x00000007; | ||
108 | internal const int MsidbCustomActionTypeReturnBits = 0x000000C0; | ||
109 | internal const int MsidbCustomActionTypeExecuteBits = 0x00000700; | ||
110 | |||
111 | // source of code | ||
112 | internal const int MsidbCustomActionTypeBinaryData = 0x00000000; // Source = Binary.Name; data stored in stream | ||
113 | internal const int MsidbCustomActionTypeSourceFile = 0x00000010; // Source = File.File; file part of installation | ||
114 | internal const int MsidbCustomActionTypeDirectory = 0x00000020; // Source = Directory.Directory; folder containing existing file | ||
115 | internal const int MsidbCustomActionTypeProperty = 0x00000030; // Source = Property.Property; full path to executable | ||
116 | |||
117 | // return processing; default is syncronous execution; process return code | ||
118 | internal const int MsidbCustomActionTypeContinue = 0x00000040; // ignore action return status; continue running | ||
119 | internal const int MsidbCustomActionTypeAsync = 0x00000080; // run asynchronously | ||
120 | |||
121 | // execution scheduling flags; default is execute whenever sequenced | ||
122 | internal const int MsidbCustomActionTypeFirstSequence = 0x00000100; // skip if UI sequence already run | ||
123 | internal const int MsidbCustomActionTypeOncePerProcess = 0x00000200; // skip if UI sequence already run in same process | ||
124 | internal const int MsidbCustomActionTypeClientRepeat = 0x00000300; // run on client only if UI already run on client | ||
125 | internal const int MsidbCustomActionTypeInScript = 0x00000400; // queue for execution within script | ||
126 | internal const int MsidbCustomActionTypeRollback = 0x00000100; // in conjunction with InScript: queue in Rollback script | ||
127 | internal const int MsidbCustomActionTypeCommit = 0x00000200; // in conjunction with InScript: run Commit ops from script on success | ||
128 | |||
129 | // security context flag; default to impersonate as user; valid only if InScript | ||
130 | internal const int MsidbCustomActionTypeNoImpersonate = 0x00000800; // no impersonation; run in system context | ||
131 | internal const int MsidbCustomActionTypeTSAware = 0x00004000; // impersonate for per-machine installs on TS machines | ||
132 | internal const int MsidbCustomActionType64BitScript = 0x00001000; // script should run in 64bit process | ||
133 | internal const int MsidbCustomActionTypeHideTarget = 0x00002000; // don't record the contents of the Target field in the log file. | ||
134 | |||
135 | internal const int MsidbCustomActionTypePatchUninstall = 0x00008000; // run on patch uninstall | ||
136 | |||
137 | // Dialog.Attributes | ||
138 | internal const int MsidbDialogAttributesVisible = 0x00000001; | ||
139 | internal const int MsidbDialogAttributesModal = 0x00000002; | ||
140 | internal const int MsidbDialogAttributesMinimize = 0x00000004; | ||
141 | internal const int MsidbDialogAttributesSysModal = 0x00000008; | ||
142 | internal const int MsidbDialogAttributesKeepModeless = 0x00000010; | ||
143 | internal const int MsidbDialogAttributesTrackDiskSpace = 0x00000020; | ||
144 | internal const int MsidbDialogAttributesUseCustomPalette = 0x00000040; | ||
145 | internal const int MsidbDialogAttributesRTLRO = 0x00000080; | ||
146 | internal const int MsidbDialogAttributesRightAligned = 0x00000100; | ||
147 | internal const int MsidbDialogAttributesLeftScroll = 0x00000200; | ||
148 | internal const int MsidbDialogAttributesBiDi = MsidbDialogAttributesRTLRO | MsidbDialogAttributesRightAligned | MsidbDialogAttributesLeftScroll; | ||
149 | internal const int MsidbDialogAttributesError = 0x00010000; | ||
150 | internal const int CommonControlAttributesInvert = MsidbControlAttributesVisible + MsidbControlAttributesEnabled; | ||
151 | internal const int DialogAttributesInvert = MsidbDialogAttributesVisible + MsidbDialogAttributesModal + MsidbDialogAttributesMinimize; | ||
152 | |||
153 | // Feature.Attributes | ||
154 | internal const int MsidbFeatureAttributesFavorLocal = 0; | ||
155 | internal const int MsidbFeatureAttributesFavorSource = 1; | ||
156 | internal const int MsidbFeatureAttributesFollowParent = 2; | ||
157 | internal const int MsidbFeatureAttributesFavorAdvertise = 4; | ||
158 | internal const int MsidbFeatureAttributesDisallowAdvertise = 8; | ||
159 | internal const int MsidbFeatureAttributesUIDisallowAbsent = 16; | ||
160 | internal const int MsidbFeatureAttributesNoUnsupportedAdvertise = 32; | ||
161 | |||
162 | // File.Attributes | ||
163 | internal const int MsidbFileAttributesReadOnly = 1; | ||
164 | internal const int MsidbFileAttributesHidden = 2; | ||
165 | internal const int MsidbFileAttributesSystem = 4; | ||
166 | internal const int MsidbFileAttributesVital = 512; | ||
167 | internal const int MsidbFileAttributesChecksum = 1024; | ||
168 | internal const int MsidbFileAttributesPatchAdded = 4096; | ||
169 | internal const int MsidbFileAttributesNoncompressed = 8192; | ||
170 | internal const int MsidbFileAttributesCompressed = 16384; | ||
171 | |||
172 | // IniFile.Action & RemoveIniFile.Action | ||
173 | internal const int MsidbIniFileActionAddLine = 0; | ||
174 | internal const int MsidbIniFileActionCreateLine = 1; | ||
175 | internal const int MsidbIniFileActionRemoveLine = 2; | ||
176 | internal const int MsidbIniFileActionAddTag = 3; | ||
177 | internal const int MsidbIniFileActionRemoveTag = 4; | ||
178 | |||
179 | // MoveFile.Options | ||
180 | internal const int MsidbMoveFileOptionsMove = 1; | ||
181 | |||
182 | // ServiceInstall.Attributes | ||
183 | internal const int MsidbServiceInstallOwnProcess = 0x00000010; | ||
184 | internal const int MsidbServiceInstallShareProcess = 0x00000020; | ||
185 | internal const int MsidbServiceInstallInteractive = 0x00000100; | ||
186 | internal const int MsidbServiceInstallAutoStart = 0x00000002; | ||
187 | internal const int MsidbServiceInstallDemandStart = 0x00000003; | ||
188 | internal const int MsidbServiceInstallDisabled = 0x00000004; | ||
189 | internal const int MsidbServiceInstallErrorIgnore = 0x00000000; | ||
190 | internal const int MsidbServiceInstallErrorNormal = 0x00000001; | ||
191 | internal const int MsidbServiceInstallErrorCritical = 0x00000003; | ||
192 | internal const int MsidbServiceInstallErrorControlVital = 0x00008000; | ||
193 | |||
194 | // ServiceConfig.Event | ||
195 | internal const int MsidbServiceConfigEventInstall = 0x00000001; | ||
196 | internal const int MsidbServiceConfigEventUninstall = 0x00000002; | ||
197 | internal const int MsidbServiceConfigEventReinstall = 0x00000004; | ||
198 | |||
199 | // ServiceControl.Attributes | ||
200 | internal const int MsidbServiceControlEventStart = 0x00000001; | ||
201 | internal const int MsidbServiceControlEventStop = 0x00000002; | ||
202 | internal const int MsidbServiceControlEventDelete = 0x00000008; | ||
203 | internal const int MsidbServiceControlEventUninstallStart = 0x00000010; | ||
204 | internal const int MsidbServiceControlEventUninstallStop = 0x00000020; | ||
205 | internal const int MsidbServiceControlEventUninstallDelete = 0x00000080; | ||
206 | |||
207 | // TextStyle.StyleBits | ||
208 | internal const int MsidbTextStyleStyleBitsBold = 1; | ||
209 | internal const int MsidbTextStyleStyleBitsItalic = 2; | ||
210 | internal const int MsidbTextStyleStyleBitsUnderline = 4; | ||
211 | internal const int MsidbTextStyleStyleBitsStrike = 8; | ||
212 | |||
213 | // Upgrade.Attributes | ||
214 | internal const int MsidbUpgradeAttributesMigrateFeatures = 0x00000001; | ||
215 | internal const int MsidbUpgradeAttributesOnlyDetect = 0x00000002; | ||
216 | internal const int MsidbUpgradeAttributesIgnoreRemoveFailure = 0x00000004; | ||
217 | internal const int MsidbUpgradeAttributesVersionMinInclusive = 0x00000100; | ||
218 | internal const int MsidbUpgradeAttributesVersionMaxInclusive = 0x00000200; | ||
219 | internal const int MsidbUpgradeAttributesLanguagesExclusive = 0x00000400; | ||
220 | |||
221 | // Registry Hive Roots | ||
222 | internal const int MsidbRegistryRootClassesRoot = 0; | ||
223 | internal const int MsidbRegistryRootCurrentUser = 1; | ||
224 | internal const int MsidbRegistryRootLocalMachine = 2; | ||
225 | internal const int MsidbRegistryRootUsers = 3; | ||
226 | |||
227 | // Locator Types | ||
228 | internal const int MsidbLocatorTypeDirectory = 0; | ||
229 | internal const int MsidbLocatorTypeFileName = 1; | ||
230 | internal const int MsidbLocatorTypeRawValue = 2; | ||
231 | internal const int MsidbLocatorType64bit = 16; | ||
232 | |||
233 | internal const int MsidbClassAttributesRelativePath = 1; | ||
234 | |||
235 | // RemoveFile.InstallMode | ||
236 | internal const int MsidbRemoveFileInstallModeOnInstall = 0x00000001; | ||
237 | internal const int MsidbRemoveFileInstallModeOnRemove = 0x00000002; | ||
238 | internal const int MsidbRemoveFileInstallModeOnBoth = 0x00000003; | ||
239 | |||
240 | // ODBCDataSource.Registration | ||
241 | internal const int MsidbODBCDataSourceRegistrationPerMachine = 0; | ||
242 | internal const int MsidbODBCDataSourceRegistrationPerUser = 1; | ||
243 | |||
244 | // ModuleConfiguration.Format | ||
245 | internal const int MsidbModuleConfigurationFormatText = 0; | ||
246 | internal const int MsidbModuleConfigurationFormatKey = 1; | ||
247 | internal const int MsidbModuleConfigurationFormatInteger = 2; | ||
248 | internal const int MsidbModuleConfigurationFormatBitfield = 3; | ||
249 | |||
250 | // ModuleConfiguration.Attributes | ||
251 | internal const int MsidbMsmConfigurableOptionKeyNoOrphan = 1; | ||
252 | internal const int MsidbMsmConfigurableOptionNonNullable = 2; | ||
253 | |||
254 | // ' Windows API function ShowWindow constants - used in Shortcut table | ||
255 | internal const int SWSHOWNORMAL = 0x00000001; | ||
256 | internal const int SWSHOWMAXIMIZED = 0x00000003; | ||
257 | internal const int SWSHOWMINNOACTIVE = 0x00000007; | ||
258 | |||
259 | // NameToBit arrays | ||
260 | // UI elements | ||
261 | internal static readonly string[] CommonControlAttributes = { "Hidden", "Disabled", "Sunken", "Indirect", "Integer", "RightToLeft", "RightAligned", "LeftScroll" }; | ||
262 | internal static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" }; | ||
263 | internal static readonly string[] HyperlinkControlAttributes = { "Transparent" }; | ||
264 | internal static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" }; | ||
265 | internal static readonly string[] ProgressControlAttributes = { "ProgressBlocks" }; | ||
266 | internal static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" }; | ||
267 | internal static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" }; | ||
268 | internal static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" }; | ||
269 | internal static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" }; | ||
270 | internal static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" }; | ||
271 | internal static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" }; | ||
272 | internal static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" }; | ||
273 | internal static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" }; | ||
274 | internal static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" }; | ||
275 | |||
276 | internal const int MsidbEmbeddedUI = 0x01; | ||
277 | internal const int MsidbEmbeddedHandlesBasic = 0x02; | ||
278 | |||
279 | internal const int INSTALLLOGMODE_FATALEXIT = 0x00001; | ||
280 | internal const int INSTALLLOGMODE_ERROR = 0x00002; | ||
281 | internal const int INSTALLLOGMODE_WARNING = 0x00004; | ||
282 | internal const int INSTALLLOGMODE_USER = 0x00008; | ||
283 | internal const int INSTALLLOGMODE_INFO = 0x00010; | ||
284 | internal const int INSTALLLOGMODE_FILESINUSE = 0x00020; | ||
285 | internal const int INSTALLLOGMODE_RESOLVESOURCE = 0x00040; | ||
286 | internal const int INSTALLLOGMODE_OUTOFDISKSPACE = 0x00080; | ||
287 | internal const int INSTALLLOGMODE_ACTIONSTART = 0x00100; | ||
288 | internal const int INSTALLLOGMODE_ACTIONDATA = 0x00200; | ||
289 | internal const int INSTALLLOGMODE_PROGRESS = 0x00400; | ||
290 | internal const int INSTALLLOGMODE_COMMONDATA = 0x00800; | ||
291 | internal const int INSTALLLOGMODE_INITIALIZE = 0x01000; | ||
292 | internal const int INSTALLLOGMODE_TERMINATE = 0x02000; | ||
293 | internal const int INSTALLLOGMODE_SHOWDIALOG = 0x04000; | ||
294 | internal const int INSTALLLOGMODE_RMFILESINUSE = 0x02000000; | ||
295 | internal const int INSTALLLOGMODE_INSTALLSTART = 0x04000000; | ||
296 | internal const int INSTALLLOGMODE_INSTALLEND = 0x08000000; | ||
297 | |||
298 | internal const int MSICONDITIONFALSE = 0; // The table is temporary. | ||
299 | internal const int MSICONDITIONTRUE = 1; // The table is persistent. | ||
300 | internal const int MSICONDITIONNONE = 2; // The table is unknown. | ||
301 | internal const int MSICONDITIONERROR = 3; // An invalid handle or invalid parameter was passed to the function. | ||
302 | |||
303 | internal const int MSIDBOPENREADONLY = 0; | ||
304 | internal const int MSIDBOPENTRANSACT = 1; | ||
305 | internal const int MSIDBOPENDIRECT = 2; | ||
306 | internal const int MSIDBOPENCREATE = 3; | ||
307 | internal const int MSIDBOPENCREATEDIRECT = 4; | ||
308 | internal const int MSIDBOPENPATCHFILE = 32; | ||
309 | |||
310 | internal const int MSIMODIFYSEEK = -1; // Refreshes the information in the supplied record without changing the position in the result set and without affecting subsequent fetch operations. The record may then be used for subsequent Update, Delete, and Refresh. All primary key columns of the table must be in the query and the record must have at least as many fields as the query. Seek cannot be used with multi-table queries. This mode cannot be used with a view containing joins. See also the remarks. | ||
311 | internal const int MSIMODIFYREFRESH = 0; // Refreshes the information in the record. Must first call MsiViewFetch with the same record. Fails for a deleted row. Works with read-write and read-only records. | ||
312 | internal const int MSIMODIFYINSERT = 1; // Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only database. This mode cannot be used with a view containing joins. | ||
313 | internal const int MSIMODIFYUPDATE = 2; // Updates an existing record. Nonprimary keys only. Must first call MsiViewFetch. Fails with a deleted record. Works only with read-write records. | ||
314 | internal const int MSIMODIFYASSIGN = 3; // Writes current data in the cursor to a table row. Updates record if the primary keys match an existing row and inserts if they do not match. Fails with a read-only database. This mode cannot be used with a view containing joins. | ||
315 | internal const int MSIMODIFYREPLACE = 4; // Updates or deletes and inserts a record into a table. Must first call MsiViewFetch with the same record. Updates record if the primary keys are unchanged. Deletes old row and inserts new if primary keys have changed. Fails with a read-only database. This mode cannot be used with a view containing joins. | ||
316 | internal const int MSIMODIFYMERGE = 5; // Inserts or validates a record in a table. Inserts if primary keys do not match any row and validates if there is a match. Fails if the record does not match the data in the table. Fails if there is a record with a duplicate key that is not identical. Works only with read-write records. This mode cannot be used with a view containing joins. | ||
317 | internal const int MSIMODIFYDELETE = 6; // Remove a row from the table. You must first call the MsiViewFetch function with the same record. Fails if the row has been deleted. Works only with read-write records. This mode cannot be used with a view containing joins. | ||
318 | internal const int MSIMODIFYINSERTTEMPORARY = 7; // Inserts a temporary record. The information is not persistent. Fails if a row with the same primary key exists. Works only with read-write records. This mode cannot be used with a view containing joins. | ||
319 | internal const int MSIMODIFYVALIDATE = 8; // Validates a record. Does not validate across joins. You must first call the MsiViewFetch function with the same record. Obtain validation errors with MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. | ||
320 | internal const int MSIMODIFYVALIDATENEW = 9; // Validate a new record. Does not validate across joins. Checks for duplicate keys. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. | ||
321 | internal const int MSIMODIFYVALIDATEFIELD = 10; // Validates fields of a fetched or new record. Can validate one or more fields of an incomplete record. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. | ||
322 | internal const int MSIMODIFYVALIDATEDELETE = 11; // Validates a record that will be deleted later. You must first call MsiViewFetch. Fails if another row refers to the primary keys of this row. Validation does not check for the existence of the primary keys of this row in properties or strings. Does not check if a column is a foreign key to multiple tables. Obtain validation errors by calling MsiViewGetError. Works with read-write and read-only records. This mode cannot be used with a view containing joins. | ||
323 | |||
324 | internal const uint VTI2 = 2; | ||
325 | internal const uint VTI4 = 3; | ||
326 | internal const uint VTLPWSTR = 30; | ||
327 | internal const uint VTFILETIME = 64; | ||
328 | |||
329 | internal const int MSICOLINFONAMES = 0; // return column names | ||
330 | internal const int MSICOLINFOTYPES = 1; // return column definitions, datatype code followed by width | ||
331 | |||
332 | /// <summary> | ||
333 | /// Protect the constructor. | ||
334 | /// </summary> | ||
335 | private MsiInterop() | ||
336 | { | ||
337 | } | ||
338 | |||
339 | /// <summary> | ||
340 | /// PInvoke of MsiCloseHandle. | ||
341 | /// </summary> | ||
342 | /// <param name="database">Handle to a database.</param> | ||
343 | /// <returns>Error code.</returns> | ||
344 | [DllImport("msi.dll", EntryPoint = "MsiCloseHandle", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
345 | internal static extern int MsiCloseHandle(uint database); | ||
346 | |||
347 | /// <summary> | ||
348 | /// PInvoke of MsiCreateRecord | ||
349 | /// </summary> | ||
350 | /// <param name="parameters">Count of columns in the record.</param> | ||
351 | /// <returns>Handle referencing the record.</returns> | ||
352 | [DllImport("msi.dll", EntryPoint = "MsiCreateRecord", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
353 | internal static extern uint MsiCreateRecord(int parameters); | ||
354 | |||
355 | /// <summary> | ||
356 | /// Creates summary information of an existing transform to include validation and error conditions. | ||
357 | /// </summary> | ||
358 | /// <param name="database">The handle to the database that contains the new database summary information.</param> | ||
359 | /// <param name="referenceDatabase">The handle to the database that contains the original summary information.</param> | ||
360 | /// <param name="transformFile">The name of the transform to which the summary information is added.</param> | ||
361 | /// <param name="errorConditions">The error conditions that should be suppressed when the transform is applied.</param> | ||
362 | /// <param name="validations">Specifies the properties to be validated to verify that the transform can be applied to the database.</param> | ||
363 | /// <returns>Error code.</returns> | ||
364 | [DllImport("msi.dll", EntryPoint = "MsiCreateTransformSummaryInfoW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
365 | internal static extern int MsiCreateTransformSummaryInfo(uint database, uint referenceDatabase, string transformFile, TransformErrorConditions errorConditions, TransformValidations validations); | ||
366 | |||
367 | /// <summary> | ||
368 | /// Applies a transform to a database. | ||
369 | /// </summary> | ||
370 | /// <param name="database">Handle to the database obtained from MsiOpenDatabase to transform.</param> | ||
371 | /// <param name="transformFile">Specifies the name of the transform file to apply.</param> | ||
372 | /// <param name="errorConditions">Error conditions that should be suppressed.</param> | ||
373 | /// <returns>Error code.</returns> | ||
374 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseApplyTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
375 | internal static extern int MsiDatabaseApplyTransform(uint database, string transformFile, TransformErrorConditions errorConditions); | ||
376 | |||
377 | /// <summary> | ||
378 | /// PInvoke of MsiDatabaseCommit. | ||
379 | /// </summary> | ||
380 | /// <param name="database">Handle to a databse.</param> | ||
381 | /// <returns>Error code.</returns> | ||
382 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseCommit", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
383 | internal static extern int MsiDatabaseCommit(uint database); | ||
384 | |||
385 | /// <summary> | ||
386 | /// PInvoke of MsiDatabaseExportW. | ||
387 | /// </summary> | ||
388 | /// <param name="database">Handle to a database.</param> | ||
389 | /// <param name="tableName">Table name.</param> | ||
390 | /// <param name="folderPath">Folder path.</param> | ||
391 | /// <param name="fileName">File name.</param> | ||
392 | /// <returns>Error code.</returns> | ||
393 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseExportW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
394 | internal static extern int MsiDatabaseExport(uint database, string tableName, string folderPath, string fileName); | ||
395 | |||
396 | /// <summary> | ||
397 | /// Generates a transform file of differences between two databases. | ||
398 | /// </summary> | ||
399 | /// <param name="database">Handle to the database obtained from MsiOpenDatabase that includes the changes.</param> | ||
400 | /// <param name="databaseReference">Handle to the database obtained from MsiOpenDatabase that does not include the changes.</param> | ||
401 | /// <param name="transformFile">A null-terminated string that specifies the name of the transform file being generated. | ||
402 | /// This parameter can be null. If szTransformFile is null, you can use MsiDatabaseGenerateTransform to test whether two | ||
403 | /// databases are identical without creating a transform. If the databases are identical, the function returns ERROR_NO_DATA. | ||
404 | /// If the databases are different the function returns NOERROR.</param> | ||
405 | /// <param name="reserved1">This is a reserved argument and must be set to 0.</param> | ||
406 | /// <param name="reserved2">This is a reserved argument and must be set to 0.</param> | ||
407 | /// <returns>Error code.</returns> | ||
408 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseGenerateTransformW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
409 | internal static extern int MsiDatabaseGenerateTransform(uint database, uint databaseReference, string transformFile, int reserved1, int reserved2); | ||
410 | |||
411 | /// <summary> | ||
412 | /// PInvoke of MsiDatabaseImportW. | ||
413 | /// </summary> | ||
414 | /// <param name="database">Handle to a database.</param> | ||
415 | /// <param name="folderPath">Folder path.</param> | ||
416 | /// <param name="fileName">File name.</param> | ||
417 | /// <returns>Error code.</returns> | ||
418 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseImportW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
419 | internal static extern int MsiDatabaseImport(uint database, string folderPath, string fileName); | ||
420 | |||
421 | /// <summary> | ||
422 | /// PInvoke of MsiDatabaseMergeW. | ||
423 | /// </summary> | ||
424 | /// <param name="database">The handle to the database obtained from MsiOpenDatabase.</param> | ||
425 | /// <param name="databaseMerge">The handle to the database obtained from MsiOpenDatabase to merge into the base database.</param> | ||
426 | /// <param name="tableName">The name of the table to receive merge conflict information.</param> | ||
427 | /// <returns>Error code.</returns> | ||
428 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseMergeW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
429 | internal static extern int MsiDatabaseMerge(uint database, uint databaseMerge, string tableName); | ||
430 | |||
431 | /// <summary> | ||
432 | /// PInvoke of MsiDatabaseOpenViewW. | ||
433 | /// </summary> | ||
434 | /// <param name="database">Handle to a database.</param> | ||
435 | /// <param name="query">SQL query.</param> | ||
436 | /// <param name="view">View handle.</param> | ||
437 | /// <returns>Error code.</returns> | ||
438 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseOpenViewW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
439 | internal static extern int MsiDatabaseOpenView(uint database, string query, out uint view); | ||
440 | |||
441 | /// <summary> | ||
442 | /// PInvoke of MsiGetFileHashW. | ||
443 | /// </summary> | ||
444 | /// <param name="filePath">File path.</param> | ||
445 | /// <param name="options">Hash options (must be 0).</param> | ||
446 | /// <param name="hash">Buffer to recieve hash.</param> | ||
447 | /// <returns>Error code.</returns> | ||
448 | [DllImport("msi.dll", EntryPoint = "MsiGetFileHashW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
449 | internal static extern int MsiGetFileHash(string filePath, uint options, MSIFILEHASHINFO hash); | ||
450 | |||
451 | /// <summary> | ||
452 | /// PInvoke of MsiGetFileVersionW. | ||
453 | /// </summary> | ||
454 | /// <param name="filePath">File path.</param> | ||
455 | /// <param name="versionBuf">Buffer to receive version info.</param> | ||
456 | /// <param name="versionBufSize">Size of version buffer.</param> | ||
457 | /// <param name="langBuf">Buffer to recieve lang info.</param> | ||
458 | /// <param name="langBufSize">Size of lang buffer.</param> | ||
459 | /// <returns>Error code.</returns> | ||
460 | [DllImport("msi.dll", EntryPoint = "MsiGetFileVersionW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
461 | internal static extern int MsiGetFileVersion(string filePath, StringBuilder versionBuf, ref int versionBufSize, StringBuilder langBuf, ref int langBufSize); | ||
462 | |||
463 | /// <summary> | ||
464 | /// PInvoke of MsiGetLastErrorRecord. | ||
465 | /// </summary> | ||
466 | /// <returns>Handle to error record if one exists.</returns> | ||
467 | [DllImport("msi.dll", EntryPoint = "MsiGetLastErrorRecord", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
468 | internal static extern uint MsiGetLastErrorRecord(); | ||
469 | |||
470 | /// <summary> | ||
471 | /// PInvoke of MsiDatabaseGetPrimaryKeysW. | ||
472 | /// </summary> | ||
473 | /// <param name="database">Handle to a database.</param> | ||
474 | /// <param name="tableName">Table name.</param> | ||
475 | /// <param name="record">Handle to receive resulting record.</param> | ||
476 | /// <returns>Error code.</returns> | ||
477 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseGetPrimaryKeysW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
478 | internal static extern int MsiDatabaseGetPrimaryKeys(uint database, string tableName, out uint record); | ||
479 | |||
480 | /// <summary> | ||
481 | /// PInvoke of MsiDoActionW. | ||
482 | /// </summary> | ||
483 | /// <param name="product">Handle to the installation provided to a DLL custom action or | ||
484 | /// obtained through MsiOpenPackage, MsiOpenPackageEx, or MsiOpenProduct.</param> | ||
485 | /// <param name="action">Specifies the action to execute.</param> | ||
486 | /// <returns>Error code.</returns> | ||
487 | [DllImport("msi.dll", EntryPoint = "MsiDoActionW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
488 | internal static extern int MsiDoAction(uint product, string action); | ||
489 | |||
490 | /// <summary> | ||
491 | /// PInvoke of MsiGetSummaryInformationW. Can use either database handle or database path as input. | ||
492 | /// </summary> | ||
493 | /// <param name="database">Handle to a database.</param> | ||
494 | /// <param name="databasePath">Path to a database.</param> | ||
495 | /// <param name="updateCount">Max number of updated values.</param> | ||
496 | /// <param name="summaryInfo">Handle to summary information.</param> | ||
497 | /// <returns>Error code.</returns> | ||
498 | [DllImport("msi.dll", EntryPoint = "MsiGetSummaryInformationW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
499 | internal static extern int MsiGetSummaryInformation(uint database, string databasePath, uint updateCount, ref uint summaryInfo); | ||
500 | |||
501 | /// <summary> | ||
502 | /// PInvoke of MsiDatabaseIsTablePersitentW. | ||
503 | /// </summary> | ||
504 | /// <param name="database">Handle to a database.</param> | ||
505 | /// <param name="tableName">Table name.</param> | ||
506 | /// <returns>MSICONDITION</returns> | ||
507 | [DllImport("msi.dll", EntryPoint = "MsiDatabaseIsTablePersistentW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
508 | internal static extern int MsiDatabaseIsTablePersistent(uint database, string tableName); | ||
509 | |||
510 | /// <summary> | ||
511 | /// PInvoke of MsiOpenDatabaseW. | ||
512 | /// </summary> | ||
513 | /// <param name="databasePath">Path to database.</param> | ||
514 | /// <param name="persist">Persist mode.</param> | ||
515 | /// <param name="database">Handle to database.</param> | ||
516 | /// <returns>Error code.</returns> | ||
517 | [DllImport("msi.dll", EntryPoint = "MsiOpenDatabaseW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
518 | internal static extern int MsiOpenDatabase(string databasePath, IntPtr persist, out uint database); | ||
519 | |||
520 | /// <summary> | ||
521 | /// PInvoke of MsiOpenPackageW. | ||
522 | /// </summary> | ||
523 | /// <param name="packagePath">The path to the package.</param> | ||
524 | /// <param name="product">A pointer to a variable that receives the product handle.</param> | ||
525 | /// <returns>Error code.</returns> | ||
526 | [DllImport("msi.dll", EntryPoint = "MsiOpenPackageW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
527 | internal static extern int MsiOpenPackage(string packagePath, out uint product); | ||
528 | |||
529 | /// <summary> | ||
530 | /// PInvoke of MsiRecordIsNull. | ||
531 | /// </summary> | ||
532 | /// <param name="record">MSI Record handle.</param> | ||
533 | /// <param name="field">Index of field to check for null value.</param> | ||
534 | /// <returns>true if the field is null, false if not, and an error code for any error.</returns> | ||
535 | [DllImport("msi.dll", EntryPoint = "MsiRecordIsNull", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
536 | internal static extern int MsiRecordIsNull(uint record, int field); | ||
537 | |||
538 | /// <summary> | ||
539 | /// PInvoke of MsiRecordGetInteger. | ||
540 | /// </summary> | ||
541 | /// <param name="record">MSI Record handle.</param> | ||
542 | /// <param name="field">Index of field to retrieve integer from.</param> | ||
543 | /// <returns>Integer value.</returns> | ||
544 | [DllImport("msi.dll", EntryPoint = "MsiRecordGetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
545 | internal static extern int MsiRecordGetInteger(uint record, int field); | ||
546 | |||
547 | /// <summary> | ||
548 | /// PInvoke of MsiRectordSetInteger. | ||
549 | /// </summary> | ||
550 | /// <param name="record">MSI Record handle.</param> | ||
551 | /// <param name="field">Index of field to set integer value in.</param> | ||
552 | /// <param name="value">Value to set field to.</param> | ||
553 | /// <returns>Error code.</returns> | ||
554 | [DllImport("msi.dll", EntryPoint = "MsiRecordSetInteger", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
555 | internal static extern int MsiRecordSetInteger(uint record, int field, int value); | ||
556 | |||
557 | /// <summary> | ||
558 | /// PInvoke of MsiRecordGetStringW. | ||
559 | /// </summary> | ||
560 | /// <param name="record">MSI Record handle.</param> | ||
561 | /// <param name="field">Index of field to get string value from.</param> | ||
562 | /// <param name="valueBuf">Buffer to recieve value.</param> | ||
563 | /// <param name="valueBufSize">Size of buffer.</param> | ||
564 | /// <returns>Error code.</returns> | ||
565 | [DllImport("msi.dll", EntryPoint = "MsiRecordGetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
566 | internal static extern int MsiRecordGetString(uint record, int field, StringBuilder valueBuf, ref int valueBufSize); | ||
567 | |||
568 | /// <summary> | ||
569 | /// PInvoke of MsiRecordSetStringW. | ||
570 | /// </summary> | ||
571 | /// <param name="record">MSI Record handle.</param> | ||
572 | /// <param name="field">Index of field to set string value in.</param> | ||
573 | /// <param name="value">String value.</param> | ||
574 | /// <returns>Error code.</returns> | ||
575 | [DllImport("msi.dll", EntryPoint = "MsiRecordSetStringW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
576 | internal static extern int MsiRecordSetString(uint record, int field, string value); | ||
577 | |||
578 | /// <summary> | ||
579 | /// PInvoke of MsiRecordSetStreamW. | ||
580 | /// </summary> | ||
581 | /// <param name="record">MSI Record handle.</param> | ||
582 | /// <param name="field">Index of field to set stream value in.</param> | ||
583 | /// <param name="filePath">Path to file to set stream value to.</param> | ||
584 | /// <returns>Error code.</returns> | ||
585 | [DllImport("msi.dll", EntryPoint = "MsiRecordSetStreamW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
586 | internal static extern int MsiRecordSetStream(uint record, int field, string filePath); | ||
587 | |||
588 | /// <summary> | ||
589 | /// PInvoke of MsiRecordReadStreamW. | ||
590 | /// </summary> | ||
591 | /// <param name="record">MSI Record handle.</param> | ||
592 | /// <param name="field">Index of field to read stream from.</param> | ||
593 | /// <param name="dataBuf">Data buffer to recieve stream value.</param> | ||
594 | /// <param name="dataBufSize">Size of data buffer.</param> | ||
595 | /// <returns>Error code.</returns> | ||
596 | [DllImport("msi.dll", EntryPoint = "MsiRecordReadStream", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
597 | internal static extern int MsiRecordReadStream(uint record, int field, byte[] dataBuf, ref int dataBufSize); | ||
598 | |||
599 | /// <summary> | ||
600 | /// PInvoke of MsiRecordGetFieldCount. | ||
601 | /// </summary> | ||
602 | /// <param name="record">MSI Record handle.</param> | ||
603 | /// <returns>Count of fields in the record.</returns> | ||
604 | [DllImport("msi.dll", EntryPoint = "MsiRecordGetFieldCount", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
605 | internal static extern int MsiRecordGetFieldCount(uint record); | ||
606 | |||
607 | /// <summary> | ||
608 | /// PInvoke of MsiSetExternalUIW. | ||
609 | /// </summary> | ||
610 | /// <param name="installUIHandler">Specifies a callback function that conforms to the INSTALLUI_HANDLER specification.</param> | ||
611 | /// <param name="installLogMode">Specifies which messages to handle using the external message handler. If the external | ||
612 | /// handler returns a non-zero result, then that message will not be sent to the UI, instead the message will be logged | ||
613 | /// if logging has been enabled.</param> | ||
614 | /// <param name="context">Pointer to an application context that is passed to the callback function. | ||
615 | /// This parameter can be used for error checking.</param> | ||
616 | /// <returns>The return value is the previously set external handler, or zero (0) if there was no previously set handler.</returns> | ||
617 | [DllImport("msi.dll", EntryPoint = "MsiSetExternalUIW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
618 | internal static extern InstallUIHandler MsiSetExternalUI(InstallUIHandler installUIHandler, int installLogMode, IntPtr context); | ||
619 | |||
620 | /// <summary> | ||
621 | /// PInvoke of MsiSetInternalUI. | ||
622 | /// </summary> | ||
623 | /// <param name="uiLevel">Specifies the level of complexity of the user interface.</param> | ||
624 | /// <param name="hwnd">Pointer to a window. This window becomes the owner of any user interface created. | ||
625 | /// A pointer to the previous owner of the user interface is returned. | ||
626 | /// If this parameter is null, the owner of the user interface does not change.</param> | ||
627 | /// <returns>The previous user interface level is returned. If an invalid dwUILevel is passed, then INSTALLUILEVEL_NOCHANGE is returned.</returns> | ||
628 | [DllImport("msi.dll", EntryPoint = "MsiSetInternalUI", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
629 | internal static extern int MsiSetInternalUI(int uiLevel, ref IntPtr hwnd); | ||
630 | |||
631 | /// <summary> | ||
632 | /// PInvoke of MsiSummaryInfoGetPropertyW. | ||
633 | /// </summary> | ||
634 | /// <param name="summaryInfo">Handle to summary info.</param> | ||
635 | /// <param name="property">Property to get value from.</param> | ||
636 | /// <param name="dataType">Data type of property.</param> | ||
637 | /// <param name="integerValue">Integer to receive integer value.</param> | ||
638 | /// <param name="fileTimeValue">File time to receive file time value.</param> | ||
639 | /// <param name="stringValueBuf">String buffer to receive string value.</param> | ||
640 | /// <param name="stringValueBufSize">Size of string buffer.</param> | ||
641 | /// <returns>Error code.</returns> | ||
642 | [DllImport("msi.dll", EntryPoint = "MsiSummaryInfoGetPropertyW", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
643 | internal static extern int MsiSummaryInfoGetProperty(uint summaryInfo, int property, out uint dataType, out int integerValue, ref FILETIME fileTimeValue, StringBuilder stringValueBuf, ref int stringValueBufSize); | ||
644 | |||
645 | /// <summary> | ||
646 | /// PInvoke of MsiViewGetColumnInfo. | ||
647 | /// </summary> | ||
648 | /// <param name="view">Handle to view.</param> | ||
649 | /// <param name="columnInfo">Column info.</param> | ||
650 | /// <param name="record">Handle for returned record.</param> | ||
651 | /// <returns>Error code.</returns> | ||
652 | [DllImport("msi.dll", EntryPoint = "MsiViewGetColumnInfo", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
653 | internal static extern int MsiViewGetColumnInfo(uint view, int columnInfo, out uint record); | ||
654 | |||
655 | /// <summary> | ||
656 | /// PInvoke of MsiViewExecute. | ||
657 | /// </summary> | ||
658 | /// <param name="view">Handle of view to execute.</param> | ||
659 | /// <param name="record">Handle to a record that supplies the parameters for the view.</param> | ||
660 | /// <returns>Error code.</returns> | ||
661 | [DllImport("msi.dll", EntryPoint = "MsiViewExecute", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
662 | internal static extern int MsiViewExecute(uint view, uint record); | ||
663 | |||
664 | /// <summary> | ||
665 | /// PInvoke of MsiViewFetch. | ||
666 | /// </summary> | ||
667 | /// <param name="view">Handle of view to fetch a row from.</param> | ||
668 | /// <param name="record">Handle to receive record info.</param> | ||
669 | /// <returns>Error code.</returns> | ||
670 | [DllImport("msi.dll", EntryPoint = "MsiViewFetch", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
671 | internal static extern int MsiViewFetch(uint view, out uint record); | ||
672 | |||
673 | /// <summary> | ||
674 | /// PInvoke of MsiViewModify. | ||
675 | /// </summary> | ||
676 | /// <param name="view">Handle of view to modify.</param> | ||
677 | /// <param name="modifyMode">Modify mode.</param> | ||
678 | /// <param name="record">Handle of record.</param> | ||
679 | /// <returns>Error code.</returns> | ||
680 | [DllImport("msi.dll", EntryPoint = "MsiViewModify", CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
681 | internal static extern int MsiViewModify(uint view, int modifyMode, uint record); | ||
682 | |||
683 | /// <summary> | ||
684 | /// contains the file hash information returned by MsiGetFileHash and used in the MsiFileHash table. | ||
685 | /// </summary> | ||
686 | [StructLayout(LayoutKind.Explicit)] | ||
687 | internal class MSIFILEHASHINFO | ||
688 | { | ||
689 | [FieldOffset(0)] internal uint FileHashInfoSize; | ||
690 | [FieldOffset(4)] internal int Data0; | ||
691 | [FieldOffset(8)] internal int Data1; | ||
692 | [FieldOffset(12)]internal int Data2; | ||
693 | [FieldOffset(16)]internal int Data3; | ||
694 | } | ||
695 | } | ||
696 | } | ||
697 | #endif \ No newline at end of file | ||
diff --git a/src/WixToolset.Core/Msi/MsiException.cs b/src/WixToolset.Core/Msi/MsiException.cs deleted file mode 100644 index b33bf27a..00000000 --- a/src/WixToolset.Core/Msi/MsiException.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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using WixToolset.Core.Native; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Exception that wraps MsiGetLastError(). | ||
11 | /// </summary> | ||
12 | [Serializable] | ||
13 | public class MsiException : Win32Exception | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Instantiate a new MsiException with a given error. | ||
17 | /// </summary> | ||
18 | /// <param name="error">The error code from the MsiXxx() function call.</param> | ||
19 | public MsiException(int error) : base(error) | ||
20 | { | ||
21 | uint handle = MsiInterop.MsiGetLastErrorRecord(); | ||
22 | if (0 != handle) | ||
23 | { | ||
24 | using (Record record = new Record(handle)) | ||
25 | { | ||
26 | this.MsiError = record.GetInteger(1); | ||
27 | |||
28 | int errorInfoCount = record.GetFieldCount() - 1; | ||
29 | this.ErrorInfo = new string[errorInfoCount]; | ||
30 | for (int i = 0; i < errorInfoCount; ++i) | ||
31 | { | ||
32 | this.ErrorInfo[i] = record.GetString(i + 2); | ||
33 | } | ||
34 | } | ||
35 | } | ||
36 | else | ||
37 | { | ||
38 | this.MsiError = 0; | ||
39 | this.ErrorInfo = new string[0]; | ||
40 | } | ||
41 | |||
42 | this.Error = error; | ||
43 | } | ||
44 | |||
45 | /// <summary> | ||
46 | /// Gets the error number. | ||
47 | /// </summary> | ||
48 | public int Error { get; private set; } | ||
49 | |||
50 | /// <summary> | ||
51 | /// Gets the internal MSI error number. | ||
52 | /// </summary> | ||
53 | public int MsiError { get; private set; } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Gets any additional the error information. | ||
57 | /// </summary> | ||
58 | public string[] ErrorInfo { get; private set; } | ||
59 | |||
60 | /// <summary> | ||
61 | /// Overrides Message property to return useful error message. | ||
62 | /// </summary> | ||
63 | public override string Message | ||
64 | { | ||
65 | get | ||
66 | { | ||
67 | if (0 == this.MsiError) | ||
68 | { | ||
69 | return base.Message; | ||
70 | } | ||
71 | else | ||
72 | { | ||
73 | return String.Format("Internal MSI failure. Win32 error: {0}, MSI error: {1}, detail: {2}", this.Error, this.MsiError, String.Join(", ", this.ErrorInfo)); | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | } | ||
diff --git a/src/WixToolset.Core/Msi/MsiHandle.cs b/src/WixToolset.Core/Msi/MsiHandle.cs deleted file mode 100644 index 6d2dc984..00000000 --- a/src/WixToolset.Core/Msi/MsiHandle.cs +++ /dev/null | |||
@@ -1,116 +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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Diagnostics; | ||
8 | using System.Threading; | ||
9 | using WixToolset.Core.Native; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Wrapper class for MSI handle. | ||
13 | /// </summary> | ||
14 | public class MsiHandle : IDisposable | ||
15 | { | ||
16 | private bool disposed; | ||
17 | private uint handle; | ||
18 | private int owningThread; | ||
19 | #if DEBUG | ||
20 | private string creationStack; | ||
21 | #endif | ||
22 | |||
23 | /// <summary> | ||
24 | /// MSI handle destructor. | ||
25 | /// </summary> | ||
26 | ~MsiHandle() | ||
27 | { | ||
28 | this.Dispose(false); | ||
29 | } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Gets or sets the MSI handle. | ||
33 | /// </summary> | ||
34 | /// <value>The MSI handle.</value> | ||
35 | internal uint Handle | ||
36 | { | ||
37 | get | ||
38 | { | ||
39 | if (this.disposed) | ||
40 | { | ||
41 | throw new ObjectDisposedException("MsiHandle"); | ||
42 | } | ||
43 | |||
44 | return this.handle; | ||
45 | } | ||
46 | |||
47 | set | ||
48 | { | ||
49 | if (this.disposed) | ||
50 | { | ||
51 | throw new ObjectDisposedException("MsiHandle"); | ||
52 | } | ||
53 | |||
54 | this.handle = value; | ||
55 | this.owningThread = Thread.CurrentThread.ManagedThreadId; | ||
56 | #if DEBUG | ||
57 | this.creationStack = Environment.StackTrace; | ||
58 | #endif | ||
59 | } | ||
60 | } | ||
61 | |||
62 | /// <summary> | ||
63 | /// Close the MSI handle. | ||
64 | /// </summary> | ||
65 | public void Close() | ||
66 | { | ||
67 | this.Dispose(); | ||
68 | } | ||
69 | |||
70 | /// <summary> | ||
71 | /// Disposes the managed and unmanaged objects in this object. | ||
72 | /// </summary> | ||
73 | public void Dispose() | ||
74 | { | ||
75 | this.Dispose(true); | ||
76 | GC.SuppressFinalize(this); | ||
77 | } | ||
78 | |||
79 | /// <summary> | ||
80 | /// Disposes the managed and unmanaged objects in this object. | ||
81 | /// </summary> | ||
82 | /// <param name="disposing">true to dispose the managed objects.</param> | ||
83 | protected virtual void Dispose(bool disposing) | ||
84 | { | ||
85 | if (!this.disposed) | ||
86 | { | ||
87 | if (0 != this.handle) | ||
88 | { | ||
89 | if (Thread.CurrentThread.ManagedThreadId == this.owningThread) | ||
90 | { | ||
91 | int error = MsiInterop.MsiCloseHandle(this.handle); | ||
92 | if (0 != error) | ||
93 | { | ||
94 | throw new Win32Exception(error); | ||
95 | } | ||
96 | this.handle = 0; | ||
97 | } | ||
98 | else | ||
99 | { | ||
100 | // Don't try to close the handle on a different thread than it was opened. | ||
101 | // This will occasionally cause MSI to AV. | ||
102 | string message = String.Format("Leaked msi handle {0} created on thread {1} by type {2}. This handle cannot be closed on thread {3}", | ||
103 | this.handle, this.owningThread, this.GetType(), Thread.CurrentThread.ManagedThreadId); | ||
104 | #if DEBUG | ||
105 | throw new InvalidOperationException(String.Format("{0}. Created {1}", message, this.creationStack)); | ||
106 | #else | ||
107 | Debug.WriteLine(message); | ||
108 | #endif | ||
109 | } | ||
110 | } | ||
111 | |||
112 | this.disposed = true; | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | } | ||
diff --git a/src/WixToolset.Core/Msi/Record.cs b/src/WixToolset.Core/Msi/Record.cs deleted file mode 100644 index 438aa3b0..00000000 --- a/src/WixToolset.Core/Msi/Record.cs +++ /dev/null | |||
@@ -1,182 +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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Text; | ||
8 | using WixToolset.Core.Native; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Wrapper class around msi.dll interop for a record. | ||
12 | /// </summary> | ||
13 | public sealed class Record : MsiHandle | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Creates a record with the specified number of fields. | ||
17 | /// </summary> | ||
18 | /// <param name="fieldCount">Number of fields in record.</param> | ||
19 | public Record(int fieldCount) | ||
20 | { | ||
21 | this.Handle = MsiInterop.MsiCreateRecord(fieldCount); | ||
22 | if (0 == this.Handle) | ||
23 | { | ||
24 | throw new OutOfMemoryException(); | ||
25 | } | ||
26 | } | ||
27 | |||
28 | /// <summary> | ||
29 | /// Creates a record from a handle. | ||
30 | /// </summary> | ||
31 | /// <param name="handle">Handle to create record from.</param> | ||
32 | internal Record(uint handle) | ||
33 | { | ||
34 | this.Handle = handle; | ||
35 | } | ||
36 | |||
37 | /// <summary> | ||
38 | /// Gets a string value at specified location. | ||
39 | /// </summary> | ||
40 | /// <param name="field">Index into record to get string.</param> | ||
41 | public string this[int field] | ||
42 | { | ||
43 | get { return this.GetString(field); } | ||
44 | set { this.SetString(field, (string)value); } | ||
45 | } | ||
46 | |||
47 | /// <summary> | ||
48 | /// Determines if the value is null at the specified location. | ||
49 | /// </summary> | ||
50 | /// <param name="field">Index into record of the field to query.</param> | ||
51 | /// <returns>true if the value is null, false otherwise.</returns> | ||
52 | public bool IsNull(int field) | ||
53 | { | ||
54 | int error = MsiInterop.MsiRecordIsNull(this.Handle, field); | ||
55 | |||
56 | switch (error) | ||
57 | { | ||
58 | case 0: | ||
59 | return false; | ||
60 | case 1: | ||
61 | return true; | ||
62 | default: | ||
63 | throw new Win32Exception(error); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | /// <summary> | ||
68 | /// Gets integer value at specified location. | ||
69 | /// </summary> | ||
70 | /// <param name="field">Index into record to get integer</param> | ||
71 | /// <returns>Integer value</returns> | ||
72 | public int GetInteger(int field) | ||
73 | { | ||
74 | return MsiInterop.MsiRecordGetInteger(this.Handle, field); | ||
75 | } | ||
76 | |||
77 | /// <summary> | ||
78 | /// Sets integer value at specified location. | ||
79 | /// </summary> | ||
80 | /// <param name="field">Index into record to set integer.</param> | ||
81 | /// <param name="value">Value to set into record.</param> | ||
82 | public void SetInteger(int field, int value) | ||
83 | { | ||
84 | int error = MsiInterop.MsiRecordSetInteger(this.Handle, field, value); | ||
85 | if (0 != error) | ||
86 | { | ||
87 | throw new Win32Exception(error); | ||
88 | } | ||
89 | } | ||
90 | |||
91 | /// <summary> | ||
92 | /// Gets string value at specified location. | ||
93 | /// </summary> | ||
94 | /// <param name="field">Index into record to get string.</param> | ||
95 | /// <returns>String value</returns> | ||
96 | public string GetString(int field) | ||
97 | { | ||
98 | int bufferSize = 255; | ||
99 | StringBuilder buffer = new StringBuilder(bufferSize); | ||
100 | int error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize); | ||
101 | if (234 == error) | ||
102 | { | ||
103 | buffer.EnsureCapacity(++bufferSize); | ||
104 | error = MsiInterop.MsiRecordGetString(this.Handle, field, buffer, ref bufferSize); | ||
105 | } | ||
106 | |||
107 | if (0 != error) | ||
108 | { | ||
109 | throw new Win32Exception(error); | ||
110 | } | ||
111 | |||
112 | return (0 < buffer.Length ? buffer.ToString() : null); | ||
113 | } | ||
114 | |||
115 | /// <summary> | ||
116 | /// Set string value at specified location | ||
117 | /// </summary> | ||
118 | /// <param name="field">Index into record to set string.</param> | ||
119 | /// <param name="value">Value to set into record</param> | ||
120 | public void SetString(int field, string value) | ||
121 | { | ||
122 | int error = MsiInterop.MsiRecordSetString(this.Handle, field, value); | ||
123 | if (0 != error) | ||
124 | { | ||
125 | throw new Win32Exception(error); | ||
126 | } | ||
127 | } | ||
128 | |||
129 | /// <summary> | ||
130 | /// Get stream at specified location. | ||
131 | /// </summary> | ||
132 | /// <param name="field">Index into record to get stream.</param> | ||
133 | /// <param name="buffer">buffer to receive bytes from stream.</param> | ||
134 | /// <param name="requestedBufferSize">Buffer size to read.</param> | ||
135 | /// <returns>Stream read into string.</returns> | ||
136 | public int GetStream(int field, byte[] buffer, int requestedBufferSize) | ||
137 | { | ||
138 | int bufferSize = 255; | ||
139 | if (requestedBufferSize > 0) | ||
140 | { | ||
141 | bufferSize = requestedBufferSize; | ||
142 | } | ||
143 | |||
144 | int error = MsiInterop.MsiRecordReadStream(this.Handle, field, buffer, ref bufferSize); | ||
145 | if (0 != error) | ||
146 | { | ||
147 | throw new Win32Exception(error); | ||
148 | } | ||
149 | |||
150 | return bufferSize; | ||
151 | } | ||
152 | |||
153 | /// <summary> | ||
154 | /// Sets a stream at a specified location. | ||
155 | /// </summary> | ||
156 | /// <param name="field">Index into record to set stream.</param> | ||
157 | /// <param name="path">Path to file to read into stream.</param> | ||
158 | public void SetStream(int field, string path) | ||
159 | { | ||
160 | int error = MsiInterop.MsiRecordSetStream(this.Handle, field, path); | ||
161 | if (0 != error) | ||
162 | { | ||
163 | throw new Win32Exception(error); | ||
164 | } | ||
165 | } | ||
166 | |||
167 | /// <summary> | ||
168 | /// Gets the number of fields in record. | ||
169 | /// </summary> | ||
170 | /// <returns>Count of fields in record.</returns> | ||
171 | public int GetFieldCount() | ||
172 | { | ||
173 | int size = MsiInterop.MsiRecordGetFieldCount(this.Handle); | ||
174 | if (0 > size) | ||
175 | { | ||
176 | throw new Win32Exception(); | ||
177 | } | ||
178 | |||
179 | return size; | ||
180 | } | ||
181 | } | ||
182 | } | ||
diff --git a/src/WixToolset.Core/Msi/Session.cs b/src/WixToolset.Core/Msi/Session.cs deleted file mode 100644 index d3a19711..00000000 --- a/src/WixToolset.Core/Msi/Session.cs +++ /dev/null | |||
@@ -1,45 +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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Globalization; | ||
8 | using WixToolset.Core.Native; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Controls the installation process. | ||
12 | /// </summary> | ||
13 | internal sealed class Session : MsiHandle | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Instantiate a new Session. | ||
17 | /// </summary> | ||
18 | /// <param name="database">The database to open.</param> | ||
19 | public Session(Database database) | ||
20 | { | ||
21 | string packagePath = String.Format(CultureInfo.InvariantCulture, "#{0}", (uint)database.Handle); | ||
22 | |||
23 | uint handle = 0; | ||
24 | int error = MsiInterop.MsiOpenPackage(packagePath, out handle); | ||
25 | if (0 != error) | ||
26 | { | ||
27 | throw new MsiException(error); | ||
28 | } | ||
29 | this.Handle = handle; | ||
30 | } | ||
31 | |||
32 | /// <summary> | ||
33 | /// Executes a built-in action, custom action, or user-interface wizard action. | ||
34 | /// </summary> | ||
35 | /// <param name="action">Specifies the action to execute.</param> | ||
36 | public void DoAction(string action) | ||
37 | { | ||
38 | int error = MsiInterop.MsiDoAction(this.Handle, action); | ||
39 | if (0 != error) | ||
40 | { | ||
41 | throw new MsiException(error); | ||
42 | } | ||
43 | } | ||
44 | } | ||
45 | } | ||
diff --git a/src/WixToolset.Core/Msi/SummaryInformation.cs b/src/WixToolset.Core/Msi/SummaryInformation.cs deleted file mode 100644 index 39949db6..00000000 --- a/src/WixToolset.Core/Msi/SummaryInformation.cs +++ /dev/null | |||
@@ -1,323 +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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | using System.Globalization; | ||
9 | using System.Text; | ||
10 | using System.Runtime.InteropServices; | ||
11 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; | ||
12 | using WixToolset.Core.Native; | ||
13 | |||
14 | /// <summary> | ||
15 | /// Summary information for the MSI files. | ||
16 | /// </summary> | ||
17 | internal sealed class SummaryInformation : MsiHandle | ||
18 | { | ||
19 | /// <summary> | ||
20 | /// Summary information properties for transforms. | ||
21 | /// </summary> | ||
22 | public enum Transform | ||
23 | { | ||
24 | /// <summary>PID_CODEPAGE = code page for the summary information stream</summary> | ||
25 | CodePage = 1, | ||
26 | |||
27 | /// <summary>PID_TITLE = typically just "Transform"</summary> | ||
28 | Title = 2, | ||
29 | |||
30 | /// <summary>PID_SUBJECT = original subject of target</summary> | ||
31 | TargetSubject = 3, | ||
32 | |||
33 | /// <summary>PID_AUTHOR = original manufacturer of target</summary> | ||
34 | TargetManufacturer = 4, | ||
35 | |||
36 | /// <summary>PID_KEYWORDS = keywords for the transform, typically including at least "Installer"</summary> | ||
37 | Keywords = 5, | ||
38 | |||
39 | /// <summary>PID_COMMENTS = describes what this package does</summary> | ||
40 | Comments = 6, | ||
41 | |||
42 | /// <summary>PID_TEMPLATE = target platform;language</summary> | ||
43 | TargetPlatformAndLanguage = 7, | ||
44 | |||
45 | /// <summary>PID_LASTAUTHOR = updated platform;language</summary> | ||
46 | UpdatedPlatformAndLanguage = 8, | ||
47 | |||
48 | /// <summary>PID_REVNUMBER = {productcode}version;{newproductcode}newversion;upgradecode</summary> | ||
49 | ProductCodes = 9, | ||
50 | |||
51 | /// <summary>PID_LASTPRINTED should be null for transforms</summary> | ||
52 | Reserved11 = 11, | ||
53 | |||
54 | ///.<summary>PID_CREATE_DTM = the timestamp when the transform was created</summary> | ||
55 | CreationTime = 12, | ||
56 | |||
57 | /// <summary>PID_PAGECOUNT = minimum installer version</summary> | ||
58 | InstallerRequirement = 14, | ||
59 | |||
60 | /// <summary>PID_CHARCOUNT = validation and error flags</summary> | ||
61 | ValidationFlags = 16, | ||
62 | |||
63 | /// <summary>PID_APPNAME = the application that created the transform</summary> | ||
64 | CreatingApplication = 18, | ||
65 | |||
66 | /// <summary>PID_SECURITY = whether read-only is enforced; should always be 4 for transforms</summary> | ||
67 | Security = 19, | ||
68 | } | ||
69 | |||
70 | /// <summary> | ||
71 | /// Summary information properties for patches. | ||
72 | /// </summary> | ||
73 | public enum Patch | ||
74 | { | ||
75 | /// <summary>PID_CODEPAGE = code page of the summary information stream</summary> | ||
76 | CodePage = 1, | ||
77 | |||
78 | /// <summary>PID_TITLE = a brief description of the package type</summary> | ||
79 | Title = 2, | ||
80 | |||
81 | /// <summary>PID_SUBJECT = package name</summary> | ||
82 | PackageName = 3, | ||
83 | |||
84 | /// <summary>PID_AUTHOR = manufacturer of the patch package</summary> | ||
85 | Manufacturer = 4, | ||
86 | |||
87 | /// <summary>PID_KEYWORDS = alternate sources for the patch package</summary> | ||
88 | Sources = 5, | ||
89 | |||
90 | /// <summary>PID_COMMENTS = general purpose of the patch package</summary> | ||
91 | Comments = 6, | ||
92 | |||
93 | /// <summary>PID_TEMPLATE = semicolon delimited list of ProductCodes</summary> | ||
94 | ProductCodes = 7, | ||
95 | |||
96 | /// <summary>PID_LASTAUTHOR = semicolon delimited list of transform names</summary> | ||
97 | TransformNames = 8, | ||
98 | |||
99 | /// <summary>PID_REVNUMBER = GUID patch code</summary> | ||
100 | PatchCode = 9, | ||
101 | |||
102 | /// <summary>PID_LASTPRINTED should be null for patches</summary> | ||
103 | Reserved11 = 11, | ||
104 | |||
105 | /// <summary>PID_PAGECOUNT should be null for patches</summary> | ||
106 | Reserved14 = 14, | ||
107 | |||
108 | /// <summary>PID_WORDCOUNT = minimum installer version</summary> | ||
109 | InstallerRequirement = 15, | ||
110 | |||
111 | /// <summary>PID_CHARCOUNT should be null for patches</summary> | ||
112 | Reserved16 = 16, | ||
113 | |||
114 | /// <summary>PID_SECURITY = read-only attribute of the patch package</summary> | ||
115 | Security = 19, | ||
116 | } | ||
117 | |||
118 | /// <summary> | ||
119 | /// Summary information values for the InstallerRequirement property. | ||
120 | /// </summary> | ||
121 | public enum InstallerRequirement | ||
122 | { | ||
123 | /// <summary>Any version of the installer will do</summary> | ||
124 | Version10 = 1, | ||
125 | |||
126 | /// <summary>At least 1.2</summary> | ||
127 | Version12 = 2, | ||
128 | |||
129 | /// <summary>At least 2.0</summary> | ||
130 | Version20 = 3, | ||
131 | |||
132 | /// <summary>At least 3.0</summary> | ||
133 | Version30 = 4, | ||
134 | |||
135 | /// <summary>At least 3.1</summary> | ||
136 | Version31 = 5, | ||
137 | } | ||
138 | |||
139 | /// <summary> | ||
140 | /// Instantiate a new SummaryInformation class from an open database. | ||
141 | /// </summary> | ||
142 | /// <param name="db">Database to retrieve summary information from.</param> | ||
143 | public SummaryInformation(Database db) | ||
144 | { | ||
145 | if (null == db) | ||
146 | { | ||
147 | throw new ArgumentNullException("db"); | ||
148 | } | ||
149 | |||
150 | uint handle = 0; | ||
151 | int error = MsiInterop.MsiGetSummaryInformation(db.Handle, null, 0, ref handle); | ||
152 | if (0 != error) | ||
153 | { | ||
154 | throw new MsiException(error); | ||
155 | } | ||
156 | this.Handle = handle; | ||
157 | } | ||
158 | |||
159 | /// <summary> | ||
160 | /// Instantiate a new SummaryInformation class from a database file. | ||
161 | /// </summary> | ||
162 | /// <param name="databaseFile">The database file.</param> | ||
163 | public SummaryInformation(string databaseFile) | ||
164 | { | ||
165 | if (null == databaseFile) | ||
166 | { | ||
167 | throw new ArgumentNullException("databaseFile"); | ||
168 | } | ||
169 | |||
170 | uint handle = 0; | ||
171 | int error = MsiInterop.MsiGetSummaryInformation(0, databaseFile, 0, ref handle); | ||
172 | if (0 != error) | ||
173 | { | ||
174 | throw new MsiException(error); | ||
175 | } | ||
176 | this.Handle = handle; | ||
177 | } | ||
178 | |||
179 | /// <summary> | ||
180 | /// Variant types in the summary information table. | ||
181 | /// </summary> | ||
182 | private enum VT : uint | ||
183 | { | ||
184 | /// <summary>Variant has not been assigned.</summary> | ||
185 | EMPTY = 0, | ||
186 | |||
187 | /// <summary>Null variant type.</summary> | ||
188 | NULL = 1, | ||
189 | |||
190 | /// <summary>16-bit integer variant type.</summary> | ||
191 | I2 = 2, | ||
192 | |||
193 | /// <summary>32-bit integer variant type.</summary> | ||
194 | I4 = 3, | ||
195 | |||
196 | /// <summary>String variant type.</summary> | ||
197 | LPSTR = 30, | ||
198 | |||
199 | /// <summary>Date time (FILETIME, converted to Variant time) variant type.</summary> | ||
200 | FILETIME = 64, | ||
201 | } | ||
202 | |||
203 | /// <summary> | ||
204 | /// Gets a summary information property. | ||
205 | /// </summary> | ||
206 | /// <param name="index">Index of the summary information property.</param> | ||
207 | /// <returns>The summary information property.</returns> | ||
208 | public string GetProperty(int index) | ||
209 | { | ||
210 | uint dataType; | ||
211 | StringBuilder stringValue = new StringBuilder(""); | ||
212 | int bufSize = 0; | ||
213 | int intValue; | ||
214 | FILETIME timeValue; | ||
215 | timeValue.dwHighDateTime = 0; | ||
216 | timeValue.dwLowDateTime = 0; | ||
217 | |||
218 | int error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize); | ||
219 | if (234 == error) | ||
220 | { | ||
221 | stringValue.EnsureCapacity(++bufSize); | ||
222 | error = MsiInterop.MsiSummaryInfoGetProperty(this.Handle, index, out dataType, out intValue, ref timeValue, stringValue, ref bufSize); | ||
223 | } | ||
224 | |||
225 | if (0 != error) | ||
226 | { | ||
227 | throw new MsiException(error); | ||
228 | } | ||
229 | |||
230 | switch ((VT)dataType) | ||
231 | { | ||
232 | case VT.EMPTY: | ||
233 | return String.Empty; | ||
234 | case VT.LPSTR: | ||
235 | return stringValue.ToString(); | ||
236 | case VT.I2: | ||
237 | case VT.I4: | ||
238 | return Convert.ToString(intValue, CultureInfo.InvariantCulture); | ||
239 | case VT.FILETIME: | ||
240 | long longFileTime = (((long)timeValue.dwHighDateTime) << 32) | unchecked((uint)timeValue.dwLowDateTime); | ||
241 | DateTime dateTime = DateTime.FromFileTime(longFileTime); | ||
242 | return dateTime.ToString("yyyy/MM/dd HH:mm:ss", CultureInfo.InvariantCulture); | ||
243 | default: | ||
244 | throw new InvalidOperationException(); | ||
245 | } | ||
246 | } | ||
247 | } | ||
248 | |||
249 | /// <summary> | ||
250 | /// Summary information values for the CharCount property in transforms. | ||
251 | /// </summary> | ||
252 | [Flags] | ||
253 | [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] | ||
254 | public enum TransformFlags | ||
255 | { | ||
256 | /// <summary>Ignore error when adding a row that exists.</summary> | ||
257 | ErrorAddExistingRow = 0x1, | ||
258 | |||
259 | /// <summary>Ignore error when deleting a row that does not exist.</summary> | ||
260 | ErrorDeleteMissingRow = 0x2, | ||
261 | |||
262 | /// <summary>Ignore error when adding a table that exists. </summary> | ||
263 | ErrorAddExistingTable = 0x4, | ||
264 | |||
265 | /// <summary>Ignore error when deleting a table that does not exist. </summary> | ||
266 | ErrorDeleteMissingTable = 0x8, | ||
267 | |||
268 | /// <summary>Ignore error when updating a row that does not exist. </summary> | ||
269 | ErrorUpdateMissingRow = 0x10, | ||
270 | |||
271 | /// <summary>Ignore error when transform and database code pages do not match, and their code pages are neutral.</summary> | ||
272 | ErrorChangeCodePage = 0x20, | ||
273 | |||
274 | /// <summary>Default language must match base database. </summary> | ||
275 | ValidateLanguage = 0x10000, | ||
276 | |||
277 | /// <summary>Product must match base database.</summary> | ||
278 | ValidateProduct = 0x20000, | ||
279 | |||
280 | /// <summary>Check major version only. </summary> | ||
281 | ValidateMajorVersion = 0x80000, | ||
282 | |||
283 | /// <summary>Check major and minor versions only. </summary> | ||
284 | ValidateMinorVersion = 0x100000, | ||
285 | |||
286 | /// <summary>Check major, minor, and update versions.</summary> | ||
287 | ValidateUpdateVersion = 0x200000, | ||
288 | |||
289 | /// <summary>Installed version lt base version. </summary> | ||
290 | ValidateNewLessBaseVersion = 0x400000, | ||
291 | |||
292 | /// <summary>Installed version lte base version. </summary> | ||
293 | ValidateNewLessEqualBaseVersion = 0x800000, | ||
294 | |||
295 | /// <summary>Installed version eq base version. </summary> | ||
296 | ValidateNewEqualBaseVersion = 0x1000000, | ||
297 | |||
298 | /// <summary>Installed version gte base version.</summary> | ||
299 | ValidateNewGreaterEqualBaseVersion = 0x2000000, | ||
300 | |||
301 | /// <summary>Installed version gt base version.</summary> | ||
302 | ValidateNewGreaterBaseVersion = 0x4000000, | ||
303 | |||
304 | /// <summary>UpgradeCode must match base database.</summary> | ||
305 | ValidateUpgradeCode = 0x8000000, | ||
306 | |||
307 | /// <summary>Masks all version checks on ProductVersion.</summary> | ||
308 | ProductVersionMask = ValidateMajorVersion | ValidateMinorVersion | ValidateUpdateVersion, | ||
309 | |||
310 | /// <summary>Masks all operations on ProductVersion.</summary> | ||
311 | ProductVersionOperatorMask = ValidateNewLessBaseVersion | ValidateNewLessEqualBaseVersion | ValidateNewEqualBaseVersion | ValidateNewGreaterEqualBaseVersion | ValidateNewGreaterBaseVersion, | ||
312 | |||
313 | /// <summary>Default value for instance transforms.</summary> | ||
314 | InstanceTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct | ValidateUpdateVersion | ValidateNewGreaterEqualBaseVersion, | ||
315 | |||
316 | /// <summary>Default value for language transforms.</summary> | ||
317 | LanguageTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct, | ||
318 | |||
319 | /// <summary>Default value for patch transforms.</summary> | ||
320 | PatchTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ValidateProduct | ValidateUpdateVersion | ValidateNewEqualBaseVersion | ValidateUpgradeCode, | ||
321 | } | ||
322 | |||
323 | } | ||
diff --git a/src/WixToolset.Core/Msi/View.cs b/src/WixToolset.Core/Msi/View.cs deleted file mode 100644 index d6542824..00000000 --- a/src/WixToolset.Core/Msi/View.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.Msi | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Globalization; | ||
8 | using WixToolset.Core.Native; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Enumeration of different modify modes. | ||
12 | /// </summary> | ||
13 | public enum ModifyView | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Writes current data in the cursor to a table row. Updates record if the primary | ||
17 | /// keys match an existing row and inserts if they do not match. Fails with a read-only | ||
18 | /// database. This mode cannot be used with a view containing joins. | ||
19 | /// </summary> | ||
20 | Assign = MsiInterop.MSIMODIFYASSIGN, | ||
21 | |||
22 | /// <summary> | ||
23 | /// Remove a row from the table. You must first call the Fetch function with the same | ||
24 | /// record. Fails if the row has been deleted. Works only with read-write records. This | ||
25 | /// mode cannot be used with a view containing joins. | ||
26 | /// </summary> | ||
27 | Delete = MsiInterop.MSIMODIFYDELETE, | ||
28 | |||
29 | /// <summary> | ||
30 | /// Inserts a record. Fails if a row with the same primary keys exists. Fails with a read-only | ||
31 | /// database. This mode cannot be used with a view containing joins. | ||
32 | /// </summary> | ||
33 | Insert = MsiInterop.MSIMODIFYINSERT, | ||
34 | |||
35 | /// <summary> | ||
36 | /// Inserts a temporary record. The information is not persistent. Fails if a row with the | ||
37 | /// same primary key exists. Works only with read-write records. This mode cannot be | ||
38 | /// used with a view containing joins. | ||
39 | /// </summary> | ||
40 | InsertTemporary = MsiInterop.MSIMODIFYINSERTTEMPORARY, | ||
41 | |||
42 | /// <summary> | ||
43 | /// Inserts or validates a record in a table. Inserts if primary keys do not match any row | ||
44 | /// and validates if there is a match. Fails if the record does not match the data in | ||
45 | /// the table. Fails if there is a record with a duplicate key that is not identical. | ||
46 | /// Works only with read-write records. This mode cannot be used with a view containing joins. | ||
47 | /// </summary> | ||
48 | Merge = MsiInterop.MSIMODIFYMERGE, | ||
49 | |||
50 | /// <summary> | ||
51 | /// Refreshes the information in the record. Must first call Fetch with the | ||
52 | /// same record. Fails for a deleted row. Works with read-write and read-only records. | ||
53 | /// </summary> | ||
54 | Refresh = MsiInterop.MSIMODIFYREFRESH, | ||
55 | |||
56 | /// <summary> | ||
57 | /// Updates or deletes and inserts a record into a table. Must first call Fetch with | ||
58 | /// the same record. Updates record if the primary keys are unchanged. Deletes old row and | ||
59 | /// inserts new if primary keys have changed. Fails with a read-only database. This mode cannot | ||
60 | /// be used with a view containing joins. | ||
61 | /// </summary> | ||
62 | Replace = MsiInterop.MSIMODIFYREPLACE, | ||
63 | |||
64 | /// <summary> | ||
65 | /// Refreshes the information in the supplied record without changing the position in the | ||
66 | /// result set and without affecting subsequent fetch operations. The record may then | ||
67 | /// be used for subsequent Update, Delete, and Refresh. All primary key columns of the | ||
68 | /// table must be in the query and the record must have at least as many fields as the | ||
69 | /// query. Seek cannot be used with multi-table queries. This mode cannot be used with | ||
70 | /// a view containing joins. See also the remarks. | ||
71 | /// </summary> | ||
72 | Seek = MsiInterop.MSIMODIFYSEEK, | ||
73 | |||
74 | /// <summary> | ||
75 | /// Updates an existing record. Non-primary keys only. Must first call Fetch. Fails with a | ||
76 | /// deleted record. Works only with read-write records. | ||
77 | /// </summary> | ||
78 | Update = MsiInterop.MSIMODIFYUPDATE | ||
79 | } | ||
80 | |||
81 | /// <summary> | ||
82 | /// Wrapper class for MSI API views. | ||
83 | /// </summary> | ||
84 | internal sealed class View : MsiHandle | ||
85 | { | ||
86 | /// <summary> | ||
87 | /// Constructor that creates a view given a database handle and a query. | ||
88 | /// </summary> | ||
89 | /// <param name="db">Handle to the database to run the query on.</param> | ||
90 | /// <param name="query">Query to be executed.</param> | ||
91 | public View(Database db, string query) | ||
92 | { | ||
93 | if (null == db) | ||
94 | { | ||
95 | throw new ArgumentNullException("db"); | ||
96 | } | ||
97 | |||
98 | if (null == query) | ||
99 | { | ||
100 | throw new ArgumentNullException("query"); | ||
101 | } | ||
102 | |||
103 | uint handle = 0; | ||
104 | |||
105 | int error = MsiInterop.MsiDatabaseOpenView(db.Handle, query, out handle); | ||
106 | if (0 != error) | ||
107 | { | ||
108 | throw new MsiException(error); | ||
109 | } | ||
110 | |||
111 | this.Handle = handle; | ||
112 | } | ||
113 | |||
114 | /// <summary> | ||
115 | /// Executes a view with no customizable parameters. | ||
116 | /// </summary> | ||
117 | public void Execute() | ||
118 | { | ||
119 | this.Execute(null); | ||
120 | } | ||
121 | |||
122 | /// <summary> | ||
123 | /// Executes a query substituing the values from the records into the customizable parameters | ||
124 | /// in the view. | ||
125 | /// </summary> | ||
126 | /// <param name="record">Record containing parameters to be substituded into the view.</param> | ||
127 | public void Execute(Record record) | ||
128 | { | ||
129 | int error = MsiInterop.MsiViewExecute(this.Handle, null == record ? 0 : record.Handle); | ||
130 | if (0 != error) | ||
131 | { | ||
132 | throw new MsiException(error); | ||
133 | } | ||
134 | } | ||
135 | |||
136 | /// <summary> | ||
137 | /// Fetches the next row in the view. | ||
138 | /// </summary> | ||
139 | /// <returns>Returns the fetched record; otherwise null.</returns> | ||
140 | public Record Fetch() | ||
141 | { | ||
142 | uint recordHandle; | ||
143 | |||
144 | int error = MsiInterop.MsiViewFetch(this.Handle, out recordHandle); | ||
145 | if (259 == error) | ||
146 | { | ||
147 | return null; | ||
148 | } | ||
149 | else if (0 != error) | ||
150 | { | ||
151 | throw new MsiException(error); | ||
152 | } | ||
153 | |||
154 | return new Record(recordHandle); | ||
155 | } | ||
156 | |||
157 | /// <summary> | ||
158 | /// Updates a fetched record. | ||
159 | /// </summary> | ||
160 | /// <param name="type">Type of modification mode.</param> | ||
161 | /// <param name="record">Record to be modified.</param> | ||
162 | public void Modify(ModifyView type, Record record) | ||
163 | { | ||
164 | int error = MsiInterop.MsiViewModify(this.Handle, Convert.ToInt32(type, CultureInfo.InvariantCulture), record.Handle); | ||
165 | if (0 != error) | ||
166 | { | ||
167 | throw new MsiException(error); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | /// <summary> | ||
172 | /// Returns a record containing column names or definitions. | ||
173 | /// </summary> | ||
174 | /// <param name="columnType">Specifies a flag indicating what type of information is needed. Either MSICOLINFO_NAMES or MSICOLINFO_TYPES.</param> | ||
175 | /// <returns>The record containing information about the column.</returns> | ||
176 | public Record GetColumnInfo(int columnType) | ||
177 | { | ||
178 | uint recordHandle; | ||
179 | |||
180 | int error = MsiInterop.MsiViewGetColumnInfo(this.Handle, columnType, out recordHandle); | ||
181 | if (0 != error) | ||
182 | { | ||
183 | throw new MsiException(error); | ||
184 | } | ||
185 | |||
186 | return new Record(recordHandle); | ||
187 | } | ||
188 | } | ||
189 | } | ||
diff --git a/src/WixToolset.Core/Ole32/Storage.cs b/src/WixToolset.Core/Ole32/Storage.cs deleted file mode 100644 index c6a43bc4..00000000 --- a/src/WixToolset.Core/Ole32/Storage.cs +++ /dev/null | |||
@@ -1,437 +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.Ole32 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Runtime.InteropServices; | ||
7 | using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; | ||
8 | using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Specifies the access mode to use when opening, creating, or deleting a storage object. | ||
12 | /// </summary> | ||
13 | internal enum StorageMode | ||
14 | { | ||
15 | /// <summary> | ||
16 | /// Indicates that the object is read-only, meaning that modifications cannot be made. | ||
17 | /// </summary> | ||
18 | Read = 0x0, | ||
19 | |||
20 | /// <summary> | ||
21 | /// Enables you to save changes to the object, but does not permit access to its data. | ||
22 | /// </summary> | ||
23 | Write = 0x1, | ||
24 | |||
25 | /// <summary> | ||
26 | /// Enables access and modification of object data. | ||
27 | /// </summary> | ||
28 | ReadWrite = 0x2, | ||
29 | |||
30 | /// <summary> | ||
31 | /// Specifies that subsequent openings of the object are not denied read or write access. | ||
32 | /// </summary> | ||
33 | ShareDenyNone = 0x40, | ||
34 | |||
35 | /// <summary> | ||
36 | /// Prevents others from subsequently opening the object in Read mode. | ||
37 | /// </summary> | ||
38 | ShareDenyRead = 0x30, | ||
39 | |||
40 | /// <summary> | ||
41 | /// Prevents others from subsequently opening the object for Write or ReadWrite access. | ||
42 | /// </summary> | ||
43 | ShareDenyWrite = 0x20, | ||
44 | |||
45 | /// <summary> | ||
46 | /// Prevents others from subsequently opening the object in any mode. | ||
47 | /// </summary> | ||
48 | ShareExclusive = 0x10, | ||
49 | |||
50 | /// <summary> | ||
51 | /// Opens the storage object with exclusive access to the most recently committed version. | ||
52 | /// </summary> | ||
53 | Priority = 0x40000, | ||
54 | |||
55 | /// <summary> | ||
56 | /// Indicates that an existing storage object or stream should be removed before the new object replaces it. | ||
57 | /// </summary> | ||
58 | Create = 0x1000, | ||
59 | } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Wrapper for the compound storage file APIs. | ||
63 | /// </summary> | ||
64 | internal sealed class Storage : IDisposable | ||
65 | { | ||
66 | private bool disposed; | ||
67 | private IStorage storage; | ||
68 | |||
69 | /// <summary> | ||
70 | /// Instantiate a new Storage. | ||
71 | /// </summary> | ||
72 | /// <param name="storage">The native storage interface.</param> | ||
73 | private Storage(IStorage storage) | ||
74 | { | ||
75 | this.storage = storage; | ||
76 | } | ||
77 | |||
78 | /// <summary> | ||
79 | /// Storage destructor. | ||
80 | /// </summary> | ||
81 | ~Storage() | ||
82 | { | ||
83 | this.Dispose(); | ||
84 | } | ||
85 | |||
86 | /// <summary> | ||
87 | /// The IEnumSTATSTG interface enumerates an array of STATSTG structures. | ||
88 | /// </summary> | ||
89 | [ComImport, Guid("0000000d-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
90 | public interface IEnumSTATSTG | ||
91 | { | ||
92 | /// <summary> | ||
93 | /// Gets a specified number of STATSTG structures. | ||
94 | /// </summary> | ||
95 | /// <param name="celt">The number of STATSTG structures requested.</param> | ||
96 | /// <param name="rgelt">An array of STATSTG structures returned.</param> | ||
97 | /// <param name="pceltFetched">The number of STATSTG structures retrieved in the rgelt parameter.</param> | ||
98 | /// <returns>The error code.</returns> | ||
99 | [PreserveSig] | ||
100 | uint Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] STATSTG[] rgelt, out uint pceltFetched); | ||
101 | |||
102 | /// <summary> | ||
103 | /// Skips a specified number of STATSTG structures in the enumeration sequence. | ||
104 | /// </summary> | ||
105 | /// <param name="celt">The number of STATSTG structures to skip.</param> | ||
106 | void Skip(uint celt); | ||
107 | |||
108 | /// <summary> | ||
109 | /// Resets the enumeration sequence to the beginning of the STATSTG structure array. | ||
110 | /// </summary> | ||
111 | void Reset(); | ||
112 | |||
113 | /// <summary> | ||
114 | /// Creates a new enumerator that contains the same enumeration state as the current STATSTG structure enumerator. | ||
115 | /// </summary> | ||
116 | /// <returns>The cloned IEnumSTATSTG interface.</returns> | ||
117 | [return: MarshalAs(UnmanagedType.Interface)] | ||
118 | IEnumSTATSTG Clone(); | ||
119 | } | ||
120 | |||
121 | /// <summary> | ||
122 | /// The IStorage interface supports the creation and management of structured storage objects. | ||
123 | /// </summary> | ||
124 | [ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
125 | private interface IStorage | ||
126 | { | ||
127 | /// <summary> | ||
128 | /// Creates and opens a stream object with the specified name contained in this storage object. | ||
129 | /// </summary> | ||
130 | /// <param name="pwcsName">The name of the newly created stream.</param> | ||
131 | /// <param name="grfMode">Specifies the access mode to use when opening the newly created stream.</param> | ||
132 | /// <param name="reserved1">Reserved for future use; must be zero.</param> | ||
133 | /// <param name="reserved2">Reserved for future use; must be zero.</param> | ||
134 | /// <param name="ppstm">On return, pointer to the location of the new IStream interface pointer.</param> | ||
135 | void CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm); | ||
136 | |||
137 | /// <summary> | ||
138 | /// Opens an existing stream object within this storage object using the specified access permissions in grfMode. | ||
139 | /// </summary> | ||
140 | /// <param name="pwcsName">The name of the stream to open.</param> | ||
141 | /// <param name="reserved1">Reserved for future use; must be NULL.</param> | ||
142 | /// <param name="grfMode">Specifies the access mode to be assigned to the open stream.</param> | ||
143 | /// <param name="reserved2">Reserved for future use; must be zero.</param> | ||
144 | /// <param name="ppstm">A pointer to IStream pointer variable that receives the interface pointer to the newly opened stream object.</param> | ||
145 | void OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm); | ||
146 | |||
147 | /// <summary> | ||
148 | /// Creates and opens a new storage object nested within this storage object with the specified name in the specified access mode. | ||
149 | /// </summary> | ||
150 | /// <param name="pwcsName">The name of the newly created storage object.</param> | ||
151 | /// <param name="grfMode">A value that specifies the access mode to use when opening the newly created storage object.</param> | ||
152 | /// <param name="reserved1">Reserved for future use; must be zero.</param> | ||
153 | /// <param name="reserved2">Reserved for future use; must be zero.</param> | ||
154 | /// <param name="ppstg">A pointer, when successful, to the location of the IStorage pointer to the newly created storage object.</param> | ||
155 | void CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg); | ||
156 | |||
157 | /// <summary> | ||
158 | /// Opens an existing storage object with the specified name in the specified access mode. | ||
159 | /// </summary> | ||
160 | /// <param name="pwcsName">The name of the storage object to open.</param> | ||
161 | /// <param name="pstgPriority">Must be NULL.</param> | ||
162 | /// <param name="grfMode">Specifies the access mode to use when opening the storage object.</param> | ||
163 | /// <param name="snbExclude">Must be NULL.</param> | ||
164 | /// <param name="reserved">Reserved for future use; must be zero.</param> | ||
165 | /// <param name="ppstg">When successful, pointer to the location of an IStorage pointer to the opened storage object.</param> | ||
166 | void OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg); | ||
167 | |||
168 | /// <summary> | ||
169 | /// Copies the entire contents of an open storage object to another storage object. | ||
170 | /// </summary> | ||
171 | /// <param name="ciidExclude">The number of elements in the array pointed to by rgiidExclude.</param> | ||
172 | /// <param name="rgiidExclude">An array of interface identifiers (IIDs) that either the caller knows about and does not want | ||
173 | /// copied or that the storage object does not support, but whose state the caller will later explicitly copy.</param> | ||
174 | /// <param name="snbExclude">A string name block (refer to SNB) that specifies a block of storage or stream objects that are not to be copied to the destination.</param> | ||
175 | /// <param name="pstgDest">A pointer to the open storage object into which this storage object is to be copied.</param> | ||
176 | void CopyTo(uint ciidExclude, IntPtr rgiidExclude, IntPtr snbExclude, IStorage pstgDest); | ||
177 | |||
178 | /// <summary> | ||
179 | /// Copies or moves a substorage or stream from this storage object to another storage object. | ||
180 | /// </summary> | ||
181 | /// <param name="pwcsName">The name of the element in this storage object to be moved or copied.</param> | ||
182 | /// <param name="pstgDest">IStorage pointer to the destination storage object.</param> | ||
183 | /// <param name="pwcsNewName">The new name for the element in its new storage object.</param> | ||
184 | /// <param name="grfFlags">Specifies whether the operation should be a move (STGMOVE_MOVE) or a copy (STGMOVE_COPY).</param> | ||
185 | void MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags); | ||
186 | |||
187 | /// <summary> | ||
188 | /// Reflects changes for a transacted storage object to the parent level. | ||
189 | /// </summary> | ||
190 | /// <param name="grfCommitFlags">Controls how the changes are committed to the storage object.</param> | ||
191 | void Commit(uint grfCommitFlags); | ||
192 | |||
193 | /// <summary> | ||
194 | /// Discards all changes that have been made to the storage object since the last commit operation. | ||
195 | /// </summary> | ||
196 | void Revert(); | ||
197 | |||
198 | /// <summary> | ||
199 | /// Returns an enumerator object that can be used to enumerate the storage and stream objects contained within this storage object. | ||
200 | /// </summary> | ||
201 | /// <param name="reserved1">Reserved for future use; must be zero.</param> | ||
202 | /// <param name="reserved2">Reserved for future use; must be NULL.</param> | ||
203 | /// <param name="reserved3">Reserved for future use; must be zero.</param> | ||
204 | /// <param name="ppenum">Pointer to IEnumSTATSTG* pointer variable that receives the interface pointer to the new enumerator object.</param> | ||
205 | void EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum); | ||
206 | |||
207 | /// <summary> | ||
208 | /// Removes the specified storage or stream from this storage object. | ||
209 | /// </summary> | ||
210 | /// <param name="pwcsName">The name of the storage or stream to be removed.</param> | ||
211 | void DestroyElement(string pwcsName); | ||
212 | |||
213 | /// <summary> | ||
214 | /// Renames the specified storage or stream in this storage object. | ||
215 | /// </summary> | ||
216 | /// <param name="pwcsOldName">The name of the substorage or stream to be changed.</param> | ||
217 | /// <param name="pwcsNewName">The new name for the specified substorage or stream.</param> | ||
218 | void RenameElement(string pwcsOldName, string pwcsNewName); | ||
219 | |||
220 | /// <summary> | ||
221 | /// Sets the modification, access, and creation times of the indicated storage element, if supported by the underlying file system. | ||
222 | /// </summary> | ||
223 | /// <param name="pwcsName">The name of the storage object element whose times are to be modified.</param> | ||
224 | /// <param name="pctime">Either the new creation time for the element or NULL if the creation time is not to be modified.</param> | ||
225 | /// <param name="patime">Either the new access time for the element or NULL if the access time is not to be modified.</param> | ||
226 | /// <param name="pmtime">Either the new modification time for the element or NULL if the modification time is not to be modified.</param> | ||
227 | void SetElementTimes(string pwcsName, FILETIME pctime, FILETIME patime, FILETIME pmtime); | ||
228 | |||
229 | /// <summary> | ||
230 | /// Assigns the specified CLSID to this storage object. | ||
231 | /// </summary> | ||
232 | /// <param name="clsid">The CLSID that is to be associated with the storage object.</param> | ||
233 | void SetClass(Guid clsid); | ||
234 | |||
235 | /// <summary> | ||
236 | /// Stores up to 32 bits of state information in this storage object. | ||
237 | /// </summary> | ||
238 | /// <param name="grfStateBits">Specifies the new values of the bits to set.</param> | ||
239 | /// <param name="grfMask">A binary mask indicating which bits in grfStateBits are significant in this call.</param> | ||
240 | void SetStateBits(uint grfStateBits, uint grfMask); | ||
241 | |||
242 | /// <summary> | ||
243 | /// Returns the STATSTG structure for this open storage object. | ||
244 | /// </summary> | ||
245 | /// <param name="pstatstg">On return, pointer to a STATSTG structure where this method places information about the open storage object.</param> | ||
246 | /// <param name="grfStatFlag">Specifies that some of the members in the STATSTG structure are not returned, thus saving a memory allocation operation.</param> | ||
247 | void Stat(out STATSTG pstatstg, uint grfStatFlag); | ||
248 | } | ||
249 | |||
250 | /// <summary> | ||
251 | /// The IStream interface lets you read and write data to stream objects. | ||
252 | /// </summary> | ||
253 | [ComImport, Guid("0000000c-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | ||
254 | private interface IStream | ||
255 | { | ||
256 | /// <summary> | ||
257 | /// Reads a specified number of bytes from the stream object into memory starting at the current seek pointer. | ||
258 | /// </summary> | ||
259 | /// <param name="pv">A pointer to the buffer which the stream data is read into.</param> | ||
260 | /// <param name="cb">The number of bytes of data to read from the stream object.</param> | ||
261 | /// <param name="pcbRead">A pointer to a ULONG variable that receives the actual number of bytes read from the stream object.</param> | ||
262 | void Read([Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbRead); | ||
263 | |||
264 | /// <summary> | ||
265 | /// Writes a specified number of bytes into the stream object starting at the current seek pointer. | ||
266 | /// </summary> | ||
267 | /// <param name="pv">A pointer to the buffer that contains the data that is to be written to the stream.</param> | ||
268 | /// <param name="cb">The number of bytes of data to attempt to write into the stream.</param> | ||
269 | /// <param name="pcbWritten">A pointer to a ULONG variable where this method writes the actual number of bytes written to the stream object.</param> | ||
270 | void Write([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pv, int cb, IntPtr pcbWritten); | ||
271 | |||
272 | /// <summary> | ||
273 | /// Changes the seek pointer to a new location relative to the beginning of the stream, the end of the stream, or the current seek pointer. | ||
274 | /// </summary> | ||
275 | /// <param name="dlibMove">The displacement to be added to the location indicated by the dwOrigin parameter.</param> | ||
276 | /// <param name="dwOrigin">The origin for the displacement specified in dlibMove.</param> | ||
277 | /// <param name="plibNewPosition">A pointer to the location where this method writes the value of the new seek pointer from the beginning of the stream.</param> | ||
278 | void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition); | ||
279 | |||
280 | /// <summary> | ||
281 | /// Changes the size of the stream object. | ||
282 | /// </summary> | ||
283 | /// <param name="libNewSize">Specifies the new size of the stream as a number of bytes.</param> | ||
284 | void SetSize(long libNewSize); | ||
285 | |||
286 | /// <summary> | ||
287 | /// Copies a specified number of bytes from the current seek pointer in the stream to the current seek pointer in another stream. | ||
288 | /// </summary> | ||
289 | /// <param name="pstm">A pointer to the destination stream.</param> | ||
290 | /// <param name="cb">The number of bytes to copy from the source stream.</param> | ||
291 | /// <param name="pcbRead">A pointer to the location where this method writes the actual number of bytes read from the source.</param> | ||
292 | /// <param name="pcbWritten">A pointer to the location where this method writes the actual number of bytes written to the destination.</param> | ||
293 | void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten); | ||
294 | |||
295 | /// <summary> | ||
296 | /// Ensures that any changes made to a stream object open in transacted mode are reflected in the parent storage object. | ||
297 | /// </summary> | ||
298 | /// <param name="grfCommitFlags">Controls how the changes for the stream object are committed.</param> | ||
299 | void Commit(int grfCommitFlags); | ||
300 | |||
301 | /// <summary> | ||
302 | /// Discards all changes that have been made to a transacted stream since the last call to IStream::Commit. | ||
303 | /// </summary> | ||
304 | void Revert(); | ||
305 | |||
306 | /// <summary> | ||
307 | /// Restricts access to a specified range of bytes in the stream. | ||
308 | /// </summary> | ||
309 | /// <param name="libOffset">Integer that specifies the byte offset for the beginning of the range.</param> | ||
310 | /// <param name="cb">Integer that specifies the length of the range, in bytes, to be restricted.</param> | ||
311 | /// <param name="dwLockType">Specifies the restrictions being requested on accessing the range.</param> | ||
312 | void LockRegion(long libOffset, long cb, int dwLockType); | ||
313 | |||
314 | /// <summary> | ||
315 | /// Removes the access restriction on a range of bytes previously restricted with IStream::LockRegion. | ||
316 | /// </summary> | ||
317 | /// <param name="libOffset">Specifies the byte offset for the beginning of the range.</param> | ||
318 | /// <param name="cb">Specifies, in bytes, the length of the range to be restricted.</param> | ||
319 | /// <param name="dwLockType">Specifies the access restrictions previously placed on the range.</param> | ||
320 | void UnlockRegion(long libOffset, long cb, int dwLockType); | ||
321 | |||
322 | /// <summary> | ||
323 | /// Retrieves the STATSTG structure for this stream. | ||
324 | /// </summary> | ||
325 | /// <param name="pstatstg">Pointer to a STATSTG structure where this method places information about this stream object.</param> | ||
326 | /// <param name="grfStatFlag">Specifies that this method does not return some of the members in the STATSTG structure, thus saving a memory allocation operation.</param> | ||
327 | void Stat(out STATSTG pstatstg, int grfStatFlag); | ||
328 | |||
329 | /// <summary> | ||
330 | /// Creates a new stream object that references the same bytes as the original stream but provides a separate seek pointer to those bytes. | ||
331 | /// </summary> | ||
332 | /// <param name="ppstm">When successful, pointer to the location of an IStream pointer to the new stream object.</param> | ||
333 | void Clone(out IStream ppstm); | ||
334 | } | ||
335 | |||
336 | /// <summary> | ||
337 | /// Creates a new compound file storage object. | ||
338 | /// </summary> | ||
339 | /// <param name="storageFile">The compound file being created.</param> | ||
340 | /// <param name="mode">Specifies the access mode to use when opening the new storage object.</param> | ||
341 | /// <returns>The created Storage object.</returns> | ||
342 | public static Storage CreateDocFile(string storageFile, StorageMode mode) | ||
343 | { | ||
344 | IStorage storage = NativeMethods.StgCreateDocfile(storageFile, (uint)mode, 0); | ||
345 | |||
346 | return new Storage(storage); | ||
347 | } | ||
348 | |||
349 | /// <summary> | ||
350 | /// Opens an existing root storage object in the file system. | ||
351 | /// </summary> | ||
352 | /// <param name="storageFile">The file that contains the storage object to open.</param> | ||
353 | /// <param name="mode">Specifies the access mode to use to open the storage object.</param> | ||
354 | /// <returns>The created Storage object.</returns> | ||
355 | public static Storage Open(string storageFile, StorageMode mode) | ||
356 | { | ||
357 | IStorage storage = NativeMethods.StgOpenStorage(storageFile, IntPtr.Zero, (uint)mode, IntPtr.Zero, 0); | ||
358 | |||
359 | return new Storage(storage); | ||
360 | } | ||
361 | |||
362 | /// <summary> | ||
363 | /// Copies the entire contents of this open storage object into another Storage object. | ||
364 | /// </summary> | ||
365 | /// <param name="destinationStorage">The destination Storage object.</param> | ||
366 | public void CopyTo(Storage destinationStorage) | ||
367 | { | ||
368 | this.storage.CopyTo(0, IntPtr.Zero, IntPtr.Zero, destinationStorage.storage); | ||
369 | } | ||
370 | |||
371 | /// <summary> | ||
372 | /// Opens an existing Storage object with the specified name according to the specified access mode. | ||
373 | /// </summary> | ||
374 | /// <param name="name">The name of the Storage object.</param> | ||
375 | /// <returns>The opened Storage object.</returns> | ||
376 | public Storage OpenStorage(string name) | ||
377 | { | ||
378 | IStorage subStorage; | ||
379 | |||
380 | this.storage.OpenStorage(name, null, (uint)(StorageMode.Read | StorageMode.ShareExclusive), IntPtr.Zero, 0, out subStorage); | ||
381 | |||
382 | return new Storage(subStorage); | ||
383 | } | ||
384 | |||
385 | /// <summary> | ||
386 | /// Disposes the managed and unmanaged objects in this object. | ||
387 | /// </summary> | ||
388 | public void Dispose() | ||
389 | { | ||
390 | if (!this.disposed) | ||
391 | { | ||
392 | Marshal.ReleaseComObject(this.storage); | ||
393 | |||
394 | this.disposed = true; | ||
395 | } | ||
396 | |||
397 | GC.SuppressFinalize(this); | ||
398 | } | ||
399 | |||
400 | /// <summary> | ||
401 | /// The native methods. | ||
402 | /// </summary> | ||
403 | private sealed class NativeMethods | ||
404 | { | ||
405 | /// <summary> | ||
406 | /// Protect the constructor since this class only contains static methods. | ||
407 | /// </summary> | ||
408 | private NativeMethods() | ||
409 | { | ||
410 | } | ||
411 | |||
412 | /// <summary> | ||
413 | /// Creates a new compound file storage object. | ||
414 | /// </summary> | ||
415 | /// <param name="pwcsName">The name for the compound file being created.</param> | ||
416 | /// <param name="grfMode">Specifies the access mode to use when opening the new storage object.</param> | ||
417 | /// <param name="reserved">Reserved for future use; must be zero.</param> | ||
418 | /// <returns>A pointer to the location of the IStorage pointer to the new storage object.</returns> | ||
419 | [DllImport("ole32.dll", PreserveSig = false)] | ||
420 | [return: MarshalAs(UnmanagedType.Interface)] | ||
421 | internal static extern IStorage StgCreateDocfile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, uint grfMode, uint reserved); | ||
422 | |||
423 | /// <summary> | ||
424 | /// Opens an existing root storage object in the file system. | ||
425 | /// </summary> | ||
426 | /// <param name="pwcsName">The file that contains the storage object to open.</param> | ||
427 | /// <param name="pstgPriority">Most often NULL.</param> | ||
428 | /// <param name="grfMode">Specifies the access mode to use to open the storage object.</param> | ||
429 | /// <param name="snbExclude">If not NULL, pointer to a block of elements in the storage to be excluded as the storage object is opened.</param> | ||
430 | /// <param name="reserved">Indicates reserved for future use; must be zero.</param> | ||
431 | /// <returns>A pointer to a IStorage* pointer variable that receives the interface pointer to the opened storage.</returns> | ||
432 | [DllImport("ole32.dll", PreserveSig = false)] | ||
433 | [return: MarshalAs(UnmanagedType.Interface)] | ||
434 | internal static extern IStorage StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved); | ||
435 | } | ||
436 | } | ||
437 | } | ||
diff --git a/src/WixToolset.Core/OptimizeCA.cs b/src/WixToolset.Core/OptimizeCA.cs new file mode 100644 index 00000000..efd07299 --- /dev/null +++ b/src/WixToolset.Core/OptimizeCA.cs | |||
@@ -0,0 +1,33 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Core | ||
4 | { | ||
5 | using System; | ||
6 | |||
7 | /// <summary> | ||
8 | /// Values for the OptimizeCA MsiPatchMetdata property, which indicates whether custom actions can be skipped when applying the patch. | ||
9 | /// </summary> | ||
10 | [Flags] | ||
11 | public enum OptimizeCA | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// No custom actions are skipped. | ||
15 | /// </summary> | ||
16 | None = 0, | ||
17 | |||
18 | /// <summary> | ||
19 | /// Skip property (type 51) and directory (type 35) assignment custom actions. | ||
20 | /// </summary> | ||
21 | SkipAssignment = 1, | ||
22 | |||
23 | /// <summary> | ||
24 | /// Skip immediate custom actions that are not property or directory assignment custom actions. | ||
25 | /// </summary> | ||
26 | SkipImmediate = 2, | ||
27 | |||
28 | /// <summary> | ||
29 | /// Skip custom actions that run within the script. | ||
30 | /// </summary> | ||
31 | SkipDeferred = 4, | ||
32 | } | ||
33 | } | ||
diff --git a/src/WixToolset.Core/Patch.cs b/src/WixToolset.Core/Patch.cs deleted file mode 100644 index e3e6c27f..00000000 --- a/src/WixToolset.Core/Patch.cs +++ /dev/null | |||
@@ -1,1284 +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.Data | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Diagnostics.CodeAnalysis; | ||
9 | using System.Globalization; | ||
10 | using WixToolset.Data.Rows; | ||
11 | using WixToolset.Extensibility; | ||
12 | using WixToolset.Msi; | ||
13 | using WixToolset.Core.Native; | ||
14 | |||
15 | /// <summary> | ||
16 | /// Values for the OptimizeCA MsiPatchMetdata property, which indicates whether custom actions can be skipped when applying the patch. | ||
17 | /// </summary> | ||
18 | [Flags] | ||
19 | internal enum OptimizeCA | ||
20 | { | ||
21 | /// <summary> | ||
22 | /// No custom actions are skipped. | ||
23 | /// </summary> | ||
24 | None = 0, | ||
25 | |||
26 | /// <summary> | ||
27 | /// Skip property (type 51) and directory (type 35) assignment custom actions. | ||
28 | /// </summary> | ||
29 | SkipAssignment = 1, | ||
30 | |||
31 | /// <summary> | ||
32 | /// Skip immediate custom actions that are not property or directory assignment custom actions. | ||
33 | /// </summary> | ||
34 | SkipImmediate = 2, | ||
35 | |||
36 | /// <summary> | ||
37 | /// Skip custom actions that run within the script. | ||
38 | /// </summary> | ||
39 | SkipDeferred = 4, | ||
40 | } | ||
41 | |||
42 | /// <summary> | ||
43 | /// Contains output tables and logic for building an MSP package. | ||
44 | /// </summary> | ||
45 | public class Patch | ||
46 | { | ||
47 | private List<IInspectorExtension> inspectorExtensions; | ||
48 | private Output patch; | ||
49 | private TableDefinitionCollection tableDefinitions; | ||
50 | |||
51 | public Output PatchOutput | ||
52 | { | ||
53 | get { return this.patch; } | ||
54 | } | ||
55 | |||
56 | public Patch() | ||
57 | { | ||
58 | this.inspectorExtensions = new List<IInspectorExtension>(); | ||
59 | this.tableDefinitions = new TableDefinitionCollection(WindowsInstallerStandard.GetTableDefinitions()); | ||
60 | } | ||
61 | |||
62 | /// <summary> | ||
63 | /// Adds an extension. | ||
64 | /// </summary> | ||
65 | /// <param name="extension">The extension to add.</param> | ||
66 | public void AddExtension(IInspectorExtension extension) | ||
67 | { | ||
68 | this.inspectorExtensions.Add(extension); | ||
69 | } | ||
70 | |||
71 | public void Load(string patchPath) | ||
72 | { | ||
73 | this.patch = Output.Load(patchPath, false); | ||
74 | } | ||
75 | |||
76 | /// <summary> | ||
77 | /// Include transforms in a patch. | ||
78 | /// </summary> | ||
79 | /// <param name="transforms">List of transforms to attach.</param> | ||
80 | [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] | ||
81 | public void AttachTransforms(List<PatchTransform> transforms) | ||
82 | { | ||
83 | InspectorCore inspectorCore = new InspectorCore(); | ||
84 | |||
85 | // Track if at least one transform gets attached. | ||
86 | bool attachedTransform = false; | ||
87 | |||
88 | if (transforms == null || transforms.Count == 0) | ||
89 | { | ||
90 | throw new WixException(WixErrors.PatchWithoutTransforms()); | ||
91 | } | ||
92 | |||
93 | // Get the patch id from the WixPatchId table. | ||
94 | string patchId = null; | ||
95 | string clientPatchId = null; | ||
96 | Table wixPatchIdTable = this.patch.Tables["WixPatchId"]; | ||
97 | if (null != wixPatchIdTable && 0 < wixPatchIdTable.Rows.Count) | ||
98 | { | ||
99 | Row patchIdRow = wixPatchIdTable.Rows[0]; | ||
100 | if (null != patchIdRow) | ||
101 | { | ||
102 | patchId = patchIdRow[0].ToString(); | ||
103 | clientPatchId = patchIdRow[1].ToString(); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | if (null == patchId) | ||
108 | { | ||
109 | throw new WixException(WixErrors.ExpectedPatchIdInWixMsp()); | ||
110 | } | ||
111 | if (null == clientPatchId) | ||
112 | { | ||
113 | throw new WixException(WixErrors.ExpectedClientPatchIdInWixMsp()); | ||
114 | } | ||
115 | |||
116 | // enumerate patch.Media to map diskId to Media row | ||
117 | Table patchMediaTable = patch.Tables["Media"]; | ||
118 | |||
119 | if (null == patchMediaTable || patchMediaTable.Rows.Count == 0) | ||
120 | { | ||
121 | throw new WixException(WixErrors.ExpectedMediaRowsInWixMsp()); | ||
122 | } | ||
123 | |||
124 | Hashtable mediaRows = new Hashtable(patchMediaTable.Rows.Count); | ||
125 | foreach (MediaRow row in patchMediaTable.Rows) | ||
126 | { | ||
127 | int media = row.DiskId; | ||
128 | mediaRows[media] = row; | ||
129 | } | ||
130 | |||
131 | // enumerate patch.WixPatchBaseline to map baseline to diskId | ||
132 | Table patchBaselineTable = patch.Tables["WixPatchBaseline"]; | ||
133 | |||
134 | int numPatchBaselineRows = (null != patchBaselineTable) ? patchBaselineTable.Rows.Count : 0; | ||
135 | |||
136 | Hashtable baselineMedia = new Hashtable(numPatchBaselineRows); | ||
137 | if (patchBaselineTable != null) | ||
138 | { | ||
139 | foreach (Row row in patchBaselineTable.Rows) | ||
140 | { | ||
141 | string baseline = (string)row[0]; | ||
142 | int media = (int)row[1]; | ||
143 | int validationFlags = (int)row[2]; | ||
144 | if (baselineMedia.Contains(baseline)) | ||
145 | { | ||
146 | this.OnMessage(WixErrors.SamePatchBaselineId(row.SourceLineNumbers, baseline)); | ||
147 | } | ||
148 | baselineMedia[baseline] = new int[] { media, validationFlags }; | ||
149 | } | ||
150 | } | ||
151 | |||
152 | // populate MSP summary information | ||
153 | Table patchSummaryInfo = patch.EnsureTable(this.tableDefinitions["_SummaryInformation"]); | ||
154 | |||
155 | // Remove properties that will be calculated or are reserved. | ||
156 | for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) | ||
157 | { | ||
158 | Row row = patchSummaryInfo.Rows[i]; | ||
159 | switch ((SummaryInformation.Patch)row[0]) | ||
160 | { | ||
161 | case SummaryInformation.Patch.ProductCodes: | ||
162 | case SummaryInformation.Patch.TransformNames: | ||
163 | case SummaryInformation.Patch.PatchCode: | ||
164 | case SummaryInformation.Patch.InstallerRequirement: | ||
165 | case SummaryInformation.Patch.Reserved11: | ||
166 | case SummaryInformation.Patch.Reserved14: | ||
167 | case SummaryInformation.Patch.Reserved16: | ||
168 | patchSummaryInfo.Rows.RemoveAt(i); | ||
169 | break; | ||
170 | } | ||
171 | } | ||
172 | |||
173 | // Index remaining summary properties. | ||
174 | SummaryInfoRowCollection summaryInfo = new SummaryInfoRowCollection(patchSummaryInfo); | ||
175 | |||
176 | // PID_CODEPAGE | ||
177 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage)) | ||
178 | { | ||
179 | // set the code page by default to the same code page for the | ||
180 | // string pool in the database. | ||
181 | Row codePage = patchSummaryInfo.CreateRow(null); | ||
182 | codePage[0] = (int)SummaryInformation.Patch.CodePage; | ||
183 | codePage[1] = this.patch.Codepage.ToString(CultureInfo.InvariantCulture); | ||
184 | } | ||
185 | |||
186 | // GUID patch code for the patch. | ||
187 | Row revisionRow = patchSummaryInfo.CreateRow(null); | ||
188 | revisionRow[0] = (int)SummaryInformation.Patch.PatchCode; | ||
189 | revisionRow[1] = patchId; | ||
190 | |||
191 | // Indicates the minimum Windows Installer version that is required to install the patch. | ||
192 | Row wordsRow = patchSummaryInfo.CreateRow(null); | ||
193 | wordsRow[0] = (int)SummaryInformation.Patch.InstallerRequirement; | ||
194 | wordsRow[1] = ((int)SummaryInformation.InstallerRequirement.Version31).ToString(CultureInfo.InvariantCulture); | ||
195 | |||
196 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Security)) | ||
197 | { | ||
198 | Row security = patchSummaryInfo.CreateRow(null); | ||
199 | security[0] = (int)SummaryInformation.Patch.Security; | ||
200 | security[1] = "4"; // Read-only enforced | ||
201 | } | ||
202 | |||
203 | // use authored comments or default to DisplayName (required) | ||
204 | string comments = null; | ||
205 | |||
206 | Table msiPatchMetadataTable = patch.Tables["MsiPatchMetadata"]; | ||
207 | Hashtable metadataTable = new Hashtable(); | ||
208 | if (null != msiPatchMetadataTable) | ||
209 | { | ||
210 | foreach (Row row in msiPatchMetadataTable.Rows) | ||
211 | { | ||
212 | metadataTable.Add(row.Fields[1].Data.ToString(), row.Fields[2].Data.ToString()); | ||
213 | } | ||
214 | |||
215 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Title) && metadataTable.Contains("DisplayName")) | ||
216 | { | ||
217 | string displayName = (string)metadataTable["DisplayName"]; | ||
218 | |||
219 | Row title = patchSummaryInfo.CreateRow(null); | ||
220 | title[0] = (int)SummaryInformation.Patch.Title; | ||
221 | title[1] = displayName; | ||
222 | |||
223 | // default comments use DisplayName as-is (no loc) | ||
224 | comments = displayName; | ||
225 | } | ||
226 | |||
227 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.CodePage) && metadataTable.Contains("CodePage")) | ||
228 | { | ||
229 | Row codePage = patchSummaryInfo.CreateRow(null); | ||
230 | codePage[0] = (int)SummaryInformation.Patch.CodePage; | ||
231 | codePage[1] = metadataTable["CodePage"]; | ||
232 | } | ||
233 | |||
234 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.PackageName) && metadataTable.Contains("Description")) | ||
235 | { | ||
236 | Row subject = patchSummaryInfo.CreateRow(null); | ||
237 | subject[0] = (int)SummaryInformation.Patch.PackageName; | ||
238 | subject[1] = metadataTable["Description"]; | ||
239 | } | ||
240 | |||
241 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Manufacturer) && metadataTable.Contains("ManufacturerName")) | ||
242 | { | ||
243 | Row author = patchSummaryInfo.CreateRow(null); | ||
244 | author[0] = (int)SummaryInformation.Patch.Manufacturer; | ||
245 | author[1] = metadataTable["ManufacturerName"]; | ||
246 | } | ||
247 | } | ||
248 | |||
249 | // special metadata marshalled through the build | ||
250 | Table wixPatchMetadataTable = patch.Tables["WixPatchMetadata"]; | ||
251 | Hashtable wixMetadataTable = new Hashtable(); | ||
252 | if (null != wixPatchMetadataTable) | ||
253 | { | ||
254 | foreach (Row row in wixPatchMetadataTable.Rows) | ||
255 | { | ||
256 | wixMetadataTable.Add(row.Fields[0].Data.ToString(), row.Fields[1].Data.ToString()); | ||
257 | } | ||
258 | |||
259 | if (wixMetadataTable.Contains("Comments")) | ||
260 | { | ||
261 | comments = (string)wixMetadataTable["Comments"]; | ||
262 | } | ||
263 | } | ||
264 | |||
265 | // write the package comments to summary info | ||
266 | if (!summaryInfo.Contains((int)SummaryInformation.Patch.Comments) && null != comments) | ||
267 | { | ||
268 | Row commentsRow = patchSummaryInfo.CreateRow(null); | ||
269 | commentsRow[0] = (int)SummaryInformation.Patch.Comments; | ||
270 | commentsRow[1] = comments; | ||
271 | } | ||
272 | |||
273 | // enumerate transforms | ||
274 | Dictionary<string, object> productCodes = new Dictionary<string, object>(); | ||
275 | ArrayList transformNames = new ArrayList(); | ||
276 | ArrayList validTransform = new ArrayList(); | ||
277 | int transformCount = 0; | ||
278 | foreach (PatchTransform mainTransform in transforms) | ||
279 | { | ||
280 | string baseline = null; | ||
281 | int media = -1; | ||
282 | int validationFlags = 0; | ||
283 | |||
284 | if (baselineMedia.Contains(mainTransform.Baseline)) | ||
285 | { | ||
286 | int[] baselineData = (int[])baselineMedia[mainTransform.Baseline]; | ||
287 | int newMedia = baselineData[0]; | ||
288 | if (media != -1 && media != newMedia) | ||
289 | { | ||
290 | throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_TransformAuthoredIntoMultipleMedia, media, newMedia)); | ||
291 | } | ||
292 | baseline = mainTransform.Baseline; | ||
293 | media = newMedia; | ||
294 | validationFlags = baselineData[1]; | ||
295 | } | ||
296 | |||
297 | if (media == -1) | ||
298 | { | ||
299 | // transform's baseline not attached to any Media | ||
300 | continue; | ||
301 | } | ||
302 | |||
303 | Table patchRefTable = patch.Tables["WixPatchRef"]; | ||
304 | if (patchRefTable != null && patchRefTable.Rows.Count > 0) | ||
305 | { | ||
306 | if (!Patch.ReduceTransform(mainTransform.Transform, patchRefTable)) | ||
307 | { | ||
308 | // transform has none of the content authored into this patch | ||
309 | continue; | ||
310 | } | ||
311 | } | ||
312 | |||
313 | // Validate the transform doesn't break any patch specific rules. | ||
314 | mainTransform.Validate(); | ||
315 | |||
316 | // ensure consistent File.Sequence within each Media | ||
317 | MediaRow mediaRow = (MediaRow)mediaRows[media]; | ||
318 | |||
319 | // Ensure that files are sequenced after the last file in any transform. | ||
320 | Table transformMediaTable = mainTransform.Transform.Tables["Media"]; | ||
321 | if (null != transformMediaTable && 0 < transformMediaTable.Rows.Count) | ||
322 | { | ||
323 | foreach (MediaRow transformMediaRow in transformMediaTable.Rows) | ||
324 | { | ||
325 | if (mediaRow.LastSequence < transformMediaRow.LastSequence) | ||
326 | { | ||
327 | // The Binder will pre-increment the sequence. | ||
328 | mediaRow.LastSequence = transformMediaRow.LastSequence; | ||
329 | } | ||
330 | } | ||
331 | } | ||
332 | |||
333 | // Use the Media/@DiskId if greater for backward compatibility. | ||
334 | if (mediaRow.LastSequence < mediaRow.DiskId) | ||
335 | { | ||
336 | mediaRow.LastSequence = mediaRow.DiskId; | ||
337 | } | ||
338 | |||
339 | // ignore media table from transform. | ||
340 | mainTransform.Transform.Tables.Remove("Media"); | ||
341 | mainTransform.Transform.Tables.Remove("WixMedia"); | ||
342 | mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); | ||
343 | |||
344 | string productCode; | ||
345 | Output pairedTransform = this.BuildPairedTransform(patchId, clientPatchId, mainTransform.Transform, mediaRow, validationFlags, out productCode); | ||
346 | productCodes[productCode] = null; | ||
347 | DictionaryEntry entry = new DictionaryEntry(); | ||
348 | entry.Key = productCode; | ||
349 | entry.Value = mainTransform.Transform; | ||
350 | validTransform.Add(entry); | ||
351 | |||
352 | // attach these transforms to the patch object | ||
353 | // TODO: is this an acceptable way to auto-generate transform stream names? | ||
354 | string transformName = baseline + "." + (++transformCount).ToString(CultureInfo.InvariantCulture); | ||
355 | patch.SubStorages.Add(new SubStorage(transformName, mainTransform.Transform)); | ||
356 | patch.SubStorages.Add(new SubStorage("#" + transformName, pairedTransform)); | ||
357 | transformNames.Add(":" + transformName); | ||
358 | transformNames.Add(":#" + transformName); | ||
359 | attachedTransform = true; | ||
360 | } | ||
361 | |||
362 | if (!attachedTransform) | ||
363 | { | ||
364 | throw new WixException(WixErrors.PatchWithoutValidTransforms()); | ||
365 | } | ||
366 | |||
367 | // Validate that a patch authored as removable is actually removable | ||
368 | if (metadataTable.Contains("AllowRemoval")) | ||
369 | { | ||
370 | if ("1" == metadataTable["AllowRemoval"].ToString()) | ||
371 | { | ||
372 | ArrayList tables = Patch.GetPatchUninstallBreakingTables(); | ||
373 | bool result = true; | ||
374 | foreach (DictionaryEntry entry in validTransform) | ||
375 | { | ||
376 | result &= this.CheckUninstallableTransform(entry.Key.ToString(), (Output)entry.Value, tables); | ||
377 | } | ||
378 | |||
379 | if (!result) | ||
380 | { | ||
381 | throw new WixException(WixErrors.PatchNotRemovable()); | ||
382 | } | ||
383 | } | ||
384 | } | ||
385 | |||
386 | // Finish filling tables with transform-dependent data. | ||
387 | // Semicolon delimited list of the product codes that can accept the patch. | ||
388 | Table wixPatchTargetTable = patch.Tables["WixPatchTarget"]; | ||
389 | if (null != wixPatchTargetTable) | ||
390 | { | ||
391 | Dictionary<string, object> targets = new Dictionary<string, object>(); | ||
392 | bool replace = true; | ||
393 | foreach (Row wixPatchTargetRow in wixPatchTargetTable.Rows) | ||
394 | { | ||
395 | string target = wixPatchTargetRow[0].ToString(); | ||
396 | if (0 == String.CompareOrdinal("*", target)) | ||
397 | { | ||
398 | replace = false; | ||
399 | } | ||
400 | else | ||
401 | { | ||
402 | targets[target] = null; | ||
403 | } | ||
404 | } | ||
405 | |||
406 | // Replace the target ProductCodes with the authored list. | ||
407 | if (replace) | ||
408 | { | ||
409 | productCodes = targets; | ||
410 | } | ||
411 | else | ||
412 | { | ||
413 | // Copy the authored target ProductCodes into the list. | ||
414 | foreach (string target in targets.Keys) | ||
415 | { | ||
416 | productCodes[target] = null; | ||
417 | } | ||
418 | } | ||
419 | } | ||
420 | |||
421 | string[] uniqueProductCodes = new string[productCodes.Keys.Count]; | ||
422 | productCodes.Keys.CopyTo(uniqueProductCodes, 0); | ||
423 | |||
424 | Row templateRow = patchSummaryInfo.CreateRow(null); | ||
425 | templateRow[0] = (int)SummaryInformation.Patch.ProductCodes; | ||
426 | templateRow[1] = String.Join(";", uniqueProductCodes); | ||
427 | |||
428 | // Semicolon delimited list of transform substorage names in the order they are applied. | ||
429 | Row savedbyRow = patchSummaryInfo.CreateRow(null); | ||
430 | savedbyRow[0] = (int)SummaryInformation.Patch.TransformNames; | ||
431 | savedbyRow[1] = String.Join(";", (string[])transformNames.ToArray(typeof(string))); | ||
432 | |||
433 | // inspect the patch and filtered transforms | ||
434 | foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) | ||
435 | { | ||
436 | inspectorExtension.Core = inspectorCore; | ||
437 | inspectorExtension.InspectOutput(this.patch); | ||
438 | |||
439 | // reset | ||
440 | inspectorExtension.Core = null; | ||
441 | } | ||
442 | } | ||
443 | |||
444 | /// <summary> | ||
445 | /// Ensure transform is uninstallable. | ||
446 | /// </summary> | ||
447 | /// <param name="productCode">Product code in transform.</param> | ||
448 | /// <param name="transform">Transform generated by torch.</param> | ||
449 | /// <param name="tables">Tables to be checked</param> | ||
450 | /// <returns>True if the transform is uninstallable</returns> | ||
451 | private bool CheckUninstallableTransform(string productCode, Output transform, ArrayList tables) | ||
452 | { | ||
453 | bool ret = true; | ||
454 | foreach (string table in tables) | ||
455 | { | ||
456 | Table wixTable = transform.Tables[table]; | ||
457 | if (null != wixTable) | ||
458 | { | ||
459 | foreach (Row row in wixTable.Rows) | ||
460 | { | ||
461 | if (row.Operation == RowOperation.Add) | ||
462 | { | ||
463 | ret = false; | ||
464 | string primaryKey = row.GetPrimaryKey('/'); | ||
465 | if (null == primaryKey) | ||
466 | { | ||
467 | primaryKey = string.Empty; | ||
468 | } | ||
469 | this.OnMessage(WixErrors.NewRowAddedInTable(row.SourceLineNumbers, productCode, wixTable.Name, primaryKey)); | ||
470 | } | ||
471 | } | ||
472 | } | ||
473 | } | ||
474 | |||
475 | return ret; | ||
476 | } | ||
477 | |||
478 | /// <summary> | ||
479 | /// Tables affect patch uninstall. | ||
480 | /// </summary> | ||
481 | /// <returns>list of tables to be checked</returns> | ||
482 | private static ArrayList GetPatchUninstallBreakingTables() | ||
483 | { | ||
484 | ArrayList tables = new ArrayList(); | ||
485 | tables.Add("AppId"); | ||
486 | tables.Add("BindImage"); | ||
487 | tables.Add("Class"); | ||
488 | tables.Add("Complus"); | ||
489 | tables.Add("CreateFolder"); | ||
490 | tables.Add("DuplicateFile"); | ||
491 | tables.Add("Environment"); | ||
492 | tables.Add("Extension"); | ||
493 | tables.Add("Font"); | ||
494 | tables.Add("IniFile"); | ||
495 | tables.Add("IsolatedComponent"); | ||
496 | tables.Add("LockPermissions"); | ||
497 | tables.Add("MIME"); | ||
498 | tables.Add("MoveFile"); | ||
499 | tables.Add("MsiLockPermissionsEx"); | ||
500 | tables.Add("MsiServiceConfig"); | ||
501 | tables.Add("MsiServiceConfigFailureActions"); | ||
502 | tables.Add("ODBCAttribute"); | ||
503 | tables.Add("ODBCDataSource"); | ||
504 | tables.Add("ODBCDriver"); | ||
505 | tables.Add("ODBCSourceAttribute"); | ||
506 | tables.Add("ODBCTranslator"); | ||
507 | tables.Add("ProgId"); | ||
508 | tables.Add("PublishComponent"); | ||
509 | tables.Add("RemoveIniFile"); | ||
510 | tables.Add("SelfReg"); | ||
511 | tables.Add("ServiceControl"); | ||
512 | tables.Add("ServiceInstall"); | ||
513 | tables.Add("TypeLib"); | ||
514 | tables.Add("Verb"); | ||
515 | |||
516 | return tables; | ||
517 | } | ||
518 | |||
519 | /// <summary> | ||
520 | /// Reduce the transform according to the patch references. | ||
521 | /// </summary> | ||
522 | /// <param name="transform">transform generated by torch.</param> | ||
523 | /// <param name="patchRefTable">Table contains patch family filter.</param> | ||
524 | /// <returns>true if the transform is not empty</returns> | ||
525 | public static bool ReduceTransform(Output transform, Table patchRefTable) | ||
526 | { | ||
527 | // identify sections to keep | ||
528 | Hashtable oldSections = new Hashtable(patchRefTable.Rows.Count); | ||
529 | Hashtable newSections = new Hashtable(patchRefTable.Rows.Count); | ||
530 | Hashtable tableKeyRows = new Hashtable(); | ||
531 | ArrayList sequenceList = new ArrayList(); | ||
532 | Hashtable componentFeatureAddsIndex = new Hashtable(); | ||
533 | Hashtable customActionTable = new Hashtable(); | ||
534 | Hashtable directoryTableAdds = new Hashtable(); | ||
535 | Hashtable featureTableAdds = new Hashtable(); | ||
536 | Hashtable keptComponents = new Hashtable(); | ||
537 | Hashtable keptDirectories = new Hashtable(); | ||
538 | Hashtable keptFeatures = new Hashtable(); | ||
539 | Hashtable keptLockPermissions = new Hashtable(); | ||
540 | Hashtable keptMsiLockPermissionExs = new Hashtable(); | ||
541 | |||
542 | Dictionary<string, List<string>> componentCreateFolderIndex = new Dictionary<string, List<string>>(); | ||
543 | Dictionary<string, List<Row>> directoryLockPermissionsIndex = new Dictionary<string, List<Row>>(); | ||
544 | Dictionary<string, List<Row>> directoryMsiLockPermissionsExIndex = new Dictionary<string, List<Row>>(); | ||
545 | |||
546 | foreach (Row patchRefRow in patchRefTable.Rows) | ||
547 | { | ||
548 | string tableName = (string)patchRefRow[0]; | ||
549 | string key = (string)patchRefRow[1]; | ||
550 | |||
551 | // Short circuit filtering if all changes should be included. | ||
552 | if ("*" == tableName && "*" == key) | ||
553 | { | ||
554 | Patch.RemoveProductCodeFromTransform(transform); | ||
555 | return true; | ||
556 | } | ||
557 | |||
558 | Table table = transform.Tables[tableName]; | ||
559 | if (table == null) | ||
560 | { | ||
561 | // table not found | ||
562 | continue; | ||
563 | } | ||
564 | |||
565 | // index this table | ||
566 | if (!tableKeyRows.Contains(tableName)) | ||
567 | { | ||
568 | Hashtable newKeyRows = new Hashtable(); | ||
569 | foreach (Row newRow in table.Rows) | ||
570 | { | ||
571 | newKeyRows[newRow.GetPrimaryKey('/')] = newRow; | ||
572 | } | ||
573 | tableKeyRows[tableName] = newKeyRows; | ||
574 | } | ||
575 | Hashtable keyRows = (Hashtable)tableKeyRows[tableName]; | ||
576 | |||
577 | Row row = (Row)keyRows[key]; | ||
578 | if (row == null) | ||
579 | { | ||
580 | // row not found | ||
581 | continue; | ||
582 | } | ||
583 | |||
584 | // Differ.sectionDelimiter | ||
585 | string[] sections = row.SectionId.Split('/'); | ||
586 | oldSections[sections[0]] = row; | ||
587 | newSections[sections[1]] = row; | ||
588 | } | ||
589 | |||
590 | // throw away sections not referenced | ||
591 | int keptRows = 0; | ||
592 | Table directoryTable = null; | ||
593 | Table featureTable = null; | ||
594 | Table lockPermissionsTable = null; | ||
595 | Table msiLockPermissionsTable = null; | ||
596 | |||
597 | foreach (Table table in transform.Tables) | ||
598 | { | ||
599 | if ("_SummaryInformation" == table.Name) | ||
600 | { | ||
601 | continue; | ||
602 | } | ||
603 | |||
604 | if (table.Name == "AdminExecuteSequence" | ||
605 | || table.Name == "AdminUISequence" | ||
606 | || table.Name == "AdvtExecuteSequence" | ||
607 | || table.Name == "InstallUISequence" | ||
608 | || table.Name == "InstallExecuteSequence") | ||
609 | { | ||
610 | sequenceList.Add(table); | ||
611 | continue; | ||
612 | } | ||
613 | |||
614 | for (int i = 0; i < table.Rows.Count; i++) | ||
615 | { | ||
616 | Row row = table.Rows[i]; | ||
617 | |||
618 | if (table.Name == "CreateFolder") | ||
619 | { | ||
620 | string createFolderComponentId = (string)row[1]; | ||
621 | |||
622 | List<string> directoryList; | ||
623 | if (!componentCreateFolderIndex.TryGetValue(createFolderComponentId, out directoryList)) | ||
624 | { | ||
625 | directoryList = new List<string>(); | ||
626 | componentCreateFolderIndex.Add(createFolderComponentId, directoryList); | ||
627 | } | ||
628 | |||
629 | directoryList.Add((string)row[0]); | ||
630 | } | ||
631 | |||
632 | if (table.Name == "CustomAction") | ||
633 | { | ||
634 | customActionTable.Add(row[0], row); | ||
635 | } | ||
636 | |||
637 | if (table.Name == "Directory") | ||
638 | { | ||
639 | directoryTable = table; | ||
640 | if (RowOperation.Add == row.Operation) | ||
641 | { | ||
642 | directoryTableAdds.Add(row[0], row); | ||
643 | } | ||
644 | } | ||
645 | |||
646 | if (table.Name == "Feature") | ||
647 | { | ||
648 | featureTable = table; | ||
649 | if (RowOperation.Add == row.Operation) | ||
650 | { | ||
651 | featureTableAdds.Add(row[0], row); | ||
652 | } | ||
653 | } | ||
654 | |||
655 | if (table.Name == "FeatureComponents") | ||
656 | { | ||
657 | if (RowOperation.Add == row.Operation) | ||
658 | { | ||
659 | string featureId = (string)row[0]; | ||
660 | string componentId = (string)row[1]; | ||
661 | |||
662 | if (componentFeatureAddsIndex.ContainsKey(componentId)) | ||
663 | { | ||
664 | ArrayList featureList = (ArrayList)componentFeatureAddsIndex[componentId]; | ||
665 | featureList.Add(featureId); | ||
666 | } | ||
667 | else | ||
668 | { | ||
669 | ArrayList featureList = new ArrayList(); | ||
670 | componentFeatureAddsIndex.Add(componentId, featureList); | ||
671 | featureList.Add(featureId); | ||
672 | } | ||
673 | } | ||
674 | } | ||
675 | |||
676 | if (table.Name == "LockPermissions") | ||
677 | { | ||
678 | lockPermissionsTable = table; | ||
679 | if ("CreateFolder" == (string)row[1]) | ||
680 | { | ||
681 | string directoryId = (string)row[0]; | ||
682 | |||
683 | List<Row> rowList; | ||
684 | if (!directoryLockPermissionsIndex.TryGetValue(directoryId, out rowList)) | ||
685 | { | ||
686 | rowList = new List<Row>(); | ||
687 | directoryLockPermissionsIndex.Add(directoryId, rowList); | ||
688 | } | ||
689 | |||
690 | rowList.Add(row); | ||
691 | } | ||
692 | } | ||
693 | |||
694 | if (table.Name == "MsiLockPermissionsEx") | ||
695 | { | ||
696 | msiLockPermissionsTable = table; | ||
697 | if ("CreateFolder" == (string)row[1]) | ||
698 | { | ||
699 | string directoryId = (string)row[0]; | ||
700 | |||
701 | List<Row> rowList; | ||
702 | if (!directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out rowList)) | ||
703 | { | ||
704 | rowList = new List<Row>(); | ||
705 | directoryMsiLockPermissionsExIndex.Add(directoryId, rowList); | ||
706 | } | ||
707 | |||
708 | rowList.Add(row); | ||
709 | } | ||
710 | } | ||
711 | |||
712 | if (null == row.SectionId) | ||
713 | { | ||
714 | table.Rows.RemoveAt(i); | ||
715 | i--; | ||
716 | } | ||
717 | else | ||
718 | { | ||
719 | string[] sections = row.SectionId.Split('/'); | ||
720 | // ignore the row without section id. | ||
721 | if (0 == sections[0].Length && 0 == sections[1].Length) | ||
722 | { | ||
723 | table.Rows.RemoveAt(i); | ||
724 | i--; | ||
725 | } | ||
726 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
727 | { | ||
728 | if ("Component" == table.Name) | ||
729 | { | ||
730 | keptComponents.Add((string)row[0], row); | ||
731 | } | ||
732 | |||
733 | if ("Directory" == table.Name) | ||
734 | { | ||
735 | keptDirectories.Add(row[0], row); | ||
736 | } | ||
737 | |||
738 | if ("Feature" == table.Name) | ||
739 | { | ||
740 | keptFeatures.Add(row[0], row); | ||
741 | } | ||
742 | |||
743 | keptRows++; | ||
744 | } | ||
745 | else | ||
746 | { | ||
747 | table.Rows.RemoveAt(i); | ||
748 | i--; | ||
749 | } | ||
750 | } | ||
751 | } | ||
752 | } | ||
753 | |||
754 | keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); | ||
755 | |||
756 | if (null != directoryTable) | ||
757 | { | ||
758 | foreach (Row componentRow in keptComponents.Values) | ||
759 | { | ||
760 | string componentId = (string)componentRow[0]; | ||
761 | |||
762 | if (RowOperation.Add == componentRow.Operation) | ||
763 | { | ||
764 | // make sure each added component has its required directory and feature heirarchy. | ||
765 | string directoryId = (string)componentRow[2]; | ||
766 | while (null != directoryId && directoryTableAdds.ContainsKey(directoryId)) | ||
767 | { | ||
768 | Row directoryRow = (Row)directoryTableAdds[directoryId]; | ||
769 | |||
770 | if (!keptDirectories.ContainsKey(directoryId)) | ||
771 | { | ||
772 | directoryTable.Rows.Add(directoryRow); | ||
773 | keptDirectories.Add(directoryRow[0], null); | ||
774 | keptRows++; | ||
775 | } | ||
776 | |||
777 | directoryId = (string)directoryRow[1]; | ||
778 | } | ||
779 | |||
780 | if (componentFeatureAddsIndex.ContainsKey(componentId)) | ||
781 | { | ||
782 | foreach (string featureId in (ArrayList)componentFeatureAddsIndex[componentId]) | ||
783 | { | ||
784 | string currentFeatureId = featureId; | ||
785 | while (null != currentFeatureId && featureTableAdds.ContainsKey(currentFeatureId)) | ||
786 | { | ||
787 | Row featureRow = (Row)featureTableAdds[currentFeatureId]; | ||
788 | |||
789 | if (!keptFeatures.ContainsKey(currentFeatureId)) | ||
790 | { | ||
791 | featureTable.Rows.Add(featureRow); | ||
792 | keptFeatures.Add(featureRow[0], null); | ||
793 | keptRows++; | ||
794 | } | ||
795 | |||
796 | currentFeatureId = (string)featureRow[1]; | ||
797 | } | ||
798 | } | ||
799 | } | ||
800 | } | ||
801 | |||
802 | // Hook in changes LockPermissions and MsiLockPermissions for folders for each component that has been kept. | ||
803 | foreach (string keptComponentId in keptComponents.Keys) | ||
804 | { | ||
805 | List<string> directoryList; | ||
806 | if (componentCreateFolderIndex.TryGetValue(keptComponentId, out directoryList)) | ||
807 | { | ||
808 | foreach (string directoryId in directoryList) | ||
809 | { | ||
810 | List<Row> lockPermissionsRowList; | ||
811 | if (directoryLockPermissionsIndex.TryGetValue(directoryId, out lockPermissionsRowList)) | ||
812 | { | ||
813 | foreach (Row lockPermissionsRow in lockPermissionsRowList) | ||
814 | { | ||
815 | string key = lockPermissionsRow.GetPrimaryKey('/'); | ||
816 | if (!keptLockPermissions.ContainsKey(key)) | ||
817 | { | ||
818 | lockPermissionsTable.Rows.Add(lockPermissionsRow); | ||
819 | keptLockPermissions.Add(key, null); | ||
820 | keptRows++; | ||
821 | } | ||
822 | } | ||
823 | } | ||
824 | |||
825 | List<Row> msiLockPermissionsExRowList; | ||
826 | if (directoryMsiLockPermissionsExIndex.TryGetValue(directoryId, out msiLockPermissionsExRowList)) | ||
827 | { | ||
828 | foreach (Row msiLockPermissionsExRow in msiLockPermissionsExRowList) | ||
829 | { | ||
830 | string key = msiLockPermissionsExRow.GetPrimaryKey('/'); | ||
831 | if (!keptMsiLockPermissionExs.ContainsKey(key)) | ||
832 | { | ||
833 | msiLockPermissionsTable.Rows.Add(msiLockPermissionsExRow); | ||
834 | keptMsiLockPermissionExs.Add(key, null); | ||
835 | keptRows++; | ||
836 | } | ||
837 | } | ||
838 | } | ||
839 | } | ||
840 | } | ||
841 | } | ||
842 | } | ||
843 | } | ||
844 | |||
845 | keptRows += ReduceTransformSequenceTable(sequenceList, oldSections, newSections, customActionTable); | ||
846 | |||
847 | // Delete tables that are empty. | ||
848 | ArrayList tablesToDelete = new ArrayList(); | ||
849 | foreach (Table table in transform.Tables) | ||
850 | { | ||
851 | if (0 == table.Rows.Count) | ||
852 | { | ||
853 | tablesToDelete.Add(table.Name); | ||
854 | } | ||
855 | } | ||
856 | |||
857 | // delete separately to avoid messing up enumeration | ||
858 | foreach (string tableName in tablesToDelete) | ||
859 | { | ||
860 | transform.Tables.Remove(tableName); | ||
861 | } | ||
862 | |||
863 | return keptRows > 0; | ||
864 | } | ||
865 | |||
866 | /// <summary> | ||
867 | /// Remove the ProductCode property from the transform. | ||
868 | /// </summary> | ||
869 | /// <param name="transform">The transform.</param> | ||
870 | /// <remarks> | ||
871 | /// Changing the ProductCode is not supported in a patch. | ||
872 | /// </remarks> | ||
873 | private static void RemoveProductCodeFromTransform(Output transform) | ||
874 | { | ||
875 | Table propertyTable = transform.Tables["Property"]; | ||
876 | if (null != propertyTable) | ||
877 | { | ||
878 | for (int i = 0; i < propertyTable.Rows.Count; ++i) | ||
879 | { | ||
880 | Row propertyRow = propertyTable.Rows[i]; | ||
881 | string property = (string)propertyRow[0]; | ||
882 | |||
883 | if ("ProductCode" == property) | ||
884 | { | ||
885 | propertyTable.Rows.RemoveAt(i); | ||
886 | break; | ||
887 | } | ||
888 | } | ||
889 | } | ||
890 | } | ||
891 | |||
892 | /// <summary> | ||
893 | /// Check if the section is in a PatchFamily. | ||
894 | /// </summary> | ||
895 | /// <param name="oldSection">Section id in target wixout</param> | ||
896 | /// <param name="newSection">Section id in upgrade wixout</param> | ||
897 | /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param> | ||
898 | /// <param name="newSections">Hashtable contains section id should be kept in the upgrade wixout.</param> | ||
899 | /// <returns>true if section in patch family</returns> | ||
900 | private static bool IsInPatchFamily(string oldSection, string newSection, Hashtable oldSections, Hashtable newSections) | ||
901 | { | ||
902 | bool result = false; | ||
903 | |||
904 | if ((String.IsNullOrEmpty(oldSection) && newSections.Contains(newSection)) || (String.IsNullOrEmpty(newSection) && oldSections.Contains(oldSection))) | ||
905 | { | ||
906 | result = true; | ||
907 | } | ||
908 | else if (!String.IsNullOrEmpty(oldSection) && !String.IsNullOrEmpty(newSection) && (oldSections.Contains(oldSection) || newSections.Contains(newSection))) | ||
909 | { | ||
910 | result = true; | ||
911 | } | ||
912 | |||
913 | return result; | ||
914 | } | ||
915 | |||
916 | /// <summary> | ||
917 | /// Reduce the transform sequence tables. | ||
918 | /// </summary> | ||
919 | /// <param name="sequenceList">ArrayList of tables to be reduced</param> | ||
920 | /// <param name="oldSections">Hashtable contains section id should be kept in the baseline wixout.</param> | ||
921 | /// <param name="newSections">Hashtable contains section id should be kept in the target wixout.</param> | ||
922 | /// <param name="customAction">Hashtable contains all the rows in the CustomAction table.</param> | ||
923 | /// <returns>Number of rows left</returns> | ||
924 | private static int ReduceTransformSequenceTable(ArrayList sequenceList, Hashtable oldSections, Hashtable newSections, Hashtable customAction) | ||
925 | { | ||
926 | int keptRows = 0; | ||
927 | |||
928 | foreach (Table currentTable in sequenceList) | ||
929 | { | ||
930 | for (int i = 0; i < currentTable.Rows.Count; i++) | ||
931 | { | ||
932 | Row row = currentTable.Rows[i]; | ||
933 | string actionName = row.Fields[0].Data.ToString(); | ||
934 | string[] sections = row.SectionId.Split('/'); | ||
935 | bool isSectionIdEmpty = (sections[0].Length == 0 && sections[1].Length == 0); | ||
936 | |||
937 | if (row.Operation == RowOperation.None) | ||
938 | { | ||
939 | // ignore the rows without section id. | ||
940 | if (isSectionIdEmpty) | ||
941 | { | ||
942 | currentTable.Rows.RemoveAt(i); | ||
943 | i--; | ||
944 | } | ||
945 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
946 | { | ||
947 | keptRows++; | ||
948 | } | ||
949 | else | ||
950 | { | ||
951 | currentTable.Rows.RemoveAt(i); | ||
952 | i--; | ||
953 | } | ||
954 | } | ||
955 | else if (row.Operation == RowOperation.Modify) | ||
956 | { | ||
957 | bool sequenceChanged = row.Fields[2].Modified; | ||
958 | bool conditionChanged = row.Fields[1].Modified; | ||
959 | |||
960 | if (sequenceChanged && !conditionChanged) | ||
961 | { | ||
962 | keptRows++; | ||
963 | } | ||
964 | else if (!sequenceChanged && conditionChanged) | ||
965 | { | ||
966 | if (isSectionIdEmpty) | ||
967 | { | ||
968 | currentTable.Rows.RemoveAt(i); | ||
969 | i--; | ||
970 | } | ||
971 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
972 | { | ||
973 | keptRows++; | ||
974 | } | ||
975 | else | ||
976 | { | ||
977 | currentTable.Rows.RemoveAt(i); | ||
978 | i--; | ||
979 | } | ||
980 | } | ||
981 | else if (sequenceChanged && conditionChanged) | ||
982 | { | ||
983 | if (isSectionIdEmpty) | ||
984 | { | ||
985 | row.Fields[1].Modified = false; | ||
986 | keptRows++; | ||
987 | } | ||
988 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
989 | { | ||
990 | keptRows++; | ||
991 | } | ||
992 | else | ||
993 | { | ||
994 | row.Fields[1].Modified = false; | ||
995 | keptRows++; | ||
996 | } | ||
997 | } | ||
998 | } | ||
999 | else if (row.Operation == RowOperation.Delete) | ||
1000 | { | ||
1001 | if (isSectionIdEmpty) | ||
1002 | { | ||
1003 | // it is a stardard action which is added by wix, we should keep this action. | ||
1004 | row.Operation = RowOperation.None; | ||
1005 | keptRows++; | ||
1006 | } | ||
1007 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
1008 | { | ||
1009 | keptRows++; | ||
1010 | } | ||
1011 | else | ||
1012 | { | ||
1013 | if (customAction.ContainsKey(actionName)) | ||
1014 | { | ||
1015 | currentTable.Rows.RemoveAt(i); | ||
1016 | i--; | ||
1017 | } | ||
1018 | else | ||
1019 | { | ||
1020 | // it is a stardard action, we should keep this action. | ||
1021 | row.Operation = RowOperation.None; | ||
1022 | keptRows++; | ||
1023 | } | ||
1024 | } | ||
1025 | } | ||
1026 | else if (row.Operation == RowOperation.Add) | ||
1027 | { | ||
1028 | if (isSectionIdEmpty) | ||
1029 | { | ||
1030 | keptRows++; | ||
1031 | } | ||
1032 | else if (IsInPatchFamily(sections[0], sections[1], oldSections, newSections)) | ||
1033 | { | ||
1034 | keptRows++; | ||
1035 | } | ||
1036 | else | ||
1037 | { | ||
1038 | if (customAction.ContainsKey(actionName)) | ||
1039 | { | ||
1040 | currentTable.Rows.RemoveAt(i); | ||
1041 | i--; | ||
1042 | } | ||
1043 | else | ||
1044 | { | ||
1045 | keptRows++; | ||
1046 | } | ||
1047 | } | ||
1048 | } | ||
1049 | } | ||
1050 | } | ||
1051 | |||
1052 | return keptRows; | ||
1053 | } | ||
1054 | |||
1055 | /// <summary> | ||
1056 | /// Create the #transform for the given main transform. | ||
1057 | /// </summary> | ||
1058 | /// <param name="patchId">Patch GUID from patch authoring.</param> | ||
1059 | /// <param name="clientPatchId">Easily referenced identity for this patch.</param> | ||
1060 | /// <param name="mainTransform">Transform generated by torch.</param> | ||
1061 | /// <param name="mediaRow">Media authored into patch.</param> | ||
1062 | /// <param name="validationFlags">Transform validation flags for the summary information stream.</param> | ||
1063 | /// <param name="productCode">Output string to receive ProductCode.</param> | ||
1064 | [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.InvalidOperationException.#ctor(System.String)")] | ||
1065 | public Output BuildPairedTransform(string patchId, string clientPatchId, Output mainTransform, MediaRow mediaRow, int validationFlags, out string productCode) | ||
1066 | { | ||
1067 | productCode = null; | ||
1068 | Output pairedTransform = new Output(null); | ||
1069 | pairedTransform.Type = OutputType.Transform; | ||
1070 | pairedTransform.Codepage = mainTransform.Codepage; | ||
1071 | |||
1072 | // lookup productVersion property to correct summaryInformation | ||
1073 | string newProductVersion = null; | ||
1074 | Table mainPropertyTable = mainTransform.Tables["Property"]; | ||
1075 | if (null != mainPropertyTable) | ||
1076 | { | ||
1077 | foreach (Row row in mainPropertyTable.Rows) | ||
1078 | { | ||
1079 | if ("ProductVersion" == (string)row[0]) | ||
1080 | { | ||
1081 | newProductVersion = (string)row[1]; | ||
1082 | } | ||
1083 | } | ||
1084 | } | ||
1085 | |||
1086 | // TODO: build class for manipulating SummaryInformation table | ||
1087 | Table mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; | ||
1088 | // add required properties | ||
1089 | Hashtable mainSummaryRows = new Hashtable(); | ||
1090 | foreach (Row mainSummaryRow in mainSummaryTable.Rows) | ||
1091 | { | ||
1092 | mainSummaryRows[mainSummaryRow[0]] = mainSummaryRow; | ||
1093 | } | ||
1094 | if (!mainSummaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) | ||
1095 | { | ||
1096 | Row mainSummaryRow = mainSummaryTable.CreateRow(null); | ||
1097 | mainSummaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; | ||
1098 | mainSummaryRow[1] = validationFlags.ToString(CultureInfo.InvariantCulture); | ||
1099 | } | ||
1100 | |||
1101 | // copy summary information from core transform | ||
1102 | Table pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); | ||
1103 | foreach (Row mainSummaryRow in mainSummaryTable.Rows) | ||
1104 | { | ||
1105 | string value = (string)mainSummaryRow[1]; | ||
1106 | switch ((SummaryInformation.Transform)mainSummaryRow[0]) | ||
1107 | { | ||
1108 | case SummaryInformation.Transform.ProductCodes: | ||
1109 | string[] propertyData = value.Split(';'); | ||
1110 | string oldProductVersion = propertyData[0].Substring(38); | ||
1111 | string upgradeCode = propertyData[2]; | ||
1112 | productCode = propertyData[0].Substring(0, 38); | ||
1113 | if (newProductVersion == null) | ||
1114 | { | ||
1115 | newProductVersion = oldProductVersion; | ||
1116 | } | ||
1117 | |||
1118 | // force mainTranform to old;new;upgrade and pairedTransform to new;new;upgrade | ||
1119 | mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); | ||
1120 | value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); | ||
1121 | break; | ||
1122 | case SummaryInformation.Transform.ValidationFlags: | ||
1123 | // use validation flags authored into the patch XML | ||
1124 | mainSummaryRow[1] = value = validationFlags.ToString(CultureInfo.InvariantCulture); | ||
1125 | break; | ||
1126 | } | ||
1127 | Row pairedSummaryRow = pairedSummaryTable.CreateRow(null); | ||
1128 | pairedSummaryRow[0] = mainSummaryRow[0]; | ||
1129 | pairedSummaryRow[1] = value; | ||
1130 | } | ||
1131 | |||
1132 | if (productCode == null) | ||
1133 | { | ||
1134 | throw new InvalidOperationException(WixStrings.EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo); | ||
1135 | } | ||
1136 | |||
1137 | // copy File table | ||
1138 | Table mainFileTable = mainTransform.Tables["File"]; | ||
1139 | if (null != mainFileTable && 0 < mainFileTable.Rows.Count) | ||
1140 | { | ||
1141 | // We require file source information. | ||
1142 | Table mainWixFileTable = mainTransform.Tables["WixFile"]; | ||
1143 | if (null == mainWixFileTable) | ||
1144 | { | ||
1145 | throw new WixException(WixErrors.AdminImageRequired(productCode)); | ||
1146 | } | ||
1147 | |||
1148 | RowDictionary<FileRow> mainFileRows = new RowDictionary<FileRow>(mainFileTable); | ||
1149 | |||
1150 | Table pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); | ||
1151 | foreach (WixFileRow mainWixFileRow in mainWixFileTable.Rows) | ||
1152 | { | ||
1153 | FileRow mainFileRow = mainFileRows[mainWixFileRow.File]; | ||
1154 | |||
1155 | // set File.Sequence to non null to satisfy transform bind | ||
1156 | mainFileRow.Sequence = 1; | ||
1157 | |||
1158 | // delete's don't need rows in the paired transform | ||
1159 | if (mainFileRow.Operation == RowOperation.Delete) | ||
1160 | { | ||
1161 | continue; | ||
1162 | } | ||
1163 | |||
1164 | FileRow pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); | ||
1165 | pairedFileRow.Operation = RowOperation.Modify; | ||
1166 | for (int i = 0; i < mainFileRow.Fields.Length; i++) | ||
1167 | { | ||
1168 | pairedFileRow[i] = mainFileRow[i]; | ||
1169 | } | ||
1170 | |||
1171 | // override authored media for patch bind | ||
1172 | mainWixFileRow.DiskId = mediaRow.DiskId; | ||
1173 | |||
1174 | // suppress any change to File.Sequence to avoid bloat | ||
1175 | mainFileRow.Fields[7].Modified = false; | ||
1176 | |||
1177 | // force File row to appear in the transform | ||
1178 | switch (mainFileRow.Operation) | ||
1179 | { | ||
1180 | case RowOperation.Modify: | ||
1181 | case RowOperation.Add: | ||
1182 | // set msidbFileAttributesPatchAdded | ||
1183 | pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; | ||
1184 | pairedFileRow.Fields[6].Modified = true; | ||
1185 | pairedFileRow.Operation = mainFileRow.Operation; | ||
1186 | break; | ||
1187 | default: | ||
1188 | pairedFileRow.Fields[6].Modified = false; | ||
1189 | break; | ||
1190 | } | ||
1191 | } | ||
1192 | } | ||
1193 | |||
1194 | // add Media row to pairedTransform | ||
1195 | Table pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); | ||
1196 | Row pairedMediaRow = pairedMediaTable.CreateRow(null); | ||
1197 | pairedMediaRow.Operation = RowOperation.Add; | ||
1198 | for (int i = 0; i < mediaRow.Fields.Length; i++) | ||
1199 | { | ||
1200 | pairedMediaRow[i] = mediaRow[i]; | ||
1201 | } | ||
1202 | |||
1203 | // add PatchPackage for this Media | ||
1204 | Table pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); | ||
1205 | pairedPackageTable.Operation = TableOperation.Add; | ||
1206 | Row pairedPackageRow = pairedPackageTable.CreateRow(null); | ||
1207 | pairedPackageRow.Operation = RowOperation.Add; | ||
1208 | pairedPackageRow[0] = patchId; | ||
1209 | pairedPackageRow[1] = mediaRow.DiskId; | ||
1210 | |||
1211 | // add property to both identify client patches and whether those patches are removable or not | ||
1212 | int allowRemoval = 0; | ||
1213 | Table msiPatchMetadataTable = this.patch.Tables["MsiPatchMetadata"]; | ||
1214 | if (null != msiPatchMetadataTable) | ||
1215 | { | ||
1216 | foreach (Row msiPatchMetadataRow in msiPatchMetadataTable.Rows) | ||
1217 | { | ||
1218 | // get the value of the standard AllowRemoval property, if present | ||
1219 | string company = (string)msiPatchMetadataRow[0]; | ||
1220 | if ((null == company || 0 == company.Length) && "AllowRemoval" == (string)msiPatchMetadataRow[1]) | ||
1221 | { | ||
1222 | allowRemoval = Int32.Parse((string)msiPatchMetadataRow[2], CultureInfo.InvariantCulture); | ||
1223 | } | ||
1224 | } | ||
1225 | } | ||
1226 | |||
1227 | // add the property to the patch transform's Property table | ||
1228 | Table pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); | ||
1229 | pairedPropertyTable.Operation = TableOperation.Add; | ||
1230 | Row pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1231 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1232 | pairedPropertyRow[0] = string.Concat(clientPatchId, ".AllowRemoval"); | ||
1233 | pairedPropertyRow[1] = allowRemoval.ToString(CultureInfo.InvariantCulture); | ||
1234 | |||
1235 | // add this patch code GUID to the patch transform to identify | ||
1236 | // which patches are installed, including in multi-patch | ||
1237 | // installations. | ||
1238 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1239 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1240 | pairedPropertyRow[0] = string.Concat(clientPatchId, ".PatchCode"); | ||
1241 | pairedPropertyRow[1] = patchId; | ||
1242 | |||
1243 | // add PATCHNEWPACKAGECODE to apply to admin layouts | ||
1244 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1245 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1246 | pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; | ||
1247 | pairedPropertyRow[1] = patchId; | ||
1248 | |||
1249 | // add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts | ||
1250 | Table _summaryInformationTable = this.patch.Tables["_SummaryInformation"]; | ||
1251 | if (null != _summaryInformationTable) | ||
1252 | { | ||
1253 | foreach (Row row in _summaryInformationTable.Rows) | ||
1254 | { | ||
1255 | if (3 == (int)row[0]) // PID_SUBJECT | ||
1256 | { | ||
1257 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1258 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1259 | pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; | ||
1260 | pairedPropertyRow[1] = row[1]; | ||
1261 | } | ||
1262 | else if (6 == (int)row[0]) // PID_COMMENTS | ||
1263 | { | ||
1264 | pairedPropertyRow = pairedPropertyTable.CreateRow(null); | ||
1265 | pairedPropertyRow.Operation = RowOperation.Add; | ||
1266 | pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; | ||
1267 | pairedPropertyRow[1] = row[1]; | ||
1268 | } | ||
1269 | } | ||
1270 | } | ||
1271 | |||
1272 | return pairedTransform; | ||
1273 | } | ||
1274 | |||
1275 | /// <summary> | ||
1276 | /// Sends a message to the message delegate if there is one. | ||
1277 | /// </summary> | ||
1278 | /// <param name="mea">Message event arguments.</param> | ||
1279 | public void OnMessage(MessageEventArgs mea) | ||
1280 | { | ||
1281 | Messaging.Instance.OnMessage(mea); | ||
1282 | } | ||
1283 | } | ||
1284 | } | ||
diff --git a/src/WixToolset.Core/PatchAPI/PatchInterop.cs b/src/WixToolset.Core/PatchAPI/PatchInterop.cs deleted file mode 100644 index ce749a33..00000000 --- a/src/WixToolset.Core/PatchAPI/PatchInterop.cs +++ /dev/null | |||
@@ -1,1002 +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.PatchAPI | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Diagnostics.CodeAnalysis; | ||
8 | using System.Globalization; | ||
9 | using System.Runtime.InteropServices; | ||
10 | |||
11 | /// <summary> | ||
12 | /// Interop class for the mspatchc.dll. | ||
13 | /// </summary> | ||
14 | internal static class PatchInterop | ||
15 | { | ||
16 | // From WinError.h in the Platform SDK | ||
17 | internal const ushort FACILITY_WIN32 = 7; | ||
18 | |||
19 | /// <summary> | ||
20 | /// Parse a number from text in either hex or decimal. | ||
21 | /// </summary> | ||
22 | /// <param name="source">Source value. Treated as hex if it starts 0x (or 0X), decimal otherwise.</param> | ||
23 | /// <returns>Numeric value that source represents.</returns> | ||
24 | static internal UInt32 ParseHexOrDecimal(string source) | ||
25 | { | ||
26 | string value = source.Trim(); | ||
27 | if (String.Equals(value.Substring(0,2), "0x", StringComparison.OrdinalIgnoreCase)) | ||
28 | { | ||
29 | return UInt32.Parse(value.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); | ||
30 | } | ||
31 | else | ||
32 | { | ||
33 | return UInt32.Parse(value, CultureInfo.InvariantCulture.NumberFormat); | ||
34 | } | ||
35 | } | ||
36 | |||
37 | /// <summary> | ||
38 | /// Create a binary delta file. | ||
39 | /// </summary> | ||
40 | /// <param name="deltaFile">Name of the delta file to create.</param> | ||
41 | /// <param name="targetFile">Name of updated file.</param> | ||
42 | /// <param name="targetSymbolPath">Optional paths to updated file's symbols.</param> | ||
43 | /// <param name="targetRetainOffsets">Optional offsets to the delta retain sections in the updated file.</param> | ||
44 | /// <param name="basisFiles">Optional array of target files.</param> | ||
45 | /// <param name="basisSymbolPaths">Optional array of target files' symbol paths (must match basisFiles array).</param> | ||
46 | /// <param name="basisIgnoreLengths">Optional array of target files' delta ignore section lengths (must match basisFiles array)(each entry must match basisIgnoreOffsets entries).</param> | ||
47 | /// <param name="basisIgnoreOffsets">Optional array of target files' delta ignore section offsets (must match basisFiles array)(each entry must match basisIgnoreLengths entries).</param> | ||
48 | /// <param name="basisRetainLengths">Optional array of target files' delta protect section lengths (must match basisFiles array)(each entry must match basisRetainOffsets and targetRetainOffsets entries).</param> | ||
49 | /// <param name="basisRetainOffsets">Optional array of target files' delta protect section offsets (must match basisFiles array)(each entry must match basisRetainLengths and targetRetainOffsets entries).</param> | ||
50 | /// <param name="apiPatchingSymbolFlags">ApiPatchingSymbolFlags value.</param> | ||
51 | /// <param name="optimizePatchSizeForLargeFiles">OptimizePatchSizeForLargeFiles value.</param> | ||
52 | /// <param name="retainRangesIgnored">Flag to indicate retain ranges were ignored due to mismatch.</param> | ||
53 | /// <returns>true if delta file was created, false if whole file should be used instead.</returns> | ||
54 | static public bool CreateDelta( | ||
55 | string deltaFile, | ||
56 | string targetFile, | ||
57 | string targetSymbolPath, | ||
58 | string targetRetainOffsets, | ||
59 | string[] basisFiles, | ||
60 | string[] basisSymbolPaths, | ||
61 | string[] basisIgnoreLengths, | ||
62 | string[] basisIgnoreOffsets, | ||
63 | string[] basisRetainLengths, | ||
64 | string[] basisRetainOffsets, | ||
65 | PatchSymbolFlagsType apiPatchingSymbolFlags, | ||
66 | bool optimizePatchSizeForLargeFiles, | ||
67 | out bool retainRangesIgnored | ||
68 | ) | ||
69 | { | ||
70 | retainRangesIgnored = false; | ||
71 | if (0 != (apiPatchingSymbolFlags & ~(PatchSymbolFlagsType.PATCH_SYMBOL_NO_IMAGEHLP | PatchSymbolFlagsType.PATCH_SYMBOL_NO_FAILURES | PatchSymbolFlagsType.PATCH_SYMBOL_UNDECORATED_TOO))) | ||
72 | { | ||
73 | throw new ArgumentOutOfRangeException("apiPatchingSymbolFlags"); | ||
74 | } | ||
75 | |||
76 | if (null == deltaFile || 0 == deltaFile.Length) | ||
77 | { | ||
78 | throw new ArgumentNullException("deltaFile"); | ||
79 | } | ||
80 | |||
81 | if (null == targetFile || 0 == targetFile.Length) | ||
82 | { | ||
83 | throw new ArgumentNullException("targetFile"); | ||
84 | } | ||
85 | |||
86 | if (null == basisFiles || 0 == basisFiles.Length) | ||
87 | { | ||
88 | return false; | ||
89 | } | ||
90 | uint countOldFiles = (uint) basisFiles.Length; | ||
91 | |||
92 | if (null != basisSymbolPaths) | ||
93 | { | ||
94 | if (0 != basisSymbolPaths.Length) | ||
95 | { | ||
96 | if ((uint) basisSymbolPaths.Length != countOldFiles) | ||
97 | { | ||
98 | throw new ArgumentOutOfRangeException("basisSymbolPaths"); | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | // a null basisSymbolPaths is allowed. | ||
103 | |||
104 | if (null != basisIgnoreLengths) | ||
105 | { | ||
106 | if (0 != basisIgnoreLengths.Length) | ||
107 | { | ||
108 | if ((uint) basisIgnoreLengths.Length != countOldFiles) | ||
109 | { | ||
110 | throw new ArgumentOutOfRangeException("basisIgnoreLengths"); | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | else | ||
115 | { | ||
116 | basisIgnoreLengths = new string[countOldFiles]; | ||
117 | } | ||
118 | |||
119 | if (null != basisIgnoreOffsets) | ||
120 | { | ||
121 | if (0 != basisIgnoreOffsets.Length) | ||
122 | { | ||
123 | if ((uint) basisIgnoreOffsets.Length != countOldFiles) | ||
124 | { | ||
125 | throw new ArgumentOutOfRangeException("basisIgnoreOffsets"); | ||
126 | } | ||
127 | } | ||
128 | } | ||
129 | else | ||
130 | { | ||
131 | basisIgnoreOffsets = new string[countOldFiles]; | ||
132 | } | ||
133 | |||
134 | if (null != basisRetainLengths) | ||
135 | { | ||
136 | if (0 != basisRetainLengths.Length) | ||
137 | { | ||
138 | if ((uint) basisRetainLengths.Length != countOldFiles) | ||
139 | { | ||
140 | throw new ArgumentOutOfRangeException("basisRetainLengths"); | ||
141 | } | ||
142 | } | ||
143 | } | ||
144 | else | ||
145 | { | ||
146 | basisRetainLengths = new string[countOldFiles]; | ||
147 | } | ||
148 | |||
149 | if (null != basisRetainOffsets) | ||
150 | { | ||
151 | if (0 != basisRetainOffsets.Length) | ||
152 | { | ||
153 | if ((uint) basisRetainOffsets.Length != countOldFiles) | ||
154 | { | ||
155 | throw new ArgumentOutOfRangeException("basisRetainOffsets"); | ||
156 | } | ||
157 | } | ||
158 | } | ||
159 | else | ||
160 | { | ||
161 | basisRetainOffsets = new string[countOldFiles]; | ||
162 | } | ||
163 | |||
164 | PatchOptionData pod = new PatchOptionData(); | ||
165 | pod.symbolOptionFlags = apiPatchingSymbolFlags; | ||
166 | pod.newFileSymbolPath = targetSymbolPath; | ||
167 | pod.oldFileSymbolPathArray = basisSymbolPaths; | ||
168 | pod.extendedOptionFlags = 0; | ||
169 | PatchOldFileInfoW[] oldFileInfoArray = new PatchOldFileInfoW[countOldFiles]; | ||
170 | string[] newRetainOffsetArray = ((null == targetRetainOffsets) ? new string[0] : targetRetainOffsets.Split(',')); | ||
171 | for (uint i = 0; i < countOldFiles; ++i) | ||
172 | { | ||
173 | PatchOldFileInfoW ofi = new PatchOldFileInfoW(); | ||
174 | ofi.oldFileName = basisFiles[i]; | ||
175 | string[] ignoreLengthArray = ((null == basisIgnoreLengths[i]) ? new string[0] : basisIgnoreLengths[i].Split(',')); | ||
176 | string[] ignoreOffsetArray = ((null == basisIgnoreOffsets[i]) ? new string[0] : basisIgnoreOffsets[i].Split(',')); | ||
177 | string[] retainLengthArray = ((null == basisRetainLengths[i]) ? new string[0] : basisRetainLengths[i].Split(',')); | ||
178 | string[] retainOffsetArray = ((null == basisRetainOffsets[i]) ? new string[0] : basisRetainOffsets[i].Split(',')); | ||
179 | // Validate inputs | ||
180 | if (ignoreLengthArray.Length != ignoreOffsetArray.Length) | ||
181 | { | ||
182 | throw new ArgumentOutOfRangeException("basisIgnoreLengths"); | ||
183 | } | ||
184 | |||
185 | if (retainLengthArray.Length != retainOffsetArray.Length) | ||
186 | { | ||
187 | throw new ArgumentOutOfRangeException("basisRetainLengths"); | ||
188 | } | ||
189 | |||
190 | if (newRetainOffsetArray.Length != retainOffsetArray.Length) | ||
191 | { | ||
192 | // remove all retain range information | ||
193 | retainRangesIgnored = true; | ||
194 | for (uint j = 0; j < countOldFiles; ++j) | ||
195 | { | ||
196 | basisRetainLengths[j] = null; | ||
197 | basisRetainOffsets[j] = null; | ||
198 | } | ||
199 | retainLengthArray = new string[0]; | ||
200 | retainOffsetArray = new string[0]; | ||
201 | newRetainOffsetArray = new string[0]; | ||
202 | for (uint j = 0; j < oldFileInfoArray.Length; ++j) | ||
203 | { | ||
204 | oldFileInfoArray[j].retainRange = null; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | // Populate IgnoreRange structure | ||
209 | PatchIgnoreRange[] ignoreArray = null; | ||
210 | if (0 != ignoreLengthArray.Length) | ||
211 | { | ||
212 | ignoreArray = new PatchIgnoreRange[ignoreLengthArray.Length]; | ||
213 | for (int j = 0; j < ignoreLengthArray.Length; ++j) | ||
214 | { | ||
215 | PatchIgnoreRange ignoreRange = new PatchIgnoreRange(); | ||
216 | ignoreRange.offsetInOldFile = ParseHexOrDecimal(ignoreOffsetArray[j]); | ||
217 | ignoreRange.lengthInBytes = ParseHexOrDecimal(ignoreLengthArray[j]); | ||
218 | ignoreArray[j] = ignoreRange; | ||
219 | } | ||
220 | ofi.ignoreRange = ignoreArray; | ||
221 | } | ||
222 | |||
223 | PatchRetainRange[] retainArray = null; | ||
224 | if (0 != newRetainOffsetArray.Length) | ||
225 | { | ||
226 | retainArray = new PatchRetainRange[retainLengthArray.Length]; | ||
227 | for (int j = 0; j < newRetainOffsetArray.Length; ++j) | ||
228 | { | ||
229 | PatchRetainRange retainRange = new PatchRetainRange(); | ||
230 | retainRange.offsetInOldFile = ParseHexOrDecimal(retainOffsetArray[j]); | ||
231 | retainRange.lengthInBytes = ParseHexOrDecimal(retainLengthArray[j]); | ||
232 | retainRange.offsetInNewFile = ParseHexOrDecimal(newRetainOffsetArray[j]); | ||
233 | retainArray[j] = retainRange; | ||
234 | } | ||
235 | ofi.retainRange = retainArray; | ||
236 | } | ||
237 | oldFileInfoArray[i] = ofi; | ||
238 | } | ||
239 | |||
240 | if (CreatePatchFileExW( | ||
241 | countOldFiles, | ||
242 | oldFileInfoArray, | ||
243 | targetFile, | ||
244 | deltaFile, | ||
245 | PatchOptionFlags(optimizePatchSizeForLargeFiles), | ||
246 | pod, | ||
247 | null, | ||
248 | IntPtr.Zero)) | ||
249 | { | ||
250 | return true; | ||
251 | } | ||
252 | |||
253 | // determine if this is an error or a need to use whole file. | ||
254 | int err = Marshal.GetLastWin32Error(); | ||
255 | switch(err) | ||
256 | { | ||
257 | case unchecked((int) ERROR_PATCH_BIGGER_THAN_COMPRESSED): | ||
258 | break; | ||
259 | |||
260 | // too late to exclude this file -- should have been caught before | ||
261 | case unchecked((int) ERROR_PATCH_SAME_FILE): | ||
262 | default: | ||
263 | throw new System.ComponentModel.Win32Exception(err); | ||
264 | } | ||
265 | return false; | ||
266 | } | ||
267 | |||
268 | /// <summary> | ||
269 | /// Extract the delta header. | ||
270 | /// </summary> | ||
271 | /// <param name="delta">Name of delta file.</param> | ||
272 | /// <param name="deltaHeader">Name of file to create with the delta's header.</param> | ||
273 | static public void ExtractDeltaHeader(string delta, string deltaHeader) | ||
274 | { | ||
275 | if (!ExtractPatchHeaderToFileW(delta, deltaHeader)) | ||
276 | { | ||
277 | throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | /// <summary> | ||
282 | /// Returns the PatchOptionFlags to use. | ||
283 | /// </summary> | ||
284 | /// <param name="optimizeForLargeFiles">True if optimizing for large files.</param> | ||
285 | /// <returns>PATCH_OPTION_FLAG values</returns> | ||
286 | static private UInt32 PatchOptionFlags(bool optimizeForLargeFiles) | ||
287 | { | ||
288 | UInt32 flags = PATCH_OPTION_FAIL_IF_SAME_FILE | PATCH_OPTION_FAIL_IF_BIGGER | PATCH_OPTION_USE_LZX_BEST; | ||
289 | if (optimizeForLargeFiles) | ||
290 | { | ||
291 | flags |= PATCH_OPTION_USE_LZX_LARGE; | ||
292 | } | ||
293 | return flags; | ||
294 | } | ||
295 | |||
296 | //--------------------------------------------------------------------- | ||
297 | // From PatchApi.h | ||
298 | //--------------------------------------------------------------------- | ||
299 | |||
300 | // | ||
301 | // The following contants can be combined and used as the OptionFlags | ||
302 | // parameter in the patch creation apis. | ||
303 | |||
304 | internal const uint PATCH_OPTION_USE_BEST = 0x00000000; // auto choose best (slower) | ||
305 | |||
306 | internal const uint PATCH_OPTION_USE_LZX_BEST = 0x00000003; // auto choose best of LXZ A/B (but not large) | ||
307 | internal const uint PATCH_OPTION_USE_LZX_A = 0x00000001; // normal | ||
308 | internal const uint PATCH_OPTION_USE_LXZ_B = 0x00000002; // better on some x86 binaries | ||
309 | internal const uint PATCH_OPTION_USE_LZX_LARGE = 0x00000004; // better support for large files (requires 5.1 or higher applyer) | ||
310 | |||
311 | internal const uint PATCH_OPTION_NO_BINDFIX = 0x00010000; // PE bound imports | ||
312 | internal const uint PATCH_OPTION_NO_LOCKFIX = 0x00020000; // PE smashed locks | ||
313 | internal const uint PATCH_OPTION_NO_REBASE = 0x00040000; // PE rebased image | ||
314 | internal const uint PATCH_OPTION_FAIL_IF_SAME_FILE = 0x00080000; // don't create if same | ||
315 | internal const uint PATCH_OPTION_FAIL_IF_BIGGER = 0x00100000; // fail if patch is larger than simply compressing new file (slower) | ||
316 | internal const uint PATCH_OPTION_NO_CHECKSUM = 0x00200000; // PE checksum zero | ||
317 | internal const uint PATCH_OPTION_NO_RESTIMEFIX = 0x00400000; // PE resource timestamps | ||
318 | internal const uint PATCH_OPTION_NO_TIMESTAMP = 0x00800000; // don't store new file timestamp in patch | ||
319 | internal const uint PATCH_OPTION_SIGNATURE_MD5 = 0x01000000; // use MD5 instead of CRC (reserved for future support) | ||
320 | internal const uint PATCH_OPTION_INTERLEAVE_FILES = 0x40000000; // better support for large files (requires 5.2 or higher applyer) | ||
321 | internal const uint PATCH_OPTION_RESERVED1 = 0x80000000; // (used internally) | ||
322 | |||
323 | internal const uint PATCH_OPTION_VALID_FLAGS = 0xC0FF0007; | ||
324 | |||
325 | // | ||
326 | // The following flags are used with PATCH_OPTION_DATA SymbolOptionFlags: | ||
327 | // | ||
328 | |||
329 | [Flags] | ||
330 | public enum PatchSymbolFlagsType :uint | ||
331 | { | ||
332 | PATCH_SYMBOL_NO_IMAGEHLP = 0x00000001, // don't use imagehlp.dll | ||
333 | PATCH_SYMBOL_NO_FAILURES = 0x00000002, // don't fail patch due to imagehlp failures | ||
334 | PATCH_SYMBOL_UNDECORATED_TOO = 0x00000004, // after matching decorated symbols, try to match remaining by undecorated names | ||
335 | PATCH_SYMBOL_RESERVED1 = 0x80000000, // (used internally) | ||
336 | MaxValue = PATCH_SYMBOL_NO_IMAGEHLP | PATCH_SYMBOL_NO_FAILURES | PATCH_SYMBOL_UNDECORATED_TOO | ||
337 | } | ||
338 | |||
339 | // | ||
340 | // The following flags are used with PATCH_OPTION_DATA ExtendedOptionFlags: | ||
341 | // | ||
342 | |||
343 | internal const uint PATCH_TRANSFORM_PE_RESOURCE_2 = 0x00000100; // better handling of PE resources (requires 5.2 or higher applyer) | ||
344 | internal const uint PATCH_TRANSFORM_PE_IRELOC_2 = 0x00000200; // better handling of PE stripped relocs (requires 5.2 or higher applyer) | ||
345 | |||
346 | // | ||
347 | // In addition to the standard Win32 error codes, the following error codes may | ||
348 | // be returned via GetLastError() when one of the patch APIs fails. | ||
349 | |||
350 | internal const uint ERROR_PATCH_ENCODE_FAILURE = 0xC00E3101; // create | ||
351 | internal const uint ERROR_PATCH_INVALID_OPTIONS = 0xC00E3102; // create | ||
352 | internal const uint ERROR_PATCH_SAME_FILE = 0xC00E3103; // create | ||
353 | internal const uint ERROR_PATCH_RETAIN_RANGES_DIFFER = 0xC00E3104; // create | ||
354 | internal const uint ERROR_PATCH_BIGGER_THAN_COMPRESSED = 0xC00E3105; // create | ||
355 | internal const uint ERROR_PATCH_IMAGEHLP_FALURE = 0xC00E3106; // create | ||
356 | |||
357 | /// <summary> | ||
358 | /// Delegate type that the PatchAPI calls for progress notification. | ||
359 | /// </summary> | ||
360 | /// <param name="context">.</param> | ||
361 | /// <param name="currentPosition">.</param> | ||
362 | /// <param name="maxPosition">.</param> | ||
363 | /// <returns>True for success</returns> | ||
364 | public delegate bool PatchProgressCallback( | ||
365 | IntPtr context, | ||
366 | uint currentPosition, | ||
367 | uint maxPosition | ||
368 | ); | ||
369 | |||
370 | /// <summary> | ||
371 | /// Delegate type that the PatchAPI calls for patch symbol load information. | ||
372 | /// </summary> | ||
373 | /// <param name="whichFile">.</param> | ||
374 | /// <param name="symbolFileName">.</param> | ||
375 | /// <param name="symType">.</param> | ||
376 | /// <param name="symbolFileCheckSum">.</param> | ||
377 | /// <param name="symbolFileTimeDate">.</param> | ||
378 | /// <param name="imageFileCheckSum">.</param> | ||
379 | /// <param name="imageFileTimeDate">.</param> | ||
380 | /// <param name="context">.</param> | ||
381 | /// <returns>???</returns> | ||
382 | public delegate bool PatchSymloadCallback( | ||
383 | uint whichFile, // 0 for new file, 1 for first old file, etc | ||
384 | [MarshalAs(UnmanagedType.LPStr)] string symbolFileName, | ||
385 | uint symType, // see SYM_TYPE in imagehlp.h | ||
386 | uint symbolFileCheckSum, | ||
387 | uint symbolFileTimeDate, | ||
388 | uint imageFileCheckSum, | ||
389 | uint imageFileTimeDate, | ||
390 | IntPtr context | ||
391 | ); | ||
392 | |||
393 | /// <summary> | ||
394 | /// Wraps PATCH_IGNORE_RANGE | ||
395 | /// </summary> | ||
396 | [StructLayout(LayoutKind.Sequential)] | ||
397 | internal class PatchIgnoreRange | ||
398 | { | ||
399 | public uint offsetInOldFile; | ||
400 | public uint lengthInBytes; | ||
401 | } | ||
402 | |||
403 | /// <summary> | ||
404 | /// Wraps PATCH_RETAIN_RANGE | ||
405 | /// </summary> | ||
406 | [StructLayout(LayoutKind.Sequential)] | ||
407 | internal class PatchRetainRange | ||
408 | { | ||
409 | public uint offsetInOldFile; | ||
410 | public uint lengthInBytes; | ||
411 | public uint offsetInNewFile; | ||
412 | } | ||
413 | |||
414 | /// <summary> | ||
415 | /// Wraps PATCH_OLD_FILE_INFO (except for the OldFile~ portion) | ||
416 | /// </summary> | ||
417 | internal class PatchOldFileInfo | ||
418 | { | ||
419 | public PatchIgnoreRange[] ignoreRange; | ||
420 | public PatchRetainRange[] retainRange; | ||
421 | } | ||
422 | |||
423 | /// <summary> | ||
424 | /// Wraps PATCH_OLD_FILE_INFO_W | ||
425 | /// </summary> | ||
426 | internal class PatchOldFileInfoW : PatchOldFileInfo | ||
427 | { | ||
428 | public string oldFileName; | ||
429 | } | ||
430 | |||
431 | /// <summary> | ||
432 | /// Wraps each PATCH_INTERLEAVE_MAP Range | ||
433 | /// </summary> | ||
434 | [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses"), StructLayout(LayoutKind.Sequential)] | ||
435 | internal class PatchInterleaveMapRange | ||
436 | { | ||
437 | public uint oldOffset; | ||
438 | public uint oldLength; | ||
439 | public uint newLength; | ||
440 | } | ||
441 | |||
442 | /// <summary> | ||
443 | /// Wraps PATCH_INTERLEAVE_MAP | ||
444 | /// </summary> | ||
445 | internal class PatchInterleaveMap | ||
446 | { | ||
447 | public PatchInterleaveMapRange[] ranges = null; | ||
448 | } | ||
449 | |||
450 | |||
451 | /// <summary> | ||
452 | /// Wraps PATCH_OPTION_DATA | ||
453 | /// </summary> | ||
454 | [BestFitMapping(false, ThrowOnUnmappableChar = true)] | ||
455 | internal class PatchOptionData | ||
456 | { | ||
457 | public PatchSymbolFlagsType symbolOptionFlags; // PATCH_SYMBOL_xxx flags | ||
458 | [MarshalAs(UnmanagedType.LPStr)] public string newFileSymbolPath; // always ANSI, never Unicode | ||
459 | [MarshalAs(UnmanagedType.LPStr)] public string[] oldFileSymbolPathArray; // array[ OldFileCount ] | ||
460 | public uint extendedOptionFlags; | ||
461 | public PatchSymloadCallback symLoadCallback = null; | ||
462 | public IntPtr symLoadContext = IntPtr.Zero; | ||
463 | public PatchInterleaveMap[] interleaveMapArray = null; // array[ OldFileCount ] (requires 5.2 or higher applyer) | ||
464 | public uint maxLzxWindowSize = 0; // limit memory requirements (requires 5.2 or higher applyer) | ||
465 | } | ||
466 | |||
467 | // | ||
468 | // Note that PATCH_OPTION_DATA contains LPCSTR paths, and no LPCWSTR (Unicode) | ||
469 | // path argument is available, even when used with one of the Unicode APIs | ||
470 | // such as CreatePatchFileW. This is because the unlerlying system services | ||
471 | // for symbol file handling (IMAGEHLP.DLL) only support ANSI file/path names. | ||
472 | // | ||
473 | |||
474 | // | ||
475 | // A note about PATCH_RETAIN_RANGE specifiers with multiple old files: | ||
476 | // | ||
477 | // Each old version file must have the same RetainRangeCount, and the same | ||
478 | // retain range LengthInBytes and OffsetInNewFile values in the same order. | ||
479 | // Only the OffsetInOldFile values can differ between old foles for retain | ||
480 | // ranges. | ||
481 | // | ||
482 | |||
483 | // | ||
484 | // The following prototypes are (some of the) interfaces for creating patches from files. | ||
485 | // | ||
486 | |||
487 | /// <summary> | ||
488 | /// Creates a new delta. | ||
489 | /// </summary> | ||
490 | /// <param name="oldFileCount">Size of oldFileInfoArray.</param> | ||
491 | /// <param name="oldFileInfoArray">Target file information.</param> | ||
492 | /// <param name="newFileName">Name of updated file.</param> | ||
493 | /// <param name="patchFileName">Name of delta to create.</param> | ||
494 | /// <param name="optionFlags">PATCH_OPTION_xxx.</param> | ||
495 | /// <param name="optionData">Optional PATCH_OPTION_DATA structure.</param> | ||
496 | /// <param name="progressCallback">Delegate for progress callbacks.</param> | ||
497 | /// <param name="context">Context for progress callback delegate.</param> | ||
498 | /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns> | ||
499 | [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
500 | [return: MarshalAs(UnmanagedType.Bool)] | ||
501 | internal static extern bool CreatePatchFileExW( | ||
502 | uint oldFileCount, // maximum 255 | ||
503 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OLD_FILE_INFO_W")] | ||
504 | PatchOldFileInfoW[] oldFileInfoArray, | ||
505 | string newFileName, // input file (required) | ||
506 | string patchFileName, // output file (required) | ||
507 | uint optionFlags, | ||
508 | [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(PatchAPIMarshaler), MarshalCookie="PATCH_OPTION_DATA")] | ||
509 | PatchOptionData optionData, | ||
510 | [MarshalAs (UnmanagedType.FunctionPtr)] | ||
511 | PatchProgressCallback progressCallback, | ||
512 | IntPtr context | ||
513 | ); | ||
514 | |||
515 | /// <summary> | ||
516 | /// Extracts delta header from delta. | ||
517 | /// </summary> | ||
518 | /// <param name="patchFileName">Name of delta file.</param> | ||
519 | /// <param name="patchHeaderFileName">Name of file to create with delta header.</param> | ||
520 | /// <returns>true if successfull, sets Marshal.GetLastWin32Error() if not.</returns> | ||
521 | [DllImport("mspatchc.dll", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
522 | [return: MarshalAs(UnmanagedType.Bool)] | ||
523 | internal static extern bool ExtractPatchHeaderToFileW( | ||
524 | string patchFileName, // input file | ||
525 | string patchHeaderFileName // output file | ||
526 | ); | ||
527 | |||
528 | // TODO: Add rest of APIs to enable custom binders to perform more exhaustive checks | ||
529 | |||
530 | /// <summary> | ||
531 | /// Marshals arguments for the CreatePatch~ APIs | ||
532 | /// </summary> | ||
533 | [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] | ||
534 | internal class PatchAPIMarshaler : ICustomMarshaler | ||
535 | { | ||
536 | internal static ICustomMarshaler GetInstance(string cookie) | ||
537 | { | ||
538 | return new PatchAPIMarshaler(cookie); | ||
539 | } | ||
540 | |||
541 | private enum MarshalType | ||
542 | { | ||
543 | PATCH_OPTION_DATA, | ||
544 | PATCH_OLD_FILE_INFO_W | ||
545 | }; | ||
546 | private PatchAPIMarshaler.MarshalType marshalType; | ||
547 | |||
548 | private PatchAPIMarshaler(string cookie) | ||
549 | { | ||
550 | this.marshalType = (PatchAPIMarshaler.MarshalType) Enum.Parse(typeof(PatchAPIMarshaler.MarshalType), cookie); | ||
551 | } | ||
552 | |||
553 | // | ||
554 | // Summary: | ||
555 | // Returns the size of the native data to be marshaled. | ||
556 | // | ||
557 | // Returns: | ||
558 | // The size in bytes of the native data. | ||
559 | public int GetNativeDataSize() | ||
560 | { | ||
561 | return Marshal.SizeOf(typeof(IntPtr)); | ||
562 | } | ||
563 | |||
564 | // | ||
565 | // Summary: | ||
566 | // Performs necessary cleanup of the managed data when it is no longer needed. | ||
567 | // | ||
568 | // Parameters: | ||
569 | // ManagedObj: | ||
570 | // The managed object to be destroyed. | ||
571 | public void CleanUpManagedData(object ManagedObj) | ||
572 | { | ||
573 | } | ||
574 | |||
575 | // | ||
576 | // Summary: | ||
577 | // Performs necessary cleanup of the unmanaged data when it is no longer needed. | ||
578 | // | ||
579 | // Parameters: | ||
580 | // pNativeData: | ||
581 | // A pointer to the unmanaged data to be destroyed. | ||
582 | public void CleanUpNativeData(IntPtr pNativeData) | ||
583 | { | ||
584 | if (IntPtr.Zero == pNativeData) | ||
585 | { | ||
586 | return; | ||
587 | } | ||
588 | |||
589 | switch (this.marshalType) | ||
590 | { | ||
591 | case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: | ||
592 | this.CleanUpPOD(pNativeData); | ||
593 | break; | ||
594 | default: | ||
595 | this.CleanUpPOFI_A(pNativeData); | ||
596 | break; | ||
597 | } | ||
598 | } | ||
599 | |||
600 | // | ||
601 | // Summary: | ||
602 | // Converts the managed data to unmanaged data. | ||
603 | // | ||
604 | // Parameters: | ||
605 | // ManagedObj: | ||
606 | // The managed object to be converted. | ||
607 | // | ||
608 | // Returns: | ||
609 | // Returns the COM view of the managed object. | ||
610 | public IntPtr MarshalManagedToNative(object ManagedObj) | ||
611 | { | ||
612 | if (null == ManagedObj) | ||
613 | { | ||
614 | return IntPtr.Zero; | ||
615 | } | ||
616 | |||
617 | switch(this.marshalType) | ||
618 | { | ||
619 | case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: | ||
620 | return this.MarshalPOD(ManagedObj as PatchOptionData); | ||
621 | case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: | ||
622 | return this.MarshalPOFIW_A(ManagedObj as PatchOldFileInfoW[]); | ||
623 | default: | ||
624 | throw new InvalidOperationException(); | ||
625 | } | ||
626 | } | ||
627 | |||
628 | |||
629 | // | ||
630 | // Summary: | ||
631 | // Converts the unmanaged data to managed data. | ||
632 | // | ||
633 | // Parameters: | ||
634 | // pNativeData: | ||
635 | // A pointer to the unmanaged data to be wrapped. | ||
636 | // | ||
637 | // Returns: | ||
638 | // Returns the managed view of the COM data. | ||
639 | public object MarshalNativeToManaged(IntPtr pNativeData) | ||
640 | { | ||
641 | return null; | ||
642 | } | ||
643 | |||
644 | // Implementation ************************************************* | ||
645 | |||
646 | // PATCH_OPTION_DATA offsets | ||
647 | private static readonly int symbolOptionFlagsOffset = Marshal.SizeOf(typeof(Int32)); | ||
648 | private static readonly int newFileSymbolPathOffset = 2*Marshal.SizeOf(typeof(Int32)); | ||
649 | private static readonly int oldFileSymbolPathArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); | ||
650 | private static readonly int extendedOptionFlagsOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); | ||
651 | private static readonly int symLoadCallbackOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); | ||
652 | private static readonly int symLoadContextOffset = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); | ||
653 | private static readonly int interleaveMapArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 4*Marshal.SizeOf(typeof(IntPtr)); | ||
654 | private static readonly int maxLzxWindowSizeOffset = 3*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); | ||
655 | private static readonly int patchOptionDataSize = 4*Marshal.SizeOf(typeof(Int32)) + 5*Marshal.SizeOf(typeof(IntPtr)); | ||
656 | |||
657 | // PATCH_OLD_FILE_INFO offsets | ||
658 | private static readonly int oldFileOffset = Marshal.SizeOf(typeof(Int32)); | ||
659 | private static readonly int ignoreRangeCountOffset = Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); | ||
660 | private static readonly int ignoreRangeArrayOffset = 2*Marshal.SizeOf(typeof(Int32)) + Marshal.SizeOf(typeof(IntPtr)); | ||
661 | private static readonly int retainRangeCountOffset = 2*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); | ||
662 | private static readonly int retainRangeArrayOffset = 3*Marshal.SizeOf(typeof(Int32)) + 2*Marshal.SizeOf(typeof(IntPtr)); | ||
663 | private static readonly int patchOldFileInfoSize = 3*Marshal.SizeOf(typeof(Int32)) + 3*Marshal.SizeOf(typeof(IntPtr)); | ||
664 | |||
665 | // Methods and data used to preserve data needed for cleanup | ||
666 | |||
667 | // This dictionary holds the quantity of items internal to each native structure that will need to be freed (the OldFileCount) | ||
668 | private static readonly Dictionary<IntPtr, int> OldFileCounts = new Dictionary<IntPtr, int>(); | ||
669 | private static readonly object OldFileCountsLock = new object(); | ||
670 | |||
671 | private IntPtr CreateMainStruct(int oldFileCount) | ||
672 | { | ||
673 | int nativeSize; | ||
674 | switch(this.marshalType) | ||
675 | { | ||
676 | case PatchAPIMarshaler.MarshalType.PATCH_OPTION_DATA: | ||
677 | nativeSize = patchOptionDataSize; | ||
678 | break; | ||
679 | case PatchAPIMarshaler.MarshalType.PATCH_OLD_FILE_INFO_W: | ||
680 | nativeSize = oldFileCount*patchOldFileInfoSize; | ||
681 | break; | ||
682 | default: | ||
683 | throw new InvalidOperationException(); | ||
684 | } | ||
685 | |||
686 | IntPtr native = Marshal.AllocCoTaskMem(nativeSize); | ||
687 | |||
688 | lock (PatchAPIMarshaler.OldFileCountsLock) | ||
689 | { | ||
690 | PatchAPIMarshaler.OldFileCounts.Add(native, oldFileCount); | ||
691 | } | ||
692 | |||
693 | return native; | ||
694 | } | ||
695 | |||
696 | private static void ReleaseMainStruct(IntPtr native) | ||
697 | { | ||
698 | lock (PatchAPIMarshaler.OldFileCountsLock) | ||
699 | { | ||
700 | PatchAPIMarshaler.OldFileCounts.Remove(native); | ||
701 | } | ||
702 | Marshal.FreeCoTaskMem(native); | ||
703 | } | ||
704 | |||
705 | private static int GetOldFileCount(IntPtr native) | ||
706 | { | ||
707 | lock (PatchAPIMarshaler.OldFileCountsLock) | ||
708 | { | ||
709 | return PatchAPIMarshaler.OldFileCounts[native]; | ||
710 | } | ||
711 | } | ||
712 | |||
713 | // Helper methods | ||
714 | |||
715 | private static IntPtr OptionalAnsiString(string managed) | ||
716 | { | ||
717 | return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemAnsi(managed); | ||
718 | } | ||
719 | |||
720 | private static IntPtr OptionalUnicodeString(string managed) | ||
721 | { | ||
722 | return (null == managed) ? IntPtr.Zero : Marshal.StringToCoTaskMemUni(managed); | ||
723 | } | ||
724 | |||
725 | // string array must be of the same length as the number of old files | ||
726 | private static IntPtr CreateArrayOfStringA(string[] managed) | ||
727 | { | ||
728 | if (null == managed) | ||
729 | { | ||
730 | return IntPtr.Zero; | ||
731 | } | ||
732 | |||
733 | int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); | ||
734 | IntPtr native = Marshal.AllocCoTaskMem(size); | ||
735 | |||
736 | for (int i = 0; i < managed.Length; ++i) | ||
737 | { | ||
738 | Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalAnsiString(managed[i])); | ||
739 | } | ||
740 | |||
741 | return native; | ||
742 | } | ||
743 | |||
744 | // string array must be of the same length as the number of old files | ||
745 | private static IntPtr CreateArrayOfStringW(string[] managed) | ||
746 | { | ||
747 | if (null == managed) | ||
748 | { | ||
749 | return IntPtr.Zero; | ||
750 | } | ||
751 | |||
752 | int size = managed.Length * Marshal.SizeOf(typeof(IntPtr)); | ||
753 | IntPtr native = Marshal.AllocCoTaskMem(size); | ||
754 | |||
755 | for (int i = 0; i < managed.Length; ++i) | ||
756 | { | ||
757 | Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), OptionalUnicodeString(managed[i])); | ||
758 | } | ||
759 | |||
760 | return native; | ||
761 | } | ||
762 | |||
763 | private static IntPtr CreateInterleaveMapRange(PatchInterleaveMap managed) | ||
764 | { | ||
765 | if (null == managed) | ||
766 | { | ||
767 | return IntPtr.Zero; | ||
768 | } | ||
769 | |||
770 | if (null == managed.ranges) | ||
771 | { | ||
772 | return IntPtr.Zero; | ||
773 | } | ||
774 | |||
775 | if (0 == managed.ranges.Length) | ||
776 | { | ||
777 | return IntPtr.Zero; | ||
778 | } | ||
779 | |||
780 | IntPtr native = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(UInt32)) | ||
781 | + managed.ranges.Length*(Marshal.SizeOf(typeof(PatchInterleaveMap)))); | ||
782 | WriteUInt32(native, (uint) managed.ranges.Length); | ||
783 | |||
784 | for (int i = 0; i < managed.ranges.Length; ++i) | ||
785 | { | ||
786 | Marshal.StructureToPtr(managed.ranges[i], (IntPtr)((Int64)native + i*Marshal.SizeOf(typeof(PatchInterleaveMap))), false); | ||
787 | } | ||
788 | return native; | ||
789 | } | ||
790 | |||
791 | private static IntPtr CreateInterleaveMap(PatchInterleaveMap[] managed) | ||
792 | { | ||
793 | if (null == managed) | ||
794 | { | ||
795 | return IntPtr.Zero; | ||
796 | } | ||
797 | |||
798 | IntPtr native = Marshal.AllocCoTaskMem(managed.Length * Marshal.SizeOf(typeof(IntPtr))); | ||
799 | |||
800 | for (int i = 0; i < managed.Length; ++i) | ||
801 | { | ||
802 | Marshal.WriteIntPtr(native, i*Marshal.SizeOf(typeof(IntPtr)), CreateInterleaveMapRange(managed[i])); | ||
803 | } | ||
804 | |||
805 | return native; | ||
806 | } | ||
807 | |||
808 | private static void WriteUInt32(IntPtr native, uint data) | ||
809 | { | ||
810 | Marshal.WriteInt32(native, unchecked((int) data)); | ||
811 | } | ||
812 | |||
813 | private static void WriteUInt32(IntPtr native, int offset, uint data) | ||
814 | { | ||
815 | Marshal.WriteInt32(native, offset, unchecked((int) data)); | ||
816 | } | ||
817 | |||
818 | // Marshal operations | ||
819 | |||
820 | private IntPtr MarshalPOD(PatchOptionData managed) | ||
821 | { | ||
822 | if (null == managed) | ||
823 | { | ||
824 | throw new ArgumentNullException("managed"); | ||
825 | } | ||
826 | |||
827 | IntPtr native = this.CreateMainStruct(managed.oldFileSymbolPathArray.Length); | ||
828 | Marshal.WriteInt32(native, patchOptionDataSize); // SizeOfThisStruct | ||
829 | WriteUInt32(native, symbolOptionFlagsOffset, (uint) managed.symbolOptionFlags); | ||
830 | Marshal.WriteIntPtr(native, newFileSymbolPathOffset, PatchAPIMarshaler.OptionalAnsiString(managed.newFileSymbolPath)); | ||
831 | Marshal.WriteIntPtr(native, oldFileSymbolPathArrayOffset, PatchAPIMarshaler.CreateArrayOfStringA(managed.oldFileSymbolPathArray)); | ||
832 | WriteUInt32(native, extendedOptionFlagsOffset, managed.extendedOptionFlags); | ||
833 | |||
834 | // GetFunctionPointerForDelegate() throws an ArgumentNullException if the delegate is null. | ||
835 | if (null == managed.symLoadCallback) | ||
836 | { | ||
837 | Marshal.WriteIntPtr(native, symLoadCallbackOffset, IntPtr.Zero); | ||
838 | } | ||
839 | else | ||
840 | { | ||
841 | Marshal.WriteIntPtr(native, symLoadCallbackOffset, Marshal.GetFunctionPointerForDelegate(managed.symLoadCallback)); | ||
842 | } | ||
843 | |||
844 | Marshal.WriteIntPtr(native, symLoadContextOffset, managed.symLoadContext); | ||
845 | Marshal.WriteIntPtr(native, interleaveMapArrayOffset, PatchAPIMarshaler.CreateInterleaveMap(managed.interleaveMapArray)); | ||
846 | WriteUInt32(native, maxLzxWindowSizeOffset, managed.maxLzxWindowSize); | ||
847 | return native; | ||
848 | } | ||
849 | |||
850 | private IntPtr MarshalPOFIW_A(PatchOldFileInfoW[] managed) | ||
851 | { | ||
852 | if (null == managed) | ||
853 | { | ||
854 | throw new ArgumentNullException("managed"); | ||
855 | } | ||
856 | |||
857 | if (0 == managed.Length) | ||
858 | { | ||
859 | return IntPtr.Zero; | ||
860 | } | ||
861 | |||
862 | IntPtr native = this.CreateMainStruct(managed.Length); | ||
863 | |||
864 | for (int i = 0; i < managed.Length; ++i) | ||
865 | { | ||
866 | PatchAPIMarshaler.MarshalPOFIW(managed[i], (IntPtr)((Int64)native + i * patchOldFileInfoSize)); | ||
867 | } | ||
868 | |||
869 | return native; | ||
870 | } | ||
871 | |||
872 | private static void MarshalPOFIW(PatchOldFileInfoW managed, IntPtr native) | ||
873 | { | ||
874 | PatchAPIMarshaler.MarshalPOFI(managed, native); | ||
875 | Marshal.WriteIntPtr(native, oldFileOffset, PatchAPIMarshaler.OptionalUnicodeString(managed.oldFileName)); // OldFileName | ||
876 | } | ||
877 | |||
878 | private static void MarshalPOFI(PatchOldFileInfo managed, IntPtr native) | ||
879 | { | ||
880 | Marshal.WriteInt32(native, patchOldFileInfoSize); // SizeOfThisStruct | ||
881 | WriteUInt32(native, ignoreRangeCountOffset, | ||
882 | (null == managed.ignoreRange) ? 0 : (uint) managed.ignoreRange.Length); // IgnoreRangeCount // maximum 255 | ||
883 | Marshal.WriteIntPtr(native, ignoreRangeArrayOffset, MarshalPIRArray(managed.ignoreRange)); // IgnoreRangeArray | ||
884 | WriteUInt32(native, retainRangeCountOffset, | ||
885 | (null == managed.retainRange) ? 0 : (uint) managed.retainRange.Length); // RetainRangeCount // maximum 255 | ||
886 | Marshal.WriteIntPtr(native, retainRangeArrayOffset, MarshalPRRArray(managed.retainRange)); // RetainRangeArray | ||
887 | } | ||
888 | |||
889 | private static IntPtr MarshalPIRArray(PatchIgnoreRange[] array) | ||
890 | { | ||
891 | if (null == array) | ||
892 | { | ||
893 | return IntPtr.Zero; | ||
894 | } | ||
895 | |||
896 | if (0 == array.Length) | ||
897 | { | ||
898 | return IntPtr.Zero; | ||
899 | } | ||
900 | |||
901 | IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchIgnoreRange))); | ||
902 | |||
903 | for (int i = 0; i < array.Length; ++i) | ||
904 | { | ||
905 | Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchIgnoreRange)))), false); | ||
906 | } | ||
907 | |||
908 | return native; | ||
909 | } | ||
910 | |||
911 | private static IntPtr MarshalPRRArray(PatchRetainRange[] array) | ||
912 | { | ||
913 | if (null == array) | ||
914 | { | ||
915 | return IntPtr.Zero; | ||
916 | } | ||
917 | |||
918 | if (0 == array.Length) | ||
919 | { | ||
920 | return IntPtr.Zero; | ||
921 | } | ||
922 | |||
923 | IntPtr native = Marshal.AllocCoTaskMem(array.Length*Marshal.SizeOf(typeof(PatchRetainRange))); | ||
924 | |||
925 | for (int i = 0; i < array.Length; ++i) | ||
926 | { | ||
927 | Marshal.StructureToPtr(array[i], (IntPtr)((Int64)native + (i*Marshal.SizeOf(typeof(PatchRetainRange)))), false); | ||
928 | } | ||
929 | |||
930 | return native; | ||
931 | } | ||
932 | |||
933 | // CleanUp operations | ||
934 | |||
935 | private void CleanUpPOD(IntPtr native) | ||
936 | { | ||
937 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, newFileSymbolPathOffset)); | ||
938 | |||
939 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)) | ||
940 | { | ||
941 | for (int i = 0; i < GetOldFileCount(native); ++i) | ||
942 | { | ||
943 | Marshal.FreeCoTaskMem( | ||
944 | Marshal.ReadIntPtr( | ||
945 | Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset), | ||
946 | i*Marshal.SizeOf(typeof(IntPtr)))); | ||
947 | } | ||
948 | |||
949 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileSymbolPathArrayOffset)); | ||
950 | } | ||
951 | |||
952 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, interleaveMapArrayOffset)) | ||
953 | { | ||
954 | for (int i = 0; i < GetOldFileCount(native); ++i) | ||
955 | { | ||
956 | Marshal.FreeCoTaskMem( | ||
957 | Marshal.ReadIntPtr( | ||
958 | Marshal.ReadIntPtr(native, interleaveMapArrayOffset), | ||
959 | i*Marshal.SizeOf(typeof(IntPtr)))); | ||
960 | } | ||
961 | |||
962 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, interleaveMapArrayOffset)); | ||
963 | } | ||
964 | |||
965 | PatchAPIMarshaler.ReleaseMainStruct(native); | ||
966 | } | ||
967 | |||
968 | private void CleanUpPOFI_A(IntPtr native) | ||
969 | { | ||
970 | for (int i = 0; i < GetOldFileCount(native); ++i) | ||
971 | { | ||
972 | PatchAPIMarshaler.CleanUpPOFI((IntPtr)((Int64)native + i*patchOldFileInfoSize)); | ||
973 | } | ||
974 | |||
975 | PatchAPIMarshaler.ReleaseMainStruct(native); | ||
976 | } | ||
977 | |||
978 | private static void CleanUpPOFI(IntPtr native) | ||
979 | { | ||
980 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, oldFileOffset)) | ||
981 | { | ||
982 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, oldFileOffset)); | ||
983 | } | ||
984 | |||
985 | PatchAPIMarshaler.CleanUpPOFIH(native); | ||
986 | } | ||
987 | |||
988 | private static void CleanUpPOFIH(IntPtr native) | ||
989 | { | ||
990 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)) | ||
991 | { | ||
992 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, ignoreRangeArrayOffset)); | ||
993 | } | ||
994 | |||
995 | if (IntPtr.Zero != Marshal.ReadIntPtr(native, retainRangeArrayOffset)) | ||
996 | { | ||
997 | Marshal.FreeCoTaskMem(Marshal.ReadIntPtr(native, retainRangeArrayOffset)); | ||
998 | } | ||
999 | } | ||
1000 | } | ||
1001 | } | ||
1002 | } | ||
diff --git a/src/WixToolset.Core/PatchSymbolFlagsType.cs b/src/WixToolset.Core/PatchSymbolFlagsType.cs new file mode 100644 index 00000000..eeb5c798 --- /dev/null +++ b/src/WixToolset.Core/PatchSymbolFlagsType.cs | |||
@@ -0,0 +1,19 @@ | |||
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 | ||
4 | { | ||
5 | using System; | ||
6 | |||
7 | // | ||
8 | // The following flags are used with PATCH_OPTION_DATA SymbolOptionFlags: | ||
9 | // | ||
10 | [Flags] | ||
11 | public enum PatchSymbolFlagsType : uint | ||
12 | { | ||
13 | PATCH_SYMBOL_NO_IMAGEHLP = 0x00000001, // don't use imagehlp.dll | ||
14 | PATCH_SYMBOL_NO_FAILURES = 0x00000002, // don't fail patch due to imagehlp failures | ||
15 | PATCH_SYMBOL_UNDECORATED_TOO = 0x00000004, // after matching decorated symbols, try to match remaining by undecorated names | ||
16 | PATCH_SYMBOL_RESERVED1 = 0x80000000, // (used internally) | ||
17 | MaxValue = PATCH_SYMBOL_NO_IMAGEHLP | PATCH_SYMBOL_NO_FAILURES | PATCH_SYMBOL_UNDECORATED_TOO | ||
18 | } | ||
19 | } | ||
diff --git a/src/WixToolset.Core/PatchTransform.cs b/src/WixToolset.Core/PatchTransform.cs index c87b1a21..46e4e6d7 100644 --- a/src/WixToolset.Core/PatchTransform.cs +++ b/src/WixToolset.Core/PatchTransform.cs | |||
@@ -155,7 +155,7 @@ namespace WixToolset | |||
155 | if (!deletedComponent.ContainsKey(componentId)) | 155 | if (!deletedComponent.ContainsKey(componentId)) |
156 | { | 156 | { |
157 | bool foundRemoveFileEntry = false; | 157 | bool foundRemoveFileEntry = false; |
158 | string filename = Msi.Installer.GetName((string)row[2], false, true); | 158 | string filename = Common.GetName((string)row[2], false, true); |
159 | 159 | ||
160 | Table removeFileTable = this.Transform.Tables["RemoveFile"]; | 160 | Table removeFileTable = this.Transform.Tables["RemoveFile"]; |
161 | if (null != removeFileTable) | 161 | if (null != removeFileTable) |
@@ -172,7 +172,7 @@ namespace WixToolset | |||
172 | // Check if there is a RemoveFile entry for this file | 172 | // Check if there is a RemoveFile entry for this file |
173 | if (null != removeFileRow[2]) | 173 | if (null != removeFileRow[2]) |
174 | { | 174 | { |
175 | string removeFileName = Msi.Installer.GetName((string)removeFileRow[2], false, true); | 175 | string removeFileName = Common.GetName((string)removeFileRow[2], false, true); |
176 | 176 | ||
177 | // Convert the MSI format for a wildcard string to Regex format. | 177 | // Convert the MSI format for a wildcard string to Regex format. |
178 | removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); | 178 | removeFileName = removeFileName.Replace('.', '|').Replace('?', '.').Replace("*", ".*").Replace("|", "\\."); |
diff --git a/src/WixToolset.Core/Properties/AssemblyInfo.cs b/src/WixToolset.Core/Properties/AssemblyInfo.cs index b3740b2a..81274e3f 100644 --- a/src/WixToolset.Core/Properties/AssemblyInfo.cs +++ b/src/WixToolset.Core/Properties/AssemblyInfo.cs | |||
@@ -5,5 +5,5 @@ using System.Reflection; | |||
5 | using System.Runtime.InteropServices; | 5 | using System.Runtime.InteropServices; |
6 | 6 | ||
7 | [assembly: AssemblyCulture("")] | 7 | [assembly: AssemblyCulture("")] |
8 | [assembly: CLSCompliant(true)] | 8 | [assembly: CLSCompliant(false)] |
9 | [assembly: ComVisible(false)] | 9 | [assembly: ComVisible(false)] |
diff --git a/src/WixToolset.Core/ProvidesDependency.cs b/src/WixToolset.Core/ProvidesDependency.cs deleted file mode 100644 index ea96b5c8..00000000 --- a/src/WixToolset.Core/ProvidesDependency.cs +++ /dev/null | |||
@@ -1,108 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Xml; | ||
7 | using WixToolset.Data; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Represents an authored or imported dependency provider. | ||
11 | /// </summary> | ||
12 | internal sealed class ProvidesDependency | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Creates a new instance of the <see cref="ProviderDependency"/> class from a <see cref="Row"/>. | ||
16 | /// </summary> | ||
17 | /// <param name="row">The <see cref="Row"/> from which data is imported.</param> | ||
18 | internal ProvidesDependency(Row row) | ||
19 | : this((string)row[2], (string)row[3], (string)row[4], (int?)row[5]) | ||
20 | { | ||
21 | } | ||
22 | |||
23 | /// <summary> | ||
24 | /// Creates a new instance of the <see cref="ProviderDependency"/> class. | ||
25 | /// </summary> | ||
26 | /// <param name="key">The unique key of the dependency.</param> | ||
27 | /// <param name="attributes">Additional attributes for the dependency.</param> | ||
28 | internal ProvidesDependency(string key, string version, string displayName, int? attributes) | ||
29 | { | ||
30 | this.Key = key; | ||
31 | this.Version = version; | ||
32 | this.DisplayName = displayName; | ||
33 | this.Attributes = attributes; | ||
34 | } | ||
35 | |||
36 | /// <summary> | ||
37 | /// Gets or sets the unique key of the package provider. | ||
38 | /// </summary> | ||
39 | internal string Key { get; set; } | ||
40 | |||
41 | /// <summary> | ||
42 | /// Gets or sets the version of the package provider. | ||
43 | /// </summary> | ||
44 | internal string Version { get; set; } | ||
45 | |||
46 | /// <summary> | ||
47 | /// Gets or sets the display name of the package provider. | ||
48 | /// </summary> | ||
49 | internal string DisplayName { get; set; } | ||
50 | |||
51 | /// <summary> | ||
52 | /// Gets or sets the attributes for the dependency. | ||
53 | /// </summary> | ||
54 | internal int? Attributes { get; set; } | ||
55 | |||
56 | /// <summary> | ||
57 | /// Gets or sets whether the dependency was imported from the package. | ||
58 | /// </summary> | ||
59 | internal bool Imported { get; set; } | ||
60 | |||
61 | /// <summary> | ||
62 | /// Gets whether certain properties are the same. | ||
63 | /// </summary> | ||
64 | /// <param name="other">Another <see cref="ProvidesDependency"/> to compare.</param> | ||
65 | /// <remarks>This is not the same as object equality, but only checks a subset of properties | ||
66 | /// to determine if the objects are similar and could be merged into a collection.</remarks> | ||
67 | /// <returns>True if certain properties are the same.</returns> | ||
68 | internal bool Equals(ProvidesDependency other) | ||
69 | { | ||
70 | if (null != other) | ||
71 | { | ||
72 | return this.Key == other.Key && | ||
73 | this.Version == other.Version && | ||
74 | this.DisplayName == other.DisplayName; | ||
75 | } | ||
76 | |||
77 | return false; | ||
78 | } | ||
79 | |||
80 | /// <summary> | ||
81 | /// Writes the dependency to the bundle XML manifest. | ||
82 | /// </summary> | ||
83 | /// <param name="writer">The <see cref="XmlTextWriter"/> for the bundle XML manifest.</param> | ||
84 | internal void WriteXml(XmlTextWriter writer) | ||
85 | { | ||
86 | writer.WriteStartElement("Provides"); | ||
87 | writer.WriteAttributeString("Key", this.Key); | ||
88 | |||
89 | if (!String.IsNullOrEmpty(this.Version)) | ||
90 | { | ||
91 | writer.WriteAttributeString("Version", this.Version); | ||
92 | } | ||
93 | |||
94 | if (!String.IsNullOrEmpty(this.DisplayName)) | ||
95 | { | ||
96 | writer.WriteAttributeString("DisplayName", this.DisplayName); | ||
97 | } | ||
98 | |||
99 | if (this.Imported) | ||
100 | { | ||
101 | // The package dependency was explicitly authored into the manifest. | ||
102 | writer.WriteAttributeString("Imported", "yes"); | ||
103 | } | ||
104 | |||
105 | writer.WriteEndElement(); | ||
106 | } | ||
107 | } | ||
108 | } | ||
diff --git a/src/WixToolset.Core/ProvidesDependencyCollection.cs b/src/WixToolset.Core/ProvidesDependencyCollection.cs deleted file mode 100644 index a777afb0..00000000 --- a/src/WixToolset.Core/ProvidesDependencyCollection.cs +++ /dev/null | |||
@@ -1,64 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.ObjectModel; | ||
7 | |||
8 | /// <summary> | ||
9 | /// A case-insensitive collection of unique <see cref="ProvidesDependency"/> objects. | ||
10 | /// </summary> | ||
11 | internal sealed class ProvidesDependencyCollection : KeyedCollection<string, ProvidesDependency> | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Creates a case-insensitive collection of unique <see cref="ProvidesDependency"/> objects. | ||
15 | /// </summary> | ||
16 | internal ProvidesDependencyCollection() | ||
17 | : base(StringComparer.InvariantCultureIgnoreCase) | ||
18 | { | ||
19 | } | ||
20 | |||
21 | /// <summary> | ||
22 | /// Adds the <see cref="ProvidesDependency"/> to the collection if it doesn't already exist. | ||
23 | /// </summary> | ||
24 | /// <param name="dependency">The <see cref="ProvidesDependency"/> to add to the collection.</param> | ||
25 | /// <returns>True if the <see cref="ProvidesDependency"/> was added to the collection; otherwise, false.</returns> | ||
26 | /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception> | ||
27 | internal bool Merge(ProvidesDependency dependency) | ||
28 | { | ||
29 | if (null == dependency) | ||
30 | { | ||
31 | throw new ArgumentNullException("dependency"); | ||
32 | } | ||
33 | |||
34 | // If the dependency key is already in the collection, verify equality for a subset of properties. | ||
35 | if (this.Contains(dependency.Key)) | ||
36 | { | ||
37 | ProvidesDependency current = this[dependency.Key]; | ||
38 | if (!current.Equals(dependency)) | ||
39 | { | ||
40 | return false; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | base.Add(dependency); | ||
45 | return true; | ||
46 | } | ||
47 | |||
48 | /// <summary> | ||
49 | /// Gets the <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>. | ||
50 | /// </summary> | ||
51 | /// <param name="dependency">The dependency to index.</param> | ||
52 | /// <exception cref="ArgumentNullException">The <paramref name="dependency"/> parameter is null.</exception> | ||
53 | /// <returns>The <see cref="ProvidesDependency.Key"/> for the <paramref name="dependency"/>.</returns> | ||
54 | protected override string GetKeyForItem(ProvidesDependency dependency) | ||
55 | { | ||
56 | if (null == dependency) | ||
57 | { | ||
58 | throw new ArgumentNullException("dependency"); | ||
59 | } | ||
60 | |||
61 | return dependency.Key; | ||
62 | } | ||
63 | } | ||
64 | } | ||
diff --git a/src/WixToolset.Core/TransformsFlags.cs b/src/WixToolset.Core/TransformsFlags.cs new file mode 100644 index 00000000..d9ec94ac --- /dev/null +++ b/src/WixToolset.Core/TransformsFlags.cs | |||
@@ -0,0 +1,81 @@ | |||
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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics.CodeAnalysis; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Summary information values for the CharCount property in transforms. | ||
10 | /// </summary> | ||
11 | [Flags] | ||
12 | [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] | ||
13 | public enum TransformFlags | ||
14 | { | ||
15 | /// <summary>Ignore error when adding a row that exists.</summary> | ||
16 | ErrorAddExistingRow = 0x1, | ||
17 | |||
18 | /// <summary>Ignore error when deleting a row that does not exist.</summary> | ||
19 | ErrorDeleteMissingRow = 0x2, | ||
20 | |||
21 | /// <summary>Ignore error when adding a table that exists. </summary> | ||
22 | ErrorAddExistingTable = 0x4, | ||
23 | |||
24 | /// <summary>Ignore error when deleting a table that does not exist. </summary> | ||
25 | ErrorDeleteMissingTable = 0x8, | ||
26 | |||
27 | /// <summary>Ignore error when updating a row that does not exist. </summary> | ||
28 | ErrorUpdateMissingRow = 0x10, | ||
29 | |||
30 | /// <summary>Ignore error when transform and database code pages do not match, and their code pages are neutral.</summary> | ||
31 | ErrorChangeCodePage = 0x20, | ||
32 | |||
33 | /// <summary>Default language must match base database. </summary> | ||
34 | ValidateLanguage = 0x10000, | ||
35 | |||
36 | /// <summary>Product must match base database.</summary> | ||
37 | ValidateProduct = 0x20000, | ||
38 | |||
39 | /// <summary>Check major version only. </summary> | ||
40 | ValidateMajorVersion = 0x80000, | ||
41 | |||
42 | /// <summary>Check major and minor versions only. </summary> | ||
43 | ValidateMinorVersion = 0x100000, | ||
44 | |||
45 | /// <summary>Check major, minor, and update versions.</summary> | ||
46 | ValidateUpdateVersion = 0x200000, | ||
47 | |||
48 | /// <summary>Installed version lt base version. </summary> | ||
49 | ValidateNewLessBaseVersion = 0x400000, | ||
50 | |||
51 | /// <summary>Installed version lte base version. </summary> | ||
52 | ValidateNewLessEqualBaseVersion = 0x800000, | ||
53 | |||
54 | /// <summary>Installed version eq base version. </summary> | ||
55 | ValidateNewEqualBaseVersion = 0x1000000, | ||
56 | |||
57 | /// <summary>Installed version gte base version.</summary> | ||
58 | ValidateNewGreaterEqualBaseVersion = 0x2000000, | ||
59 | |||
60 | /// <summary>Installed version gt base version.</summary> | ||
61 | ValidateNewGreaterBaseVersion = 0x4000000, | ||
62 | |||
63 | /// <summary>UpgradeCode must match base database.</summary> | ||
64 | ValidateUpgradeCode = 0x8000000, | ||
65 | |||
66 | /// <summary>Masks all version checks on ProductVersion.</summary> | ||
67 | ProductVersionMask = ValidateMajorVersion | ValidateMinorVersion | ValidateUpdateVersion, | ||
68 | |||
69 | /// <summary>Masks all operations on ProductVersion.</summary> | ||
70 | ProductVersionOperatorMask = ValidateNewLessBaseVersion | ValidateNewLessEqualBaseVersion | ValidateNewEqualBaseVersion | ValidateNewGreaterEqualBaseVersion | ValidateNewGreaterBaseVersion, | ||
71 | |||
72 | /// <summary>Default value for instance transforms.</summary> | ||
73 | InstanceTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct | ValidateUpdateVersion | ValidateNewGreaterEqualBaseVersion, | ||
74 | |||
75 | /// <summary>Default value for language transforms.</summary> | ||
76 | LanguageTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ErrorChangeCodePage | ValidateProduct, | ||
77 | |||
78 | /// <summary>Default value for patch transforms.</summary> | ||
79 | PatchTransformDefault = ErrorAddExistingRow | ErrorDeleteMissingRow | ErrorAddExistingTable | ErrorDeleteMissingTable | ErrorUpdateMissingRow | ValidateProduct | ValidateUpdateVersion | ValidateNewEqualBaseVersion | ValidateUpgradeCode, | ||
80 | } | ||
81 | } | ||
diff --git a/src/WixToolset.Core/UnbindContext.cs b/src/WixToolset.Core/UnbindContext.cs new file mode 100644 index 00000000..ed55f312 --- /dev/null +++ b/src/WixToolset.Core/UnbindContext.cs | |||
@@ -0,0 +1,24 @@ | |||
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 | ||
4 | { | ||
5 | using WixToolset.Data; | ||
6 | using WixToolset.Extensibility; | ||
7 | |||
8 | internal class UnbindContext : IUnbindContext | ||
9 | { | ||
10 | public Messaging Messaging { get; } = Messaging.Instance; | ||
11 | |||
12 | public string ExportBasePath { get; set; } | ||
13 | |||
14 | public string InputFilePath { get; set; } | ||
15 | |||
16 | public string IntermediateFolder { get; set; } | ||
17 | |||
18 | public bool IsAdminImage { get; set; } | ||
19 | |||
20 | public bool SuppressExtractCabinets { get; set; } | ||
21 | |||
22 | public bool SuppressDemodularization { get; set; } | ||
23 | } | ||
24 | } | ||
diff --git a/src/WixToolset.Core/Unbinder.cs b/src/WixToolset.Core/Unbinder.cs index 744d5536..2ff51997 100644 --- a/src/WixToolset.Core/Unbinder.cs +++ b/src/WixToolset.Core/Unbinder.cs | |||
@@ -1,37 +1,18 @@ | |||
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 | 3 | namespace WixToolset.Core |
4 | { | 4 | { |
5 | using System; | ||
6 | using System.CodeDom.Compiler; | ||
7 | using System.Collections; | 5 | using System.Collections; |
8 | using System.Collections.Generic; | ||
9 | using System.Collections.Specialized; | ||
10 | using System.ComponentModel; | ||
11 | using System.Globalization; | ||
12 | using System.IO; | 6 | using System.IO; |
13 | using System.Linq; | ||
14 | using System.Text.RegularExpressions; | ||
15 | using WixToolset.Bind; | ||
16 | using WixToolset.Bind.Bundles; | ||
17 | using WixToolset.Cab; | ||
18 | using WixToolset.Data; | 7 | using WixToolset.Data; |
19 | using WixToolset.Data.Rows; | ||
20 | using WixToolset.Extensibility; | 8 | using WixToolset.Extensibility; |
21 | using WixToolset.Msi; | 9 | using System.Collections.Generic; |
22 | using WixToolset.Core.Native; | ||
23 | using WixToolset.Ole32; | ||
24 | 10 | ||
25 | /// <summary> | 11 | /// <summary> |
26 | /// Unbinder core of the WiX toolset. | 12 | /// Unbinder core of the WiX toolset. |
27 | /// </summary> | 13 | /// </summary> |
28 | public sealed class Unbinder : IMessageHandler | 14 | public sealed class Unbinder |
29 | { | 15 | { |
30 | private string emptyFile; | ||
31 | private bool isAdminImage; | ||
32 | private int sectionCount; | ||
33 | private bool suppressDemodularization; | ||
34 | private bool suppressExtractCabinets; | ||
35 | private TableDefinitionCollection tableDefinitions; | 16 | private TableDefinitionCollection tableDefinitions; |
36 | private ArrayList unbinderExtensions; | 17 | private ArrayList unbinderExtensions; |
37 | // private TempFileCollection tempFiles; | 18 | // private TempFileCollection tempFiles; |
@@ -45,61 +26,32 @@ namespace WixToolset | |||
45 | this.unbinderExtensions = new ArrayList(); | 26 | this.unbinderExtensions = new ArrayList(); |
46 | } | 27 | } |
47 | 28 | ||
29 | public IEnumerable<IBackendFactory> BackendFactories { get; } | ||
30 | |||
48 | /// <summary> | 31 | /// <summary> |
49 | /// Gets or sets whether the input msi is an admin image. | 32 | /// Gets or sets whether the input msi is an admin image. |
50 | /// </summary> | 33 | /// </summary> |
51 | /// <value>Set to true if the input msi is part of an admin image.</value> | 34 | /// <value>Set to true if the input msi is part of an admin image.</value> |
52 | public bool IsAdminImage | 35 | public bool IsAdminImage { get; set; } |
53 | { | ||
54 | get { return this.isAdminImage; } | ||
55 | set { this.isAdminImage = value; } | ||
56 | } | ||
57 | 36 | ||
58 | /// <summary> | 37 | /// <summary> |
59 | /// Gets or sets the option to suppress demodularizing values. | 38 | /// Gets or sets the option to suppress demodularizing values. |
60 | /// </summary> | 39 | /// </summary> |
61 | /// <value>The option to suppress demodularizing values.</value> | 40 | /// <value>The option to suppress demodularizing values.</value> |
62 | public bool SuppressDemodularization | 41 | public bool SuppressDemodularization { get; set; } |
63 | { | ||
64 | get { return this.suppressDemodularization; } | ||
65 | set { this.suppressDemodularization = value; } | ||
66 | } | ||
67 | 42 | ||
68 | /// <summary> | 43 | /// <summary> |
69 | /// Gets or sets the option to suppress extracting cabinets. | 44 | /// Gets or sets the option to suppress extracting cabinets. |
70 | /// </summary> | 45 | /// </summary> |
71 | /// <value>The option to suppress extracting cabinets.</value> | 46 | /// <value>The option to suppress extracting cabinets.</value> |
72 | public bool SuppressExtractCabinets | 47 | public bool SuppressExtractCabinets { get; set; } |
73 | { | ||
74 | get { return this.suppressExtractCabinets; } | ||
75 | set { this.suppressExtractCabinets = value; } | ||
76 | } | ||
77 | 48 | ||
78 | /// <summary> | 49 | /// <summary> |
79 | /// Gets or sets the temporary path for the Binder. If left null, the binder | 50 | /// Gets or sets the temporary path for the Binder. If left null, the binder |
80 | /// will use %TEMP% environment variable. | 51 | /// will use %TEMP% environment variable. |
81 | /// </summary> | 52 | /// </summary> |
82 | /// <value>Path to temp files.</value> | 53 | /// <value>Path to temp files.</value> |
83 | public string TempFilesLocation | 54 | public string TempFilesLocation => Path.GetTempPath(); |
84 | { | ||
85 | get | ||
86 | { | ||
87 | // return null == this.tempFiles ? String.Empty : this.tempFiles.BasePath; | ||
88 | return Path.GetTempPath(); | ||
89 | } | ||
90 | |||
91 | // set | ||
92 | // { | ||
93 | // if (null == value) | ||
94 | // { | ||
95 | // this.tempFiles = new TempFileCollection(); | ||
96 | // } | ||
97 | // else | ||
98 | // { | ||
99 | // this.tempFiles = new TempFileCollection(value); | ||
100 | // } | ||
101 | // } | ||
102 | } | ||
103 | 55 | ||
104 | /// <summary> | 56 | /// <summary> |
105 | /// Adds extension data. | 57 | /// Adds extension data. |
@@ -156,1336 +108,25 @@ namespace WixToolset | |||
156 | // if we don't have the temporary files object yet, get one | 108 | // if we don't have the temporary files object yet, get one |
157 | Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there | 109 | Directory.CreateDirectory(this.TempFilesLocation); // ensure the base path is there |
158 | 110 | ||
159 | if (OutputType.Patch == outputType) | 111 | var context = new UnbindContext(); |
160 | { | 112 | context.InputFilePath = file; |
161 | return this.UnbindPatch(file, exportBasePath); | 113 | context.ExportBasePath = exportBasePath; |
162 | } | 114 | context.IntermediateFolder = this.TempFilesLocation; |
163 | else if (OutputType.Transform == outputType) | 115 | context.IsAdminImage = this.IsAdminImage; |
164 | { | 116 | context.SuppressDemodularization = this.SuppressDemodularization; |
165 | return this.UnbindTransform(file, exportBasePath); | 117 | context.SuppressExtractCabinets = this.SuppressExtractCabinets; |
166 | } | ||
167 | else if (OutputType.Bundle == outputType) | ||
168 | { | ||
169 | return this.UnbindBundle(file, exportBasePath); | ||
170 | } | ||
171 | else // other database types | ||
172 | { | ||
173 | return this.UnbindDatabase(file, outputType, exportBasePath); | ||
174 | } | ||
175 | } | ||
176 | |||
177 | /// <summary> | ||
178 | /// Cleans up the temp files used by the Decompiler. | ||
179 | /// </summary> | ||
180 | /// <returns>True if all files were deleted, false otherwise.</returns> | ||
181 | /// <remarks> | ||
182 | /// This should be called after every call to Decompile to ensure there | ||
183 | /// are no conflicts between each decompiled database. | ||
184 | /// </remarks> | ||
185 | public bool DeleteTempFiles() | ||
186 | { | ||
187 | #if REDO_IN_NETCORE | ||
188 | bool deleted = Common.DeleteTempFiles(this.tempFiles.BasePath, this); | ||
189 | |||
190 | if (deleted) | ||
191 | { | ||
192 | this.tempFiles = null; // temp files have been deleted, no need to remember this now | ||
193 | } | ||
194 | |||
195 | return deleted; | ||
196 | #endif | ||
197 | return true; | ||
198 | } | ||
199 | |||
200 | /// <summary> | ||
201 | /// Sends a message to the message delegate if there is one. | ||
202 | /// </summary> | ||
203 | /// <param name="mea">Message event arguments.</param> | ||
204 | public void OnMessage(MessageEventArgs e) | ||
205 | { | ||
206 | Messaging.Instance.OnMessage(e); | ||
207 | } | ||
208 | |||
209 | /// <summary> | ||
210 | /// Unbind an MSI database file. | ||
211 | /// </summary> | ||
212 | /// <param name="databaseFile">The database file.</param> | ||
213 | /// <param name="outputType">The output type.</param> | ||
214 | /// <param name="exportBasePath">The path where files should be exported.</param> | ||
215 | /// <returns>The unbound database.</returns> | ||
216 | private Output UnbindDatabase(string databaseFile, OutputType outputType, string exportBasePath) | ||
217 | { | ||
218 | Output output; | ||
219 | |||
220 | try | ||
221 | { | ||
222 | using (Database database = new Database(databaseFile, OpenDatabase.ReadOnly)) | ||
223 | { | ||
224 | output = this.UnbindDatabase(databaseFile, database, outputType, exportBasePath, false); | ||
225 | |||
226 | // extract the files from the cabinets | ||
227 | if (null != exportBasePath && !this.suppressExtractCabinets) | ||
228 | { | ||
229 | this.ExtractCabinets(output, database, databaseFile, exportBasePath); | ||
230 | } | ||
231 | } | ||
232 | } | ||
233 | catch (Win32Exception e) | ||
234 | { | ||
235 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED | ||
236 | { | ||
237 | throw new WixException(WixErrors.OpenDatabaseFailed(databaseFile)); | ||
238 | } | ||
239 | |||
240 | throw; | ||
241 | } | ||
242 | |||
243 | return output; | ||
244 | } | ||
245 | |||
246 | /// <summary> | ||
247 | /// Unbind an MSI database file. | ||
248 | /// </summary> | ||
249 | /// <param name="databaseFile">The database file.</param> | ||
250 | /// <param name="database">The opened database.</param> | ||
251 | /// <param name="outputType">The type of output to create.</param> | ||
252 | /// <param name="exportBasePath">The path where files should be exported.</param> | ||
253 | /// <param name="skipSummaryInfo">Option to skip unbinding the _SummaryInformation table.</param> | ||
254 | /// <returns>The output representing the database.</returns> | ||
255 | private Output UnbindDatabase(string databaseFile, Database database, OutputType outputType, string exportBasePath, bool skipSummaryInfo) | ||
256 | { | ||
257 | string modularizationGuid = null; | ||
258 | Output output = new Output(new SourceLineNumber(databaseFile)); | ||
259 | View validationView = null; | ||
260 | |||
261 | // set the output type | ||
262 | output.Type = outputType; | ||
263 | |||
264 | // get the codepage | ||
265 | database.Export("_ForceCodepage", this.TempFilesLocation, "_ForceCodepage.idt"); | ||
266 | using (StreamReader sr = File.OpenText(Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"))) | ||
267 | { | ||
268 | string line; | ||
269 | |||
270 | while (null != (line = sr.ReadLine())) | ||
271 | { | ||
272 | string[] data = line.Split('\t'); | ||
273 | |||
274 | if (2 == data.Length) | ||
275 | { | ||
276 | output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); | ||
277 | } | ||
278 | } | ||
279 | } | ||
280 | |||
281 | // get the summary information table if it exists; it won't if unbinding a transform | ||
282 | if (!skipSummaryInfo) | ||
283 | { | ||
284 | using (SummaryInformation summaryInformation = new SummaryInformation(database)) | ||
285 | { | ||
286 | Table table = new Table(null, this.tableDefinitions["_SummaryInformation"]); | ||
287 | |||
288 | for (int i = 1; 19 >= i; i++) | ||
289 | { | ||
290 | string value = summaryInformation.GetProperty(i); | ||
291 | |||
292 | if (0 < value.Length) | ||
293 | { | ||
294 | Row row = table.CreateRow(output.SourceLineNumbers); | ||
295 | row[0] = i; | ||
296 | row[1] = value; | ||
297 | } | ||
298 | } | ||
299 | |||
300 | output.Tables.Add(table); | ||
301 | } | ||
302 | } | ||
303 | |||
304 | try | ||
305 | { | ||
306 | // open a view on the validation table if it exists | ||
307 | if (database.TableExists("_Validation")) | ||
308 | { | ||
309 | validationView = database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); | ||
310 | } | ||
311 | |||
312 | // get the normal tables | ||
313 | using (View tablesView = database.OpenExecuteView("SELECT * FROM _Tables")) | ||
314 | { | ||
315 | while (true) | ||
316 | { | ||
317 | using (Record tableRecord = tablesView.Fetch()) | ||
318 | { | ||
319 | if (null == tableRecord) | ||
320 | { | ||
321 | break; | ||
322 | } | ||
323 | |||
324 | string tableName = tableRecord.GetString(1); | ||
325 | |||
326 | using (View tableView = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) | ||
327 | { | ||
328 | List<ColumnDefinition> columns; | ||
329 | using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES), | ||
330 | columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES)) | ||
331 | { | ||
332 | // index the primary keys | ||
333 | HashSet<string> tablePrimaryKeys = new HashSet<string>(); | ||
334 | using (Record primaryKeysRecord = database.PrimaryKeys(tableName)) | ||
335 | { | ||
336 | int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); | ||
337 | |||
338 | for (int i = 1; i <= primaryKeysFieldCount; i++) | ||
339 | { | ||
340 | tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); | ||
341 | } | ||
342 | } | ||
343 | |||
344 | int columnCount = columnNameRecord.GetFieldCount(); | ||
345 | columns = new List<ColumnDefinition>(columnCount); | ||
346 | for (int i = 1; i <= columnCount; i++) | ||
347 | { | ||
348 | string columnName = columnNameRecord.GetString(i); | ||
349 | string idtType = columnTypeRecord.GetString(i); | ||
350 | |||
351 | ColumnType columnType; | ||
352 | int length; | ||
353 | bool nullable; | ||
354 | |||
355 | ColumnCategory columnCategory = ColumnCategory.Unknown; | ||
356 | ColumnModularizeType columnModularizeType = ColumnModularizeType.None; | ||
357 | bool primary = tablePrimaryKeys.Contains(columnName); | ||
358 | bool minValueSet = false; | ||
359 | int minValue = -1; | ||
360 | bool maxValueSet = false; | ||
361 | int maxValue = -1; | ||
362 | string keyTable = null; | ||
363 | bool keyColumnSet = false; | ||
364 | int keyColumn = -1; | ||
365 | string category = null; | ||
366 | string set = null; | ||
367 | string description = null; | ||
368 | |||
369 | // get the column type, length, and whether its nullable | ||
370 | switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) | ||
371 | { | ||
372 | case 'i': | ||
373 | columnType = ColumnType.Number; | ||
374 | break; | ||
375 | case 'l': | ||
376 | columnType = ColumnType.Localized; | ||
377 | break; | ||
378 | case 's': | ||
379 | columnType = ColumnType.String; | ||
380 | break; | ||
381 | case 'v': | ||
382 | columnType = ColumnType.Object; | ||
383 | break; | ||
384 | default: | ||
385 | // TODO: error | ||
386 | columnType = ColumnType.Unknown; | ||
387 | break; | ||
388 | } | ||
389 | length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); | ||
390 | nullable = Char.IsUpper(idtType[0]); | ||
391 | |||
392 | // try to get validation information | ||
393 | if (null != validationView) | ||
394 | { | ||
395 | using (Record validationRecord = new Record(2)) | ||
396 | { | ||
397 | validationRecord.SetString(1, tableName); | ||
398 | validationRecord.SetString(2, columnName); | ||
399 | |||
400 | validationView.Execute(validationRecord); | ||
401 | } | ||
402 | |||
403 | using (Record validationRecord = validationView.Fetch()) | ||
404 | { | ||
405 | if (null != validationRecord) | ||
406 | { | ||
407 | string validationNullable = validationRecord.GetString(3); | ||
408 | minValueSet = !validationRecord.IsNull(4); | ||
409 | minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); | ||
410 | maxValueSet = !validationRecord.IsNull(5); | ||
411 | maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); | ||
412 | keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); | ||
413 | keyColumnSet = !validationRecord.IsNull(7); | ||
414 | keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); | ||
415 | category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); | ||
416 | set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); | ||
417 | description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); | ||
418 | |||
419 | // check the validation nullable value against the column definition | ||
420 | if (null == validationNullable) | ||
421 | { | ||
422 | // TODO: warn for illegal validation nullable column | ||
423 | } | ||
424 | else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) | ||
425 | { | ||
426 | // TODO: warn for mismatch between column definition and validation nullable | ||
427 | } | ||
428 | |||
429 | // convert category to ColumnCategory | ||
430 | if (null != category) | ||
431 | { | ||
432 | try | ||
433 | { | ||
434 | columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); | ||
435 | } | ||
436 | catch (ArgumentException) | ||
437 | { | ||
438 | columnCategory = ColumnCategory.Unknown; | ||
439 | } | ||
440 | } | ||
441 | } | ||
442 | else | ||
443 | { | ||
444 | // TODO: warn about no validation information | ||
445 | } | ||
446 | } | ||
447 | } | ||
448 | |||
449 | // guess the modularization type | ||
450 | if ("Icon" == keyTable && 1 == keyColumn) | ||
451 | { | ||
452 | columnModularizeType = ColumnModularizeType.Icon; | ||
453 | } | ||
454 | else if ("Condition" == columnName) | ||
455 | { | ||
456 | columnModularizeType = ColumnModularizeType.Condition; | ||
457 | } | ||
458 | else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) | ||
459 | { | ||
460 | columnModularizeType = ColumnModularizeType.Property; | ||
461 | } | ||
462 | else if (ColumnCategory.Identifier == columnCategory) | ||
463 | { | ||
464 | columnModularizeType = ColumnModularizeType.Column; | ||
465 | } | ||
466 | |||
467 | columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); | ||
468 | } | ||
469 | } | ||
470 | |||
471 | TableDefinition tableDefinition = new TableDefinition(tableName, columns, false, false); | ||
472 | |||
473 | // use our table definitions if core properties are the same; this allows us to take advantage | ||
474 | // of wix concepts like localizable columns which current code assumes | ||
475 | if (this.tableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.tableDefinitions[tableName])) | ||
476 | { | ||
477 | tableDefinition = this.tableDefinitions[tableName]; | ||
478 | } | ||
479 | |||
480 | Table table = new Table(null, tableDefinition); | ||
481 | |||
482 | while (true) | ||
483 | { | ||
484 | using (Record rowRecord = tableView.Fetch()) | ||
485 | { | ||
486 | if (null == rowRecord) | ||
487 | { | ||
488 | break; | ||
489 | } | ||
490 | |||
491 | int recordCount = rowRecord.GetFieldCount(); | ||
492 | Row row = table.CreateRow(output.SourceLineNumbers); | ||
493 | |||
494 | for (int i = 0; recordCount > i && row.Fields.Length > i; i++) | ||
495 | { | ||
496 | if (rowRecord.IsNull(i + 1)) | ||
497 | { | ||
498 | if (!row.Fields[i].Column.Nullable) | ||
499 | { | ||
500 | // TODO: display an error for a null value in a non-nullable field OR | ||
501 | // display a warning and put an empty string in the value to let the compiler handle it | ||
502 | // (the second option is risky because the later code may make certain assumptions about | ||
503 | // the contents of a row value) | ||
504 | } | ||
505 | } | ||
506 | else | ||
507 | { | ||
508 | switch (row.Fields[i].Column.Type) | ||
509 | { | ||
510 | case ColumnType.Number: | ||
511 | bool success = false; | ||
512 | int intValue = rowRecord.GetInteger(i + 1); | ||
513 | if (row.Fields[i].Column.IsLocalizable) | ||
514 | { | ||
515 | success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); | ||
516 | } | ||
517 | else | ||
518 | { | ||
519 | success = row.BestEffortSetField(i, intValue); | ||
520 | } | ||
521 | |||
522 | if (!success) | ||
523 | { | ||
524 | this.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); | ||
525 | } | ||
526 | break; | ||
527 | case ColumnType.Object: | ||
528 | string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; | ||
529 | |||
530 | if (null != exportBasePath) | ||
531 | { | ||
532 | string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); | ||
533 | sourceFile = Path.Combine(exportBasePath, relativeSourceFile); | ||
534 | |||
535 | // ensure the parent directory exists | ||
536 | System.IO.Directory.CreateDirectory(Path.Combine(exportBasePath, tableName)); | ||
537 | |||
538 | using (FileStream fs = System.IO.File.Create(sourceFile)) | ||
539 | { | ||
540 | int bytesRead; | ||
541 | byte[] buffer = new byte[512]; | ||
542 | |||
543 | while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) | ||
544 | { | ||
545 | fs.Write(buffer, 0, bytesRead); | ||
546 | } | ||
547 | } | ||
548 | } | ||
549 | |||
550 | row[i] = sourceFile; | ||
551 | break; | ||
552 | default: | ||
553 | string value = rowRecord.GetString(i + 1); | ||
554 | 118 | ||
555 | switch (row.Fields[i].Column.Category) | 119 | foreach (var factory in this.BackendFactories) |
556 | { | ||
557 | case ColumnCategory.Guid: | ||
558 | value = value.ToUpper(CultureInfo.InvariantCulture); | ||
559 | break; | ||
560 | } | ||
561 | |||
562 | // de-modularize | ||
563 | if (!this.suppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) | ||
564 | { | ||
565 | Regex modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); | ||
566 | |||
567 | if (null == modularizationGuid) | ||
568 | { | ||
569 | Match match = modularization.Match(value); | ||
570 | if (match.Success) | ||
571 | { | ||
572 | modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); | ||
573 | } | ||
574 | } | ||
575 | |||
576 | value = modularization.Replace(value, String.Empty); | ||
577 | } | ||
578 | |||
579 | // escape "$(" for the preprocessor | ||
580 | value = value.Replace("$(", "$$("); | ||
581 | |||
582 | // escape things that look like wix variables | ||
583 | MatchCollection matches = Common.WixVariableRegex.Matches(value); | ||
584 | for (int j = matches.Count - 1; 0 <= j; j--) | ||
585 | { | ||
586 | value = value.Insert(matches[j].Index, "!"); | ||
587 | } | ||
588 | |||
589 | row[i] = value; | ||
590 | break; | ||
591 | } | ||
592 | } | ||
593 | } | ||
594 | } | ||
595 | } | ||
596 | |||
597 | output.Tables.Add(table); | ||
598 | } | ||
599 | |||
600 | } | ||
601 | } | ||
602 | } | ||
603 | } | ||
604 | finally | ||
605 | { | ||
606 | if (null != validationView) | ||
607 | { | ||
608 | validationView.Close(); | ||
609 | } | ||
610 | } | ||
611 | |||
612 | // set the modularization guid as the PackageCode | ||
613 | if (null != modularizationGuid) | ||
614 | { | 120 | { |
615 | Table table = output.Tables["_SummaryInformation"]; | 121 | if (factory.TryCreateBackend(outputType.ToString(), file, null, out var backend)) |
616 | |||
617 | foreach (Row row in table.Rows) | ||
618 | { | 122 | { |
619 | if (9 == (int)row[0]) // PID_REVNUMBER | 123 | return backend.Unbind(context); |
620 | { | ||
621 | row[1] = modularizationGuid; | ||
622 | } | ||
623 | } | 124 | } |
624 | } | 125 | } |
625 | 126 | ||
626 | if (this.isAdminImage) | 127 | // TODO: Display message that could not find a unbinder for output type? |
627 | { | ||
628 | GenerateWixFileTable(databaseFile, output); | ||
629 | GenerateSectionIds(output); | ||
630 | } | ||
631 | |||
632 | return output; | ||
633 | } | ||
634 | |||
635 | /// <summary> | ||
636 | /// Creates section ids on rows which form logical groupings of resources. | ||
637 | /// </summary> | ||
638 | /// <param name="output">The Output that represents the msi database.</param> | ||
639 | private void GenerateSectionIds(Output output) | ||
640 | { | ||
641 | // First assign and index section ids for the tables that are in their own sections. | ||
642 | AssignSectionIdsToTable(output.Tables["Binary"], 0); | ||
643 | Hashtable componentSectionIdIndex = AssignSectionIdsToTable(output.Tables["Component"], 0); | ||
644 | Hashtable customActionSectionIdIndex = AssignSectionIdsToTable(output.Tables["CustomAction"], 0); | ||
645 | AssignSectionIdsToTable(output.Tables["Directory"], 0); | ||
646 | Hashtable featureSectionIdIndex = AssignSectionIdsToTable(output.Tables["Feature"], 0); | ||
647 | AssignSectionIdsToTable(output.Tables["Icon"], 0); | ||
648 | Hashtable digitalCertificateSectionIdIndex = AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); | ||
649 | AssignSectionIdsToTable(output.Tables["Property"], 0); | ||
650 | |||
651 | // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here. | ||
652 | Hashtable fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); | ||
653 | Hashtable appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); | ||
654 | Hashtable odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); | ||
655 | Hashtable odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); | ||
656 | Hashtable registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); | ||
657 | Hashtable serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0); | ||
658 | |||
659 | // Now handle all the tables which only rely on previous indexes and order does not matter. | ||
660 | foreach (Table table in output.Tables) | ||
661 | { | ||
662 | switch (table.Name) | ||
663 | { | ||
664 | case "WixFile": | ||
665 | case "MsiFileHash": | ||
666 | ConnectTableToSection(table, fileSectionIdIndex, 0); | ||
667 | break; | ||
668 | case "MsiAssembly": | ||
669 | case "MsiAssemblyName": | ||
670 | ConnectTableToSection(table, componentSectionIdIndex, 0); | ||
671 | break; | ||
672 | case "MsiPackageCertificate": | ||
673 | case "MsiPatchCertificate": | ||
674 | ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); | ||
675 | break; | ||
676 | case "CreateFolder": | ||
677 | case "FeatureComponents": | ||
678 | case "MoveFile": | ||
679 | case "ReserveCost": | ||
680 | case "ODBCTranslator": | ||
681 | ConnectTableToSection(table, componentSectionIdIndex, 1); | ||
682 | break; | ||
683 | case "TypeLib": | ||
684 | ConnectTableToSection(table, componentSectionIdIndex, 2); | ||
685 | break; | ||
686 | case "Shortcut": | ||
687 | case "Environment": | ||
688 | ConnectTableToSection(table, componentSectionIdIndex, 3); | ||
689 | break; | ||
690 | case "RemoveRegistry": | ||
691 | ConnectTableToSection(table, componentSectionIdIndex, 4); | ||
692 | break; | ||
693 | case "ServiceControl": | ||
694 | ConnectTableToSection(table, componentSectionIdIndex, 5); | ||
695 | break; | ||
696 | case "IniFile": | ||
697 | case "RemoveIniFile": | ||
698 | ConnectTableToSection(table, componentSectionIdIndex, 7); | ||
699 | break; | ||
700 | case "AppId": | ||
701 | ConnectTableToSection(table, appIdSectionIdIndex, 0); | ||
702 | break; | ||
703 | case "Condition": | ||
704 | ConnectTableToSection(table, featureSectionIdIndex, 0); | ||
705 | break; | ||
706 | case "ODBCSourceAttribute": | ||
707 | ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); | ||
708 | break; | ||
709 | case "ODBCAttribute": | ||
710 | ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); | ||
711 | break; | ||
712 | case "AdminExecuteSequence": | ||
713 | case "AdminUISequence": | ||
714 | case "AdvtExecuteSequence": | ||
715 | case "AdvtUISequence": | ||
716 | case "InstallExecuteSequence": | ||
717 | case "InstallUISequence": | ||
718 | ConnectTableToSection(table, customActionSectionIdIndex, 0); | ||
719 | break; | ||
720 | case "LockPermissions": | ||
721 | case "MsiLockPermissions": | ||
722 | foreach (Row row in table.Rows) | ||
723 | { | ||
724 | string lockObject = (string)row[0]; | ||
725 | string tableName = (string)row[1]; | ||
726 | switch (tableName) | ||
727 | { | ||
728 | case "File": | ||
729 | row.SectionId = (string)fileSectionIdIndex[lockObject]; | ||
730 | break; | ||
731 | case "Registry": | ||
732 | row.SectionId = (string)registrySectionIdIndex[lockObject]; | ||
733 | break; | ||
734 | case "ServiceInstall": | ||
735 | row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; | ||
736 | break; | ||
737 | } | ||
738 | } | ||
739 | break; | ||
740 | } | ||
741 | } | ||
742 | |||
743 | // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. | ||
744 | foreach (IUnbinderExtension extension in this.unbinderExtensions) | ||
745 | { | ||
746 | extension.GenerateSectionIds(output); | ||
747 | } | ||
748 | } | ||
749 | |||
750 | /// <summary> | ||
751 | /// Creates new section ids on all the rows in a table. | ||
752 | /// </summary> | ||
753 | /// <param name="table">The table to add sections to.</param> | ||
754 | /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> | ||
755 | /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> | ||
756 | private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) | ||
757 | { | ||
758 | Hashtable hashtable = new Hashtable(); | ||
759 | if (null != table) | ||
760 | { | ||
761 | foreach (Row row in table.Rows) | ||
762 | { | ||
763 | row.SectionId = GetNewSectionId(); | ||
764 | hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); | ||
765 | } | ||
766 | } | ||
767 | return hashtable; | ||
768 | } | ||
769 | |||
770 | /// <summary> | ||
771 | /// Connects a table's rows to an already sectioned table. | ||
772 | /// </summary> | ||
773 | /// <param name="table">The table containing rows that need to be connected to sections.</param> | ||
774 | /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> | ||
775 | /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> | ||
776 | private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex) | ||
777 | { | ||
778 | if (null != table) | ||
779 | { | ||
780 | foreach (Row row in table.Rows) | ||
781 | { | ||
782 | if (sectionIdIndex.ContainsKey(row[rowIndex])) | ||
783 | { | ||
784 | row.SectionId = (string)sectionIdIndex[row[rowIndex]]; | ||
785 | } | ||
786 | } | ||
787 | } | ||
788 | } | ||
789 | |||
790 | /// <summary> | ||
791 | /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it. | ||
792 | /// </summary> | ||
793 | /// <param name="table">The table containing rows that need to be connected to sections.</param> | ||
794 | /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> | ||
795 | /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> | ||
796 | /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> | ||
797 | /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> | ||
798 | private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) | ||
799 | { | ||
800 | Hashtable newHashTable = new Hashtable(); | ||
801 | if (null != table) | ||
802 | { | ||
803 | foreach (Row row in table.Rows) | ||
804 | { | ||
805 | if (!sectionIdIndex.ContainsKey(row[rowIndex])) | ||
806 | { | ||
807 | continue; | ||
808 | } | ||
809 | |||
810 | row.SectionId = (string)sectionIdIndex[row[rowIndex]]; | ||
811 | if (null != row[rowPrimaryKeyIndex]) | ||
812 | { | ||
813 | newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); | ||
814 | } | ||
815 | } | ||
816 | } | ||
817 | return newHashTable; | ||
818 | } | ||
819 | |||
820 | /// <summary> | ||
821 | /// Creates a new section identifier to be used when adding a section to an output. | ||
822 | /// </summary> | ||
823 | /// <returns>A string representing a new section id.</returns> | ||
824 | private string GetNewSectionId() | ||
825 | { | ||
826 | this.sectionCount++; | ||
827 | return "wix.section." + this.sectionCount.ToString(CultureInfo.InvariantCulture); | ||
828 | } | ||
829 | |||
830 | /// <summary> | ||
831 | /// Generates the WixFile table based on a path to an admin image msi and an Output. | ||
832 | /// </summary> | ||
833 | /// <param name="databaseFile">The path to the msi database file in an admin image.</param> | ||
834 | /// <param name="output">The Output that represents the msi database.</param> | ||
835 | private void GenerateWixFileTable(string databaseFile, Output output) | ||
836 | { | ||
837 | string adminRootPath = Path.GetDirectoryName(databaseFile); | ||
838 | |||
839 | Hashtable componentDirectoryIndex = new Hashtable(); | ||
840 | Table componentTable = output.Tables["Component"]; | ||
841 | foreach (Row row in componentTable.Rows) | ||
842 | { | ||
843 | componentDirectoryIndex.Add(row[0], row[2]); | ||
844 | } | ||
845 | |||
846 | // Index full source paths for all directories | ||
847 | Hashtable directoryDirectoryParentIndex = new Hashtable(); | ||
848 | Hashtable directoryFullPathIndex = new Hashtable(); | ||
849 | Hashtable directorySourceNameIndex = new Hashtable(); | ||
850 | Table directoryTable = output.Tables["Directory"]; | ||
851 | foreach (Row row in directoryTable.Rows) | ||
852 | { | ||
853 | directoryDirectoryParentIndex.Add(row[0], row[1]); | ||
854 | if (null == row[1]) | ||
855 | { | ||
856 | directoryFullPathIndex.Add(row[0], adminRootPath); | ||
857 | } | ||
858 | else | ||
859 | { | ||
860 | directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); | ||
861 | } | ||
862 | } | ||
863 | |||
864 | foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) | ||
865 | { | ||
866 | if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) | ||
867 | { | ||
868 | GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); | ||
869 | } | ||
870 | } | ||
871 | |||
872 | Table fileTable = output.Tables["File"]; | ||
873 | Table wixFileTable = output.EnsureTable(this.tableDefinitions["WixFile"]); | ||
874 | foreach (Row row in fileTable.Rows) | ||
875 | { | ||
876 | WixFileRow wixFileRow = new WixFileRow(null, this.tableDefinitions["WixFile"]); | ||
877 | wixFileRow.File = (string)row[0]; | ||
878 | wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; | ||
879 | wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); | ||
880 | |||
881 | if (!File.Exists(wixFileRow.Source)) | ||
882 | { | ||
883 | throw new WixException(WixErrors.WixFileNotFound(wixFileRow.Source)); | ||
884 | } | ||
885 | |||
886 | wixFileTable.Rows.Add(wixFileRow); | ||
887 | } | ||
888 | } | ||
889 | |||
890 | /// <summary> | ||
891 | /// Gets the full path of a directory. Populates the full path index with the directory's full path and all of its parent directorie's full paths. | ||
892 | /// </summary> | ||
893 | /// <param name="directory">The directory identifier.</param> | ||
894 | /// <param name="directoryDirectoryParentIndex">The Hashtable containing all the directory to directory parent mapping.</param> | ||
895 | /// <param name="directorySourceNameIndex">The Hashtable containing all the directory to source name mapping.</param> | ||
896 | /// <param name="directoryFullPathIndex">The Hashtable containing a mapping between all of the directories and their previously calculated full paths.</param> | ||
897 | /// <returns>The full path to the directory.</returns> | ||
898 | private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) | ||
899 | { | ||
900 | string parent = (string)directoryDirectoryParentIndex[directory]; | ||
901 | string sourceName = (string)directorySourceNameIndex[directory]; | ||
902 | |||
903 | string parentFullPath; | ||
904 | if (directoryFullPathIndex.ContainsKey(parent)) | ||
905 | { | ||
906 | parentFullPath = (string)directoryFullPathIndex[parent]; | ||
907 | } | ||
908 | else | ||
909 | { | ||
910 | parentFullPath = GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); | ||
911 | } | ||
912 | |||
913 | if (null == sourceName) | ||
914 | { | ||
915 | sourceName = String.Empty; | ||
916 | } | ||
917 | |||
918 | string fullPath = Path.Combine(parentFullPath, sourceName); | ||
919 | directoryFullPathIndex.Add(directory, fullPath); | ||
920 | |||
921 | return fullPath; | ||
922 | } | ||
923 | |||
924 | /// <summary> | ||
925 | /// Get the source name in an admin image. | ||
926 | /// </summary> | ||
927 | /// <param name="value">The Filename value.</param> | ||
928 | /// <returns>The source name of the directory in an admin image.</returns> | ||
929 | private static string GetAdminSourceName(string value) | ||
930 | { | ||
931 | string name = null; | ||
932 | string[] names; | ||
933 | string shortname = null; | ||
934 | string shortsourcename = null; | ||
935 | string sourcename = null; | ||
936 | |||
937 | names = Installer.GetNames(value); | ||
938 | |||
939 | if (null != names[0] && "." != names[0]) | ||
940 | { | ||
941 | if (null != names[1]) | ||
942 | { | ||
943 | shortname = names[0]; | ||
944 | } | ||
945 | else | ||
946 | { | ||
947 | name = names[0]; | ||
948 | } | ||
949 | } | ||
950 | |||
951 | if (null != names[1]) | ||
952 | { | ||
953 | name = names[1]; | ||
954 | } | ||
955 | |||
956 | if (null != names[2]) | ||
957 | { | ||
958 | if (null != names[3]) | ||
959 | { | ||
960 | shortsourcename = names[2]; | ||
961 | } | ||
962 | else | ||
963 | { | ||
964 | sourcename = names[2]; | ||
965 | } | ||
966 | } | ||
967 | |||
968 | if (null != names[3]) | ||
969 | { | ||
970 | sourcename = names[3]; | ||
971 | } | ||
972 | |||
973 | if (null != sourcename) | ||
974 | { | ||
975 | return sourcename; | ||
976 | } | ||
977 | else if (null != shortsourcename) | ||
978 | { | ||
979 | return shortsourcename; | ||
980 | } | ||
981 | else if (null != name) | ||
982 | { | ||
983 | return name; | ||
984 | } | ||
985 | else | ||
986 | { | ||
987 | return shortname; | ||
988 | } | ||
989 | } | ||
990 | |||
991 | /// <summary> | ||
992 | /// Unbind an MSP patch file. | ||
993 | /// </summary> | ||
994 | /// <param name="patchFile">The patch file.</param> | ||
995 | /// <param name="exportBasePath">The path where files should be exported.</param> | ||
996 | /// <returns>The unbound patch.</returns> | ||
997 | private Output UnbindPatch(string patchFile, string exportBasePath) | ||
998 | { | ||
999 | Output patch; | ||
1000 | |||
1001 | // patch files are essentially database files (use a special flag to let the API know its a patch file) | ||
1002 | try | ||
1003 | { | ||
1004 | using (Database database = new Database(patchFile, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) | ||
1005 | { | ||
1006 | patch = this.UnbindDatabase(patchFile, database, OutputType.Patch, exportBasePath, false); | ||
1007 | } | ||
1008 | } | ||
1009 | catch (Win32Exception e) | ||
1010 | { | ||
1011 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED | ||
1012 | { | ||
1013 | throw new WixException(WixErrors.OpenDatabaseFailed(patchFile)); | ||
1014 | } | ||
1015 | |||
1016 | throw; | ||
1017 | } | ||
1018 | |||
1019 | // retrieve the transforms (they are in substorages) | ||
1020 | using (Storage storage = Storage.Open(patchFile, StorageMode.Read | StorageMode.ShareDenyWrite)) | ||
1021 | { | ||
1022 | Table summaryInformationTable = patch.Tables["_SummaryInformation"]; | ||
1023 | foreach (Row row in summaryInformationTable.Rows) | ||
1024 | { | ||
1025 | if (8 == (int)row[0]) // PID_LASTAUTHOR | ||
1026 | { | ||
1027 | string value = (string)row[1]; | ||
1028 | |||
1029 | foreach (string decoratedSubStorageName in value.Split(';')) | ||
1030 | { | ||
1031 | string subStorageName = decoratedSubStorageName.Substring(1); | ||
1032 | string transformFile = Path.Combine(this.TempFilesLocation, String.Concat("Transform", Path.DirectorySeparatorChar, subStorageName, ".mst")); | ||
1033 | |||
1034 | // ensure the parent directory exists | ||
1035 | System.IO.Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); | ||
1036 | |||
1037 | // copy the substorage to a new storage for the transform file | ||
1038 | using (Storage subStorage = storage.OpenStorage(subStorageName)) | ||
1039 | { | ||
1040 | using (Storage transformStorage = Storage.CreateDocFile(transformFile, StorageMode.ReadWrite | StorageMode.ShareExclusive | StorageMode.Create)) | ||
1041 | { | ||
1042 | subStorage.CopyTo(transformStorage); | ||
1043 | } | ||
1044 | } | ||
1045 | |||
1046 | // unbind the transform | ||
1047 | Output transform = this.UnbindTransform(transformFile, (null == exportBasePath ? null : Path.Combine(exportBasePath, subStorageName))); | ||
1048 | patch.SubStorages.Add(new SubStorage(subStorageName, transform)); | ||
1049 | } | ||
1050 | |||
1051 | break; | ||
1052 | } | ||
1053 | } | ||
1054 | } | ||
1055 | |||
1056 | // extract the files from the cabinets | ||
1057 | // TODO: use per-transform export paths for support of multi-product patches | ||
1058 | if (null != exportBasePath && !this.suppressExtractCabinets) | ||
1059 | { | ||
1060 | using (Database database = new Database(patchFile, OpenDatabase.ReadOnly | OpenDatabase.OpenPatchFile)) | ||
1061 | { | ||
1062 | foreach (SubStorage subStorage in patch.SubStorages) | ||
1063 | { | ||
1064 | // only patch transforms should carry files | ||
1065 | if (subStorage.Name.StartsWith("#", StringComparison.Ordinal)) | ||
1066 | { | ||
1067 | this.ExtractCabinets(subStorage.Data, database, patchFile, exportBasePath); | ||
1068 | } | ||
1069 | } | ||
1070 | } | ||
1071 | } | ||
1072 | |||
1073 | return patch; | ||
1074 | } | ||
1075 | |||
1076 | /// <summary> | ||
1077 | /// Unbind an MSI transform file. | ||
1078 | /// </summary> | ||
1079 | /// <param name="transformFile">The transform file.</param> | ||
1080 | /// <param name="exportBasePath">The path where files should be exported.</param> | ||
1081 | /// <returns>The unbound transform.</returns> | ||
1082 | private Output UnbindTransform(string transformFile, string exportBasePath) | ||
1083 | { | ||
1084 | Output transform = new Output(new SourceLineNumber(transformFile)); | ||
1085 | transform.Type = OutputType.Transform; | ||
1086 | |||
1087 | // get the summary information table | ||
1088 | using (SummaryInformation summaryInformation = new SummaryInformation(transformFile)) | ||
1089 | { | ||
1090 | Table table = transform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); | ||
1091 | |||
1092 | for (int i = 1; 19 >= i; i++) | ||
1093 | { | ||
1094 | string value = summaryInformation.GetProperty(i); | ||
1095 | |||
1096 | if (0 < value.Length) | ||
1097 | { | ||
1098 | Row row = table.CreateRow(transform.SourceLineNumbers); | ||
1099 | row[0] = i; | ||
1100 | row[1] = value; | ||
1101 | } | ||
1102 | } | ||
1103 | } | ||
1104 | |||
1105 | // create a schema msi which hopefully matches the table schemas in the transform | ||
1106 | Output schemaOutput = new Output(null); | ||
1107 | string msiDatabaseFile = Path.Combine(this.TempFilesLocation, "schema.msi"); | ||
1108 | foreach (TableDefinition tableDefinition in this.tableDefinitions) | ||
1109 | { | ||
1110 | // skip unreal tables and the Patch table | ||
1111 | if (!tableDefinition.Unreal && "Patch" != tableDefinition.Name) | ||
1112 | { | ||
1113 | schemaOutput.EnsureTable(tableDefinition); | ||
1114 | } | ||
1115 | } | ||
1116 | |||
1117 | Hashtable addedRows = new Hashtable(); | ||
1118 | Table transformViewTable; | ||
1119 | |||
1120 | // Bind the schema msi. | ||
1121 | this.GenerateDatabase(schemaOutput, msiDatabaseFile); | ||
1122 | |||
1123 | // apply the transform to the database and retrieve the modifications | ||
1124 | using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) | ||
1125 | { | ||
1126 | // apply the transform with the ViewTransform option to collect all the modifications | ||
1127 | msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); | ||
1128 | |||
1129 | // unbind the database | ||
1130 | Output transformViewOutput = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); | ||
1131 | |||
1132 | // index the added and possibly modified rows (added rows may also appears as modified rows) | ||
1133 | transformViewTable = transformViewOutput.Tables["_TransformView"]; | ||
1134 | Hashtable modifiedRows = new Hashtable(); | ||
1135 | foreach (Row row in transformViewTable.Rows) | ||
1136 | { | ||
1137 | string tableName = (string)row[0]; | ||
1138 | string columnName = (string)row[1]; | ||
1139 | string primaryKeys = (string)row[2]; | ||
1140 | |||
1141 | if ("INSERT" == columnName) | ||
1142 | { | ||
1143 | string index = String.Concat(tableName, ':', primaryKeys); | ||
1144 | |||
1145 | addedRows.Add(index, null); | ||
1146 | } | ||
1147 | else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row | ||
1148 | { | ||
1149 | string index = String.Concat(tableName, ':', primaryKeys); | ||
1150 | |||
1151 | modifiedRows[index] = row; | ||
1152 | } | ||
1153 | } | ||
1154 | |||
1155 | // create placeholder rows for modified rows to make the transform insert the updated values when its applied | ||
1156 | foreach (Row row in modifiedRows.Values) | ||
1157 | { | ||
1158 | string tableName = (string)row[0]; | ||
1159 | string columnName = (string)row[1]; | ||
1160 | string primaryKeys = (string)row[2]; | ||
1161 | |||
1162 | string index = String.Concat(tableName, ':', primaryKeys); | ||
1163 | |||
1164 | // ignore information for added rows | ||
1165 | if (!addedRows.Contains(index)) | ||
1166 | { | ||
1167 | Table table = schemaOutput.Tables[tableName]; | ||
1168 | this.CreateRow(table, primaryKeys, true); | ||
1169 | } | ||
1170 | } | ||
1171 | } | ||
1172 | |||
1173 | // Re-bind the schema output with the placeholder rows. | ||
1174 | this.GenerateDatabase(schemaOutput, msiDatabaseFile); | ||
1175 | |||
1176 | // apply the transform to the database and retrieve the modifications | ||
1177 | using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) | ||
1178 | { | ||
1179 | try | ||
1180 | { | ||
1181 | // apply the transform | ||
1182 | msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All); | ||
1183 | |||
1184 | // commit the database to guard against weird errors with streams | ||
1185 | msiDatabase.Commit(); | ||
1186 | } | ||
1187 | catch (Win32Exception ex) | ||
1188 | { | ||
1189 | if (0x65B == ex.NativeErrorCode) | ||
1190 | { | ||
1191 | // this commonly happens when the transform was built | ||
1192 | // against a database schema different from the internal | ||
1193 | // table definitions | ||
1194 | throw new WixException(WixErrors.TransformSchemaMismatch()); | ||
1195 | } | ||
1196 | } | ||
1197 | |||
1198 | // unbind the database | ||
1199 | Output output = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); | ||
1200 | |||
1201 | // index all the rows to easily find modified rows | ||
1202 | Hashtable rows = new Hashtable(); | ||
1203 | foreach (Table table in output.Tables) | ||
1204 | { | ||
1205 | foreach (Row row in table.Rows) | ||
1206 | { | ||
1207 | rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); | ||
1208 | } | ||
1209 | } | ||
1210 | |||
1211 | // process the _TransformView rows into transform rows | ||
1212 | foreach (Row row in transformViewTable.Rows) | ||
1213 | { | ||
1214 | string tableName = (string)row[0]; | ||
1215 | string columnName = (string)row[1]; | ||
1216 | string primaryKeys = (string)row[2]; | ||
1217 | |||
1218 | Table table = transform.EnsureTable(this.tableDefinitions[tableName]); | ||
1219 | |||
1220 | if ("CREATE" == columnName) // added table | ||
1221 | { | ||
1222 | table.Operation = TableOperation.Add; | ||
1223 | } | ||
1224 | else if ("DELETE" == columnName) // deleted row | ||
1225 | { | ||
1226 | Row deletedRow = this.CreateRow(table, primaryKeys, false); | ||
1227 | deletedRow.Operation = RowOperation.Delete; | ||
1228 | } | ||
1229 | else if ("DROP" == columnName) // dropped table | ||
1230 | { | ||
1231 | table.Operation = TableOperation.Drop; | ||
1232 | } | ||
1233 | else if ("INSERT" == columnName) // added row | ||
1234 | { | ||
1235 | string index = String.Concat(tableName, ':', primaryKeys); | ||
1236 | Row addedRow = (Row)rows[index]; | ||
1237 | addedRow.Operation = RowOperation.Add; | ||
1238 | table.Rows.Add(addedRow); | ||
1239 | } | ||
1240 | else if (null != primaryKeys) // modified row | ||
1241 | { | ||
1242 | string index = String.Concat(tableName, ':', primaryKeys); | ||
1243 | |||
1244 | // the _TransformView table includes information for added rows | ||
1245 | // that looks like modified rows so it sometimes needs to be ignored | ||
1246 | if (!addedRows.Contains(index)) | ||
1247 | { | ||
1248 | Row modifiedRow = (Row)rows[index]; | ||
1249 | |||
1250 | // mark the field as modified | ||
1251 | int indexOfModifiedValue = -1; | ||
1252 | for (int i = 0; i < modifiedRow.TableDefinition.Columns.Count; ++i) | ||
1253 | { | ||
1254 | if (columnName.Equals(modifiedRow.TableDefinition.Columns[i].Name, StringComparison.Ordinal)) | ||
1255 | { | ||
1256 | indexOfModifiedValue = i; | ||
1257 | break; | ||
1258 | } | ||
1259 | } | ||
1260 | modifiedRow.Fields[indexOfModifiedValue].Modified = true; | ||
1261 | |||
1262 | // move the modified row into the transform the first time its encountered | ||
1263 | if (RowOperation.None == modifiedRow.Operation) | ||
1264 | { | ||
1265 | modifiedRow.Operation = RowOperation.Modify; | ||
1266 | table.Rows.Add(modifiedRow); | ||
1267 | } | ||
1268 | } | ||
1269 | } | ||
1270 | else // added column | ||
1271 | { | ||
1272 | ColumnDefinition column = table.Definition.Columns.Single(c => c.Name.Equals(columnName, StringComparison.Ordinal)); | ||
1273 | column.Added = true; | ||
1274 | } | ||
1275 | } | ||
1276 | } | ||
1277 | |||
1278 | return transform; | ||
1279 | } | ||
1280 | |||
1281 | private void GenerateDatabase(Output output, string databaseFile) | ||
1282 | { | ||
1283 | GenerateDatabaseCommand command = new GenerateDatabaseCommand(); | ||
1284 | command.Extensions = Enumerable.Empty<IBinderExtension>(); | ||
1285 | command.FileManagers = Enumerable.Empty<IBinderFileManager>(); | ||
1286 | command.Output = output; | ||
1287 | command.OutputPath = databaseFile; | ||
1288 | command.KeepAddedColumns = true; | ||
1289 | command.UseSubDirectory = false; | ||
1290 | command.SuppressAddingValidationRows = true; | ||
1291 | command.TableDefinitions = this.tableDefinitions; | ||
1292 | command.TempFilesLocation = this.TempFilesLocation; | ||
1293 | command.Codepage = -1; | ||
1294 | command.Execute(); | ||
1295 | } | ||
1296 | |||
1297 | /// <summary> | ||
1298 | /// Unbind a bundle. | ||
1299 | /// </summary> | ||
1300 | /// <param name="bundleFile">The bundle file.</param> | ||
1301 | /// <param name="exportBasePath">The path where files should be exported.</param> | ||
1302 | /// <returns>The unbound bundle.</returns> | ||
1303 | private Output UnbindBundle(string bundleFile, string exportBasePath) | ||
1304 | { | ||
1305 | string uxExtractPath = Path.Combine(exportBasePath, "UX"); | ||
1306 | string acExtractPath = Path.Combine(exportBasePath, "AttachedContainer"); | ||
1307 | |||
1308 | using (BurnReader reader = BurnReader.Open(bundleFile)) | ||
1309 | { | ||
1310 | reader.ExtractUXContainer(uxExtractPath, this.TempFilesLocation); | ||
1311 | reader.ExtractAttachedContainer(acExtractPath, this.TempFilesLocation); | ||
1312 | } | ||
1313 | 128 | ||
1314 | return null; | 129 | return null; |
1315 | } | 130 | } |
1316 | |||
1317 | /// <summary> | ||
1318 | /// Create a deleted or modified row. | ||
1319 | /// </summary> | ||
1320 | /// <param name="table">The table containing the row.</param> | ||
1321 | /// <param name="primaryKeys">The primary keys of the row.</param> | ||
1322 | /// <param name="setRequiredFields">Option to set all required fields with placeholder values.</param> | ||
1323 | /// <returns>The new row.</returns> | ||
1324 | private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) | ||
1325 | { | ||
1326 | Row row = table.CreateRow(null); | ||
1327 | |||
1328 | string[] primaryKeyParts = primaryKeys.Split('\t'); | ||
1329 | int primaryKeyPartIndex = 0; | ||
1330 | |||
1331 | for (int i = 0; i < table.Definition.Columns.Count; i++) | ||
1332 | { | ||
1333 | ColumnDefinition columnDefinition = table.Definition.Columns[i]; | ||
1334 | |||
1335 | if (columnDefinition.PrimaryKey) | ||
1336 | { | ||
1337 | if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) | ||
1338 | { | ||
1339 | row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); | ||
1340 | } | ||
1341 | else | ||
1342 | { | ||
1343 | row[i] = primaryKeyParts[primaryKeyPartIndex++]; | ||
1344 | } | ||
1345 | } | ||
1346 | else if (setRequiredFields) | ||
1347 | { | ||
1348 | if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) | ||
1349 | { | ||
1350 | row[i] = 1; | ||
1351 | } | ||
1352 | else if (ColumnType.Object == columnDefinition.Type) | ||
1353 | { | ||
1354 | if (null == this.emptyFile) | ||
1355 | { | ||
1356 | this.emptyFile = Path.GetTempFileName() + ".empty"; | ||
1357 | using (FileStream fileStream = File.Create(this.emptyFile)) | ||
1358 | { | ||
1359 | } | ||
1360 | } | ||
1361 | |||
1362 | row[i] = this.emptyFile; | ||
1363 | } | ||
1364 | else | ||
1365 | { | ||
1366 | row[i] = "1"; | ||
1367 | } | ||
1368 | } | ||
1369 | } | ||
1370 | |||
1371 | return row; | ||
1372 | } | ||
1373 | |||
1374 | /// <summary> | ||
1375 | /// Extract the cabinets from a database. | ||
1376 | /// </summary> | ||
1377 | /// <param name="output">The output to use when finding cabinets.</param> | ||
1378 | /// <param name="database">The database containing the cabinets.</param> | ||
1379 | /// <param name="databaseFile">The location of the database file.</param> | ||
1380 | /// <param name="exportBasePath">The path where the files should be exported.</param> | ||
1381 | private void ExtractCabinets(Output output, Database database, string databaseFile, string exportBasePath) | ||
1382 | { | ||
1383 | string databaseBasePath = Path.GetDirectoryName(databaseFile); | ||
1384 | StringCollection cabinetFiles = new StringCollection(); | ||
1385 | SortedList embeddedCabinets = new SortedList(); | ||
1386 | |||
1387 | // index all of the cabinet files | ||
1388 | if (OutputType.Module == output.Type) | ||
1389 | { | ||
1390 | embeddedCabinets.Add(0, "MergeModule.CABinet"); | ||
1391 | } | ||
1392 | else if (null != output.Tables["Media"]) | ||
1393 | { | ||
1394 | foreach (MediaRow mediaRow in output.Tables["Media"].Rows) | ||
1395 | { | ||
1396 | if (null != mediaRow.Cabinet) | ||
1397 | { | ||
1398 | if (OutputType.Product == output.Type || | ||
1399 | (OutputType.Transform == output.Type && RowOperation.Add == mediaRow.Operation)) | ||
1400 | { | ||
1401 | if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) | ||
1402 | { | ||
1403 | embeddedCabinets.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1)); | ||
1404 | } | ||
1405 | else | ||
1406 | { | ||
1407 | cabinetFiles.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet)); | ||
1408 | } | ||
1409 | } | ||
1410 | } | ||
1411 | } | ||
1412 | } | ||
1413 | |||
1414 | // extract the embedded cabinet files from the database | ||
1415 | if (0 < embeddedCabinets.Count) | ||
1416 | { | ||
1417 | using (View streamsView = database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?")) | ||
1418 | { | ||
1419 | foreach (int diskId in embeddedCabinets.Keys) | ||
1420 | { | ||
1421 | using (Record record = new Record(1)) | ||
1422 | { | ||
1423 | record.SetString(1, (string)embeddedCabinets[diskId]); | ||
1424 | streamsView.Execute(record); | ||
1425 | } | ||
1426 | |||
1427 | using (Record record = streamsView.Fetch()) | ||
1428 | { | ||
1429 | if (null != record) | ||
1430 | { | ||
1431 | // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not case-sensitive, | ||
1432 | // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work | ||
1433 | string cabinetFile = Path.Combine(this.TempFilesLocation, String.Concat("Media", Path.DirectorySeparatorChar, diskId.ToString(CultureInfo.InvariantCulture), ".cab")); | ||
1434 | |||
1435 | // ensure the parent directory exists | ||
1436 | System.IO.Directory.CreateDirectory(Path.GetDirectoryName(cabinetFile)); | ||
1437 | |||
1438 | using (FileStream fs = System.IO.File.Create(cabinetFile)) | ||
1439 | { | ||
1440 | int bytesRead; | ||
1441 | byte[] buffer = new byte[512]; | ||
1442 | |||
1443 | while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length))) | ||
1444 | { | ||
1445 | fs.Write(buffer, 0, bytesRead); | ||
1446 | } | ||
1447 | } | ||
1448 | |||
1449 | cabinetFiles.Add(cabinetFile); | ||
1450 | } | ||
1451 | else | ||
1452 | { | ||
1453 | // TODO: warning about missing embedded cabinet | ||
1454 | } | ||
1455 | } | ||
1456 | } | ||
1457 | } | ||
1458 | } | ||
1459 | |||
1460 | // extract the cabinet files | ||
1461 | if (0 < cabinetFiles.Count) | ||
1462 | { | ||
1463 | string fileDirectory = Path.Combine(exportBasePath, "File"); | ||
1464 | |||
1465 | // delete the directory and its files to prevent cab extraction due to an existing file | ||
1466 | if (Directory.Exists(fileDirectory)) | ||
1467 | { | ||
1468 | Directory.Delete(fileDirectory, true); | ||
1469 | } | ||
1470 | |||
1471 | // ensure the directory exists or extraction will fail | ||
1472 | Directory.CreateDirectory(fileDirectory); | ||
1473 | |||
1474 | foreach (string cabinetFile in cabinetFiles) | ||
1475 | { | ||
1476 | using (WixExtractCab extractCab = new WixExtractCab()) | ||
1477 | { | ||
1478 | try | ||
1479 | { | ||
1480 | extractCab.Extract(cabinetFile, fileDirectory); | ||
1481 | } | ||
1482 | catch (FileNotFoundException) | ||
1483 | { | ||
1484 | throw new WixException(WixErrors.FileNotFound(new SourceLineNumber(databaseFile), cabinetFile)); | ||
1485 | } | ||
1486 | } | ||
1487 | } | ||
1488 | } | ||
1489 | } | ||
1490 | } | 131 | } |
1491 | } | 132 | } |
diff --git a/src/WixToolset.Core/Uuid.cs b/src/WixToolset.Core/Uuid.cs index 2e599793..d512d92f 100644 --- a/src/WixToolset.Core/Uuid.cs +++ b/src/WixToolset.Core/Uuid.cs | |||
@@ -10,7 +10,7 @@ namespace WixToolset | |||
10 | /// <summary> | 10 | /// <summary> |
11 | /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. | 11 | /// Implementation of RFC 4122 - A Universally Unique Identifier (UUID) URN Namespace. |
12 | /// </summary> | 12 | /// </summary> |
13 | internal sealed class Uuid | 13 | public sealed class Uuid |
14 | { | 14 | { |
15 | /// <summary> | 15 | /// <summary> |
16 | /// Protect the constructor. | 16 | /// Protect the constructor. |
diff --git a/src/WixToolset.Core/Validator.cs b/src/WixToolset.Core/Validator.cs deleted file mode 100644 index 6420b9b7..00000000 --- a/src/WixToolset.Core/Validator.cs +++ /dev/null | |||
@@ -1,401 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Collections.Specialized; | ||
8 | using System.ComponentModel; | ||
9 | using System.Diagnostics.CodeAnalysis; | ||
10 | using System.Globalization; | ||
11 | using System.IO; | ||
12 | using System.Threading; | ||
13 | using WixToolset.Data; | ||
14 | using WixToolset.Extensibility; | ||
15 | using WixToolset.Msi; | ||
16 | using WixToolset.Core.Native; | ||
17 | |||
18 | /// <summary> | ||
19 | /// Runs internal consistency evaluators (ICEs) from cub files against a database. | ||
20 | /// </summary> | ||
21 | public sealed class Validator : IMessageHandler | ||
22 | { | ||
23 | private string actionName; | ||
24 | private StringCollection cubeFiles; | ||
25 | private ValidatorExtension extension; | ||
26 | private string[] ices; | ||
27 | private Output output; | ||
28 | private string[] suppressedICEs; | ||
29 | private InstallUIHandler validationUIHandler; | ||
30 | private bool validationSessionComplete; | ||
31 | |||
32 | /// <summary> | ||
33 | /// Instantiate a new Validator. | ||
34 | /// </summary> | ||
35 | public Validator() | ||
36 | { | ||
37 | this.cubeFiles = new StringCollection(); | ||
38 | this.extension = new ValidatorExtension(); | ||
39 | this.validationUIHandler = new InstallUIHandler(this.ValidationUIHandler); | ||
40 | } | ||
41 | |||
42 | /// <summary> | ||
43 | /// Gets or sets a <see cref="ValidatorExtension"/> that directs messages from the validator. | ||
44 | /// </summary> | ||
45 | /// <value>A <see cref="ValidatorExtension"/> that directs messages from the validator.</value> | ||
46 | public ValidatorExtension Extension | ||
47 | { | ||
48 | get { return this.extension; } | ||
49 | set { this.extension = value; } | ||
50 | } | ||
51 | |||
52 | /// <summary> | ||
53 | /// Gets or sets the list of ICEs to run. | ||
54 | /// </summary> | ||
55 | /// <value>The list of ICEs.</value> | ||
56 | [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] | ||
57 | public string[] ICEs | ||
58 | { | ||
59 | get { return this.ices; } | ||
60 | set { this.ices = value; } | ||
61 | } | ||
62 | |||
63 | /// <summary> | ||
64 | /// Gets or sets the output used for finding source line information. | ||
65 | /// </summary> | ||
66 | /// <value>The output used for finding source line information.</value> | ||
67 | public Output Output | ||
68 | { | ||
69 | // cache Output object until validation for changes in extension | ||
70 | get { return this.output; } | ||
71 | set { this.output = value; } | ||
72 | } | ||
73 | |||
74 | /// <summary> | ||
75 | /// Gets or sets the suppressed ICEs. | ||
76 | /// </summary> | ||
77 | /// <value>The suppressed ICEs.</value> | ||
78 | [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] | ||
79 | public string[] SuppressedICEs | ||
80 | { | ||
81 | get { return this.suppressedICEs; } | ||
82 | set { this.suppressedICEs = value; } | ||
83 | } | ||
84 | |||
85 | /// <summary> | ||
86 | /// Sets the temporary path for the Binder. | ||
87 | /// </summary> | ||
88 | public string TempFilesLocation { private get; set; } | ||
89 | |||
90 | /// <summary> | ||
91 | /// Add a cube file to the validation run. | ||
92 | /// </summary> | ||
93 | /// <param name="cubeFile">A cube file.</param> | ||
94 | public void AddCubeFile(string cubeFile) | ||
95 | { | ||
96 | this.cubeFiles.Add(cubeFile); | ||
97 | } | ||
98 | |||
99 | /// <summary> | ||
100 | /// Validate a database. | ||
101 | /// </summary> | ||
102 | /// <param name="databaseFile">The database to validate.</param> | ||
103 | /// <returns>true if validation succeeded; false otherwise.</returns> | ||
104 | public void Validate(string databaseFile) | ||
105 | { | ||
106 | Dictionary<string, string> indexedICEs = new Dictionary<string, string>(); | ||
107 | Dictionary<string, string> indexedSuppressedICEs = new Dictionary<string, string>(); | ||
108 | int previousUILevel = (int)InstallUILevels.Basic; | ||
109 | IntPtr previousHwnd = IntPtr.Zero; | ||
110 | InstallUIHandler previousUIHandler = null; | ||
111 | |||
112 | if (null == databaseFile) | ||
113 | { | ||
114 | throw new ArgumentNullException("databaseFile"); | ||
115 | } | ||
116 | |||
117 | // initialize the validator extension | ||
118 | this.extension.DatabaseFile = databaseFile; | ||
119 | this.extension.Output = this.output; | ||
120 | this.extension.InitializeValidator(); | ||
121 | |||
122 | // Ensure the temporary files can be created. | ||
123 | Directory.CreateDirectory(this.TempFilesLocation); | ||
124 | |||
125 | // index the ICEs | ||
126 | if (null != this.ices) | ||
127 | { | ||
128 | foreach (string ice in this.ices) | ||
129 | { | ||
130 | indexedICEs[ice] = null; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | // index the suppressed ICEs | ||
135 | if (null != this.suppressedICEs) | ||
136 | { | ||
137 | foreach (string suppressedICE in this.suppressedICEs) | ||
138 | { | ||
139 | indexedSuppressedICEs[suppressedICE] = null; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | // copy the database to a temporary location so it can be manipulated | ||
144 | string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(databaseFile)); | ||
145 | File.Copy(databaseFile, tempDatabaseFile); | ||
146 | |||
147 | // remove the read-only property from the temporary database | ||
148 | FileAttributes attributes = File.GetAttributes(tempDatabaseFile); | ||
149 | File.SetAttributes(tempDatabaseFile, attributes & ~FileAttributes.ReadOnly); | ||
150 | |||
151 | Mutex mutex = new Mutex(false, "WixValidator"); | ||
152 | try | ||
153 | { | ||
154 | if (!mutex.WaitOne(0, false)) | ||
155 | { | ||
156 | this.OnMessage(WixVerboses.ValidationSerialized()); | ||
157 | mutex.WaitOne(); | ||
158 | } | ||
159 | |||
160 | using (Database database = new Database(tempDatabaseFile, OpenDatabase.Direct)) | ||
161 | { | ||
162 | bool propertyTableExists = database.TableExists("Property"); | ||
163 | string productCode = null; | ||
164 | |||
165 | // remove the product code from the database before opening a session to prevent opening an installed product | ||
166 | if (propertyTableExists) | ||
167 | { | ||
168 | using (View view = database.OpenExecuteView("SELECT `Value` FROM `Property` WHERE Property = 'ProductCode'")) | ||
169 | { | ||
170 | using (Record record = view.Fetch()) | ||
171 | { | ||
172 | if (null != record) | ||
173 | { | ||
174 | productCode = record.GetString(1); | ||
175 | |||
176 | using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) | ||
177 | { | ||
178 | } | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | // merge in the cube databases | ||
185 | foreach (string cubeFile in this.cubeFiles) | ||
186 | { | ||
187 | try | ||
188 | { | ||
189 | using (Database cubeDatabase = new Database(cubeFile, OpenDatabase.ReadOnly)) | ||
190 | { | ||
191 | try | ||
192 | { | ||
193 | database.Merge(cubeDatabase, "MergeConflicts"); | ||
194 | } | ||
195 | catch | ||
196 | { | ||
197 | // ignore merge errors since they are expected in the _Validation table | ||
198 | } | ||
199 | } | ||
200 | } | ||
201 | catch (Win32Exception e) | ||
202 | { | ||
203 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED | ||
204 | { | ||
205 | throw new WixException(WixErrors.CubeFileNotFound(cubeFile)); | ||
206 | } | ||
207 | |||
208 | throw; | ||
209 | } | ||
210 | } | ||
211 | |||
212 | // commit the database before proceeding to ensure the streams don't get confused | ||
213 | database.Commit(); | ||
214 | |||
215 | // the property table may have been added to the database | ||
216 | // from a cub database without the proper validation rows | ||
217 | if (!propertyTableExists) | ||
218 | { | ||
219 | using (View view = database.OpenExecuteView("DROP table `Property`")) | ||
220 | { | ||
221 | } | ||
222 | } | ||
223 | |||
224 | // get all the action names for ICEs which have not been suppressed | ||
225 | List<string> actions = new List<string>(); | ||
226 | using (View view = database.OpenExecuteView("SELECT `Action` FROM `_ICESequence` ORDER BY `Sequence`")) | ||
227 | { | ||
228 | while (true) | ||
229 | { | ||
230 | using (Record record = view.Fetch()) | ||
231 | { | ||
232 | if (null == record) | ||
233 | { | ||
234 | break; | ||
235 | } | ||
236 | |||
237 | string action = record.GetString(1); | ||
238 | |||
239 | if (!indexedSuppressedICEs.ContainsKey(action)) | ||
240 | { | ||
241 | actions.Add(action); | ||
242 | } | ||
243 | } | ||
244 | } | ||
245 | } | ||
246 | |||
247 | if (0 != indexedICEs.Count) | ||
248 | { | ||
249 | // Walk backwards and remove those that arent in the list | ||
250 | for (int i = actions.Count - 1; 0 <= i; i--) | ||
251 | { | ||
252 | if (!indexedICEs.ContainsKey(actions[i])) | ||
253 | { | ||
254 | actions.RemoveAt(i); | ||
255 | } | ||
256 | } | ||
257 | } | ||
258 | |||
259 | // disable the internal UI handler and set an external UI handler | ||
260 | previousUILevel = Installer.SetInternalUI((int)InstallUILevels.None, ref previousHwnd); | ||
261 | previousUIHandler = Installer.SetExternalUI(this.validationUIHandler, (int)InstallLogModes.Error | (int)InstallLogModes.Warning | (int)InstallLogModes.User, IntPtr.Zero); | ||
262 | |||
263 | // create a session for running the ICEs | ||
264 | this.validationSessionComplete = false; | ||
265 | using (Session session = new Session(database)) | ||
266 | { | ||
267 | // add the product code back into the database | ||
268 | if (null != productCode) | ||
269 | { | ||
270 | // some CUBs erroneously have a ProductCode property, so delete it if we just picked one up | ||
271 | using (View dropProductCodeView = database.OpenExecuteView("DELETE FROM `Property` WHERE `Property` = 'ProductCode'")) | ||
272 | { | ||
273 | } | ||
274 | |||
275 | using (View view = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "INSERT INTO `Property` (`Property`, `Value`) VALUES ('ProductCode', '{0}')", productCode))) | ||
276 | { | ||
277 | } | ||
278 | } | ||
279 | |||
280 | foreach (string action in actions) | ||
281 | { | ||
282 | this.actionName = action; | ||
283 | try | ||
284 | { | ||
285 | session.DoAction(action); | ||
286 | } | ||
287 | catch (Win32Exception e) | ||
288 | { | ||
289 | if (!Messaging.Instance.EncounteredError) | ||
290 | { | ||
291 | throw e; | ||
292 | } | ||
293 | // TODO: Review why this was clearing the error state when an exception had happened but an error was already encountered. That's weird. | ||
294 | //else | ||
295 | //{ | ||
296 | // this.encounteredError = false; | ||
297 | //} | ||
298 | } | ||
299 | this.actionName = null; | ||
300 | } | ||
301 | |||
302 | // Mark the validation session complete so we ignore any messages that MSI may fire | ||
303 | // during session clean-up. | ||
304 | this.validationSessionComplete = true; | ||
305 | } | ||
306 | } | ||
307 | } | ||
308 | catch (Win32Exception e) | ||
309 | { | ||
310 | // avoid displaying errors twice since one may have already occurred in the UI handler | ||
311 | if (!Messaging.Instance.EncounteredError) | ||
312 | { | ||
313 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED | ||
314 | { | ||
315 | // databaseFile is not passed since during light | ||
316 | // this would be the temporary copy and there would be | ||
317 | // no final output since the error occured; during smoke | ||
318 | // they should know the path passed into smoke | ||
319 | this.OnMessage(WixErrors.ValidationFailedToOpenDatabase()); | ||
320 | } | ||
321 | else if (0x64D == e.NativeErrorCode) | ||
322 | { | ||
323 | this.OnMessage(WixErrors.ValidationFailedDueToLowMsiEngine()); | ||
324 | } | ||
325 | else if (0x654 == e.NativeErrorCode) | ||
326 | { | ||
327 | this.OnMessage(WixErrors.ValidationFailedDueToInvalidPackage()); | ||
328 | } | ||
329 | else if (0x658 == e.NativeErrorCode) | ||
330 | { | ||
331 | this.OnMessage(WixErrors.ValidationFailedDueToMultilanguageMergeModule()); | ||
332 | } | ||
333 | else if (0x659 == e.NativeErrorCode) | ||
334 | { | ||
335 | this.OnMessage(WixWarnings.ValidationFailedDueToSystemPolicy()); | ||
336 | } | ||
337 | else | ||
338 | { | ||
339 | string msgTemp = e.Message; | ||
340 | |||
341 | if (null != this.actionName) | ||
342 | { | ||
343 | msgTemp = String.Concat("Action - '", this.actionName, "' ", e.Message); | ||
344 | } | ||
345 | |||
346 | this.OnMessage(WixErrors.Win32Exception(e.NativeErrorCode, msgTemp)); | ||
347 | } | ||
348 | } | ||
349 | } | ||
350 | finally | ||
351 | { | ||
352 | Installer.SetExternalUI(previousUIHandler, 0, IntPtr.Zero); | ||
353 | Installer.SetInternalUI(previousUILevel, ref previousHwnd); | ||
354 | |||
355 | this.validationSessionComplete = false; // no validation session at this point, so reset the completion flag. | ||
356 | |||
357 | mutex.ReleaseMutex(); | ||
358 | this.cubeFiles.Clear(); | ||
359 | this.extension.FinalizeValidator(); | ||
360 | } | ||
361 | } | ||
362 | |||
363 | /// <summary> | ||
364 | /// Sends a message to the message delegate if there is one. | ||
365 | /// </summary> | ||
366 | /// <param name="mea">Message event arguments.</param> | ||
367 | public void OnMessage(MessageEventArgs e) | ||
368 | { | ||
369 | Messaging.Instance.OnMessage(e); | ||
370 | this.extension.OnMessage(e); | ||
371 | } | ||
372 | |||
373 | /// <summary> | ||
374 | /// The validation external UI handler. | ||
375 | /// </summary> | ||
376 | /// <param name="context">Pointer to an application context. | ||
377 | /// This parameter can be used for error checking.</param> | ||
378 | /// <param name="messageType">Specifies a combination of one message box style, | ||
379 | /// one message box icon type, one default button, and one installation message type.</param> | ||
380 | /// <param name="message">Specifies the message text.</param> | ||
381 | /// <returns>-1 for an error, 0 if no action was taken, 1 if OK, 3 to abort.</returns> | ||
382 | private int ValidationUIHandler(IntPtr context, uint messageType, string message) | ||
383 | { | ||
384 | try | ||
385 | { | ||
386 | // If we're getting messges during the validation session, send them to | ||
387 | // the extension. Otherwise, ignore the messages. | ||
388 | if (!this.validationSessionComplete) | ||
389 | { | ||
390 | this.extension.Log(message, this.actionName); | ||
391 | } | ||
392 | } | ||
393 | catch (WixException ex) | ||
394 | { | ||
395 | this.OnMessage(ex.Error); | ||
396 | } | ||
397 | |||
398 | return 1; | ||
399 | } | ||
400 | } | ||
401 | } | ||
diff --git a/src/WixToolset.Core/VerifyInterop.cs b/src/WixToolset.Core/VerifyInterop.cs deleted file mode 100644 index 81fbec65..00000000 --- a/src/WixToolset.Core/VerifyInterop.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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Runtime.CompilerServices; | ||
8 | using System.Runtime.InteropServices; | ||
9 | |||
10 | internal class VerifyInterop | ||
11 | { | ||
12 | internal const string GenericVerify2 = "00AAC56B-CD44-11d0-8CC2-00C04FC295EE"; | ||
13 | internal const uint WTD_UI_NONE = 2; | ||
14 | internal const uint WTD_REVOKE_NONE = 0; | ||
15 | internal const uint WTD_CHOICE_CATALOG = 2; | ||
16 | internal const uint WTD_STATEACTION_VERIFY = 1; | ||
17 | internal const uint WTD_REVOCATION_CHECK_NONE = 0x10; | ||
18 | internal const int ErrorInsufficientBuffer = 122; | ||
19 | |||
20 | [StructLayout(LayoutKind.Sequential)] | ||
21 | internal struct WinTrustData | ||
22 | { | ||
23 | internal uint cbStruct; | ||
24 | internal IntPtr pPolicyCallbackData; | ||
25 | internal IntPtr pSIPClientData; | ||
26 | internal uint dwUIChoice; | ||
27 | internal uint fdwRevocationChecks; | ||
28 | internal uint dwUnionChoice; | ||
29 | internal IntPtr pCatalog; | ||
30 | internal uint dwStateAction; | ||
31 | internal IntPtr hWVTStateData; | ||
32 | [MarshalAs(UnmanagedType.LPWStr)] | ||
33 | internal string pwszURLReference; | ||
34 | internal uint dwProvFlags; | ||
35 | internal uint dwUIContext; | ||
36 | } | ||
37 | |||
38 | [StructLayout(LayoutKind.Sequential)] | ||
39 | internal struct WinTrustCatalogInfo | ||
40 | { | ||
41 | internal uint cbStruct; | ||
42 | internal uint dwCatalogVersion; | ||
43 | [MarshalAs(UnmanagedType.LPWStr)] | ||
44 | internal string pcwszCatalogFilePath; | ||
45 | [MarshalAs(UnmanagedType.LPWStr)] | ||
46 | internal string pcwszMemberTag; | ||
47 | [MarshalAs(UnmanagedType.LPWStr)] | ||
48 | internal string pcwszMemberFilePath; | ||
49 | internal IntPtr hMemberFile; | ||
50 | internal IntPtr pbCalculatedFileHash; | ||
51 | internal uint cbCalculatedFileHash; | ||
52 | internal IntPtr pcCatalogContext; | ||
53 | } | ||
54 | |||
55 | [DllImport("wintrust.dll", SetLastError = true)] | ||
56 | internal static extern long WinVerifyTrust(IntPtr windowHandle, ref Guid actionGuid, ref WinTrustData trustData); | ||
57 | |||
58 | [DllImport("wintrust.dll", SetLastError = true)] | ||
59 | [return: MarshalAs(UnmanagedType.Bool)] | ||
60 | internal static extern bool CryptCATAdminCalcHashFromFileHandle( | ||
61 | IntPtr fileHandle, | ||
62 | [In, Out] | ||
63 | ref uint hashSize, | ||
64 | [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] | ||
65 | byte[] hashBytes, | ||
66 | uint flags); | ||
67 | } | ||
68 | } | ||
diff --git a/src/WixToolset.Core/WixComponentSearchInfo.cs b/src/WixToolset.Core/WixComponentSearchInfo.cs deleted file mode 100644 index dfd5d8ba..00000000 --- a/src/WixToolset.Core/WixComponentSearchInfo.cs +++ /dev/null | |||
@@ -1,64 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Xml; | ||
7 | using WixToolset.Data; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Utility class for all WixComponentSearches. | ||
11 | /// </summary> | ||
12 | internal class WixComponentSearchInfo : WixSearchInfo | ||
13 | { | ||
14 | public WixComponentSearchInfo(Row row) | ||
15 | : this((string)row[0], (string)row[1], (string)row[2], (int)row[3]) | ||
16 | { | ||
17 | } | ||
18 | |||
19 | public WixComponentSearchInfo(string id, string guid, string productCode, int attributes) | ||
20 | : base(id) | ||
21 | { | ||
22 | this.Guid = guid; | ||
23 | this.ProductCode = productCode; | ||
24 | this.Attributes = (WixComponentSearchAttributes)attributes; | ||
25 | } | ||
26 | |||
27 | public string Guid { get; private set; } | ||
28 | public string ProductCode { get; private set; } | ||
29 | public WixComponentSearchAttributes Attributes { get; private set; } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Generates Burn manifest and ParameterInfo-style markup for a component search. | ||
33 | /// </summary> | ||
34 | /// <param name="writer"></param> | ||
35 | public override void WriteXml(XmlTextWriter writer) | ||
36 | { | ||
37 | writer.WriteStartElement("MsiComponentSearch"); | ||
38 | this.WriteWixSearchAttributes(writer); | ||
39 | |||
40 | writer.WriteAttributeString("ComponentId", this.Guid); | ||
41 | |||
42 | if (!String.IsNullOrEmpty(this.ProductCode)) | ||
43 | { | ||
44 | writer.WriteAttributeString("ProductCode", this.ProductCode); | ||
45 | } | ||
46 | |||
47 | if (0 != (this.Attributes & WixComponentSearchAttributes.KeyPath)) | ||
48 | { | ||
49 | writer.WriteAttributeString("Type", "keyPath"); | ||
50 | } | ||
51 | else if (0 != (this.Attributes & WixComponentSearchAttributes.State)) | ||
52 | { | ||
53 | writer.WriteAttributeString("Type", "state"); | ||
54 | } | ||
55 | else if (0 != (this.Attributes & WixComponentSearchAttributes.WantDirectory)) | ||
56 | { | ||
57 | writer.WriteAttributeString("Type", "directory"); | ||
58 | } | ||
59 | |||
60 | writer.WriteEndElement(); | ||
61 | } | ||
62 | } | ||
63 | |||
64 | } | ||
diff --git a/src/WixToolset.Core/WixFileSearchInfo.cs b/src/WixToolset.Core/WixFileSearchInfo.cs deleted file mode 100644 index e53f7bf7..00000000 --- a/src/WixToolset.Core/WixFileSearchInfo.cs +++ /dev/null | |||
@@ -1,54 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Xml; | ||
7 | using WixToolset.Data; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Utility class for all WixFileSearches (file and directory searches). | ||
11 | /// </summary> | ||
12 | internal class WixFileSearchInfo : WixSearchInfo | ||
13 | { | ||
14 | public WixFileSearchInfo(Row row) | ||
15 | : this((string)row[0], (string)row[1], (int)row[9]) | ||
16 | { | ||
17 | } | ||
18 | |||
19 | public WixFileSearchInfo(string id, string path, int attributes) | ||
20 | : base(id) | ||
21 | { | ||
22 | this.Path = path; | ||
23 | this.Attributes = (WixFileSearchAttributes)attributes; | ||
24 | } | ||
25 | |||
26 | public string Path { get; private set; } | ||
27 | public WixFileSearchAttributes Attributes { get; private set; } | ||
28 | |||
29 | /// <summary> | ||
30 | /// Generates Burn manifest and ParameterInfo-style markup for a file/directory search. | ||
31 | /// </summary> | ||
32 | /// <param name="writer"></param> | ||
33 | public override void WriteXml(XmlTextWriter writer) | ||
34 | { | ||
35 | writer.WriteStartElement((0 == (this.Attributes & WixFileSearchAttributes.IsDirectory)) ? "FileSearch" : "DirectorySearch"); | ||
36 | this.WriteWixSearchAttributes(writer); | ||
37 | writer.WriteAttributeString("Path", this.Path); | ||
38 | if (WixFileSearchAttributes.WantExists == (this.Attributes & WixFileSearchAttributes.WantExists)) | ||
39 | { | ||
40 | writer.WriteAttributeString("Type", "exists"); | ||
41 | } | ||
42 | else if (WixFileSearchAttributes.WantVersion == (this.Attributes & WixFileSearchAttributes.WantVersion)) | ||
43 | { | ||
44 | // Can never get here for DirectorySearch. | ||
45 | writer.WriteAttributeString("Type", "version"); | ||
46 | } | ||
47 | else | ||
48 | { | ||
49 | writer.WriteAttributeString("Type", "path"); | ||
50 | } | ||
51 | writer.WriteEndElement(); | ||
52 | } | ||
53 | } | ||
54 | } | ||
diff --git a/src/WixToolset.Core/WixProductSearchInfo.cs b/src/WixToolset.Core/WixProductSearchInfo.cs deleted file mode 100644 index 4c57d8be..00000000 --- a/src/WixToolset.Core/WixProductSearchInfo.cs +++ /dev/null | |||
@@ -1,67 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Xml; | ||
7 | using WixToolset.Data; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Utility class for all WixProductSearches. | ||
11 | /// </summary> | ||
12 | internal class WixProductSearchInfo : WixSearchInfo | ||
13 | { | ||
14 | public WixProductSearchInfo(Row row) | ||
15 | : this((string)row[0], (string)row[1], (int)row[2]) | ||
16 | { | ||
17 | } | ||
18 | |||
19 | public WixProductSearchInfo(string id, string guid, int attributes) | ||
20 | : base(id) | ||
21 | { | ||
22 | this.Guid = guid; | ||
23 | this.Attributes = (WixProductSearchAttributes)attributes; | ||
24 | } | ||
25 | |||
26 | public string Guid { get; private set; } | ||
27 | public WixProductSearchAttributes Attributes { get; private set; } | ||
28 | |||
29 | /// <summary> | ||
30 | /// Generates Burn manifest and ParameterInfo-style markup for a product search. | ||
31 | /// </summary> | ||
32 | /// <param name="writer"></param> | ||
33 | public override void WriteXml(XmlTextWriter writer) | ||
34 | { | ||
35 | writer.WriteStartElement("MsiProductSearch"); | ||
36 | this.WriteWixSearchAttributes(writer); | ||
37 | |||
38 | if (0 != (this.Attributes & WixProductSearchAttributes.UpgradeCode)) | ||
39 | { | ||
40 | writer.WriteAttributeString("UpgradeCode", this.Guid); | ||
41 | } | ||
42 | else | ||
43 | { | ||
44 | writer.WriteAttributeString("ProductCode", this.Guid); | ||
45 | } | ||
46 | |||
47 | if (0 != (this.Attributes & WixProductSearchAttributes.Version)) | ||
48 | { | ||
49 | writer.WriteAttributeString("Type", "version"); | ||
50 | } | ||
51 | else if (0 != (this.Attributes & WixProductSearchAttributes.Language)) | ||
52 | { | ||
53 | writer.WriteAttributeString("Type", "language"); | ||
54 | } | ||
55 | else if (0 != (this.Attributes & WixProductSearchAttributes.State)) | ||
56 | { | ||
57 | writer.WriteAttributeString("Type", "state"); | ||
58 | } | ||
59 | else if (0 != (this.Attributes & WixProductSearchAttributes.Assignment)) | ||
60 | { | ||
61 | writer.WriteAttributeString("Type", "assignment"); | ||
62 | } | ||
63 | |||
64 | writer.WriteEndElement(); | ||
65 | } | ||
66 | } | ||
67 | } | ||
diff --git a/src/WixToolset.Core/WixRegistrySearchInfo.cs b/src/WixToolset.Core/WixRegistrySearchInfo.cs deleted file mode 100644 index e8d7ce9b..00000000 --- a/src/WixToolset.Core/WixRegistrySearchInfo.cs +++ /dev/null | |||
@@ -1,92 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Xml; | ||
7 | using WixToolset.Data; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Utility class for all WixRegistrySearches. | ||
11 | /// </summary> | ||
12 | internal class WixRegistrySearchInfo : WixSearchInfo | ||
13 | { | ||
14 | public WixRegistrySearchInfo(Row row) | ||
15 | : this((string)row[0], (int)row[1], (string)row[2], (string)row[3], (int)row[4]) | ||
16 | { | ||
17 | } | ||
18 | |||
19 | public WixRegistrySearchInfo(string id, int root, string key, string value, int attributes) | ||
20 | : base(id) | ||
21 | { | ||
22 | this.Root = root; | ||
23 | this.Key = key; | ||
24 | this.Value = value; | ||
25 | this.Attributes = (WixRegistrySearchAttributes)attributes; | ||
26 | } | ||
27 | |||
28 | public int Root { get; private set; } | ||
29 | public string Key { get; private set; } | ||
30 | public string Value { get; private set; } | ||
31 | public WixRegistrySearchAttributes Attributes { get; private set; } | ||
32 | |||
33 | /// <summary> | ||
34 | /// Generates Burn manifest and ParameterInfo-style markup for a registry search. | ||
35 | /// </summary> | ||
36 | /// <param name="writer"></param> | ||
37 | public override void WriteXml(XmlTextWriter writer) | ||
38 | { | ||
39 | writer.WriteStartElement("RegistrySearch"); | ||
40 | this.WriteWixSearchAttributes(writer); | ||
41 | |||
42 | switch (this.Root) | ||
43 | { | ||
44 | case Core.Native.MsiInterop.MsidbRegistryRootClassesRoot: | ||
45 | writer.WriteAttributeString("Root", "HKCR"); | ||
46 | break; | ||
47 | case Core.Native.MsiInterop.MsidbRegistryRootCurrentUser: | ||
48 | writer.WriteAttributeString("Root", "HKCU"); | ||
49 | break; | ||
50 | case Core.Native.MsiInterop.MsidbRegistryRootLocalMachine: | ||
51 | writer.WriteAttributeString("Root", "HKLM"); | ||
52 | break; | ||
53 | case Core.Native.MsiInterop.MsidbRegistryRootUsers: | ||
54 | writer.WriteAttributeString("Root", "HKU"); | ||
55 | break; | ||
56 | } | ||
57 | |||
58 | writer.WriteAttributeString("Key", this.Key); | ||
59 | |||
60 | if (!String.IsNullOrEmpty(this.Value)) | ||
61 | { | ||
62 | writer.WriteAttributeString("Value", this.Value); | ||
63 | } | ||
64 | |||
65 | bool existenceOnly = 0 != (this.Attributes & WixRegistrySearchAttributes.WantExists); | ||
66 | |||
67 | writer.WriteAttributeString("Type", existenceOnly ? "exists" : "value"); | ||
68 | |||
69 | if (0 != (this.Attributes & WixRegistrySearchAttributes.Win64)) | ||
70 | { | ||
71 | writer.WriteAttributeString("Win64", "yes"); | ||
72 | } | ||
73 | |||
74 | if (!existenceOnly) | ||
75 | { | ||
76 | if (0 != (this.Attributes & WixRegistrySearchAttributes.ExpandEnvironmentVariables)) | ||
77 | { | ||
78 | writer.WriteAttributeString("ExpandEnvironment", "yes"); | ||
79 | } | ||
80 | |||
81 | // We *always* say this is VariableType="string". If we end up | ||
82 | // needing to be more specific, we will have to expand the "Format" | ||
83 | // attribute to allow "number" and "version". | ||
84 | |||
85 | writer.WriteAttributeString("VariableType", "string"); | ||
86 | } | ||
87 | |||
88 | writer.WriteEndElement(); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | } | ||
diff --git a/src/WixToolset.Core/WixSearchInfo.cs b/src/WixToolset.Core/WixSearchInfo.cs deleted file mode 100644 index 906365a2..00000000 --- a/src/WixToolset.Core/WixSearchInfo.cs +++ /dev/null | |||
@@ -1,53 +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 | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics; | ||
7 | using System.Xml; | ||
8 | using WixToolset.Data; | ||
9 | |||
10 | /// <summary> | ||
11 | /// Utility base class for all WixSearches. | ||
12 | /// </summary> | ||
13 | internal abstract class WixSearchInfo | ||
14 | { | ||
15 | public WixSearchInfo(string id) | ||
16 | { | ||
17 | this.Id = id; | ||
18 | } | ||
19 | |||
20 | public void AddWixSearchRowInfo(Row row) | ||
21 | { | ||
22 | Debug.Assert((string)row[0] == Id); | ||
23 | Variable = (string)row[1]; | ||
24 | Condition = (string)row[2]; | ||
25 | } | ||
26 | |||
27 | public string Id { get; private set; } | ||
28 | public string Variable { get; private set; } | ||
29 | public string Condition { get; private set; } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Generates Burn manifest and ParameterInfo-style markup a search. | ||
33 | /// </summary> | ||
34 | /// <param name="writer"></param> | ||
35 | public virtual void WriteXml(XmlTextWriter writer) | ||
36 | { | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// Writes attributes common to all WixSearch elements. | ||
41 | /// </summary> | ||
42 | /// <param name="writer"></param> | ||
43 | protected void WriteWixSearchAttributes(XmlTextWriter writer) | ||
44 | { | ||
45 | writer.WriteAttributeString("Id", this.Id); | ||
46 | writer.WriteAttributeString("Variable", this.Variable); | ||
47 | if (!String.IsNullOrEmpty(this.Condition)) | ||
48 | { | ||
49 | writer.WriteAttributeString("Condition", this.Condition); | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | } | ||
diff --git a/src/WixToolset.Core/WixStrings.Designer.cs b/src/WixToolset.Core/WixStrings.Designer.cs index 4ba9381a..75e2b908 100644 --- a/src/WixToolset.Core/WixStrings.Designer.cs +++ b/src/WixToolset.Core/WixStrings.Designer.cs | |||
@@ -14,7 +14,7 @@ namespace WixToolset { | |||
14 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] | 14 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] |
15 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] | 15 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] |
16 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] | 16 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] |
17 | internal class WixStrings { | 17 | public class WixStrings { |
18 | 18 | ||
19 | private static global::System.Resources.ResourceManager resourceMan; | 19 | private static global::System.Resources.ResourceManager resourceMan; |
20 | 20 | ||
@@ -127,7 +127,7 @@ namespace WixToolset { | |||
127 | /// <summary> | 127 | /// <summary> |
128 | /// Looks up a localized string similar to Could not determine ProductCode from transform summary information. | 128 | /// Looks up a localized string similar to Could not determine ProductCode from transform summary information. |
129 | /// </summary> | 129 | /// </summary> |
130 | internal static string EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo { | 130 | public static string EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo { |
131 | get { | 131 | get { |
132 | return ResourceManager.GetString("EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo", resourceCulture); | 132 | return ResourceManager.GetString("EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo", resourceCulture); |
133 | } | 133 | } |
@@ -226,7 +226,7 @@ namespace WixToolset { | |||
226 | /// <summary> | 226 | /// <summary> |
227 | /// Looks up a localized string similar to Transform authored into multiple Media '{0}' and '{1}'.. | 227 | /// Looks up a localized string similar to Transform authored into multiple Media '{0}' and '{1}'.. |
228 | /// </summary> | 228 | /// </summary> |
229 | internal static string EXP_TransformAuthoredIntoMultipleMedia { | 229 | public static string EXP_TransformAuthoredIntoMultipleMedia { |
230 | get { | 230 | get { |
231 | return ResourceManager.GetString("EXP_TransformAuthoredIntoMultipleMedia", resourceCulture); | 231 | return ResourceManager.GetString("EXP_TransformAuthoredIntoMultipleMedia", resourceCulture); |
232 | } | 232 | } |
@@ -253,7 +253,7 @@ namespace WixToolset { | |||
253 | /// <summary> | 253 | /// <summary> |
254 | /// Looks up a localized string similar to Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'. | 254 | /// Looks up a localized string similar to Encountered an unexpected error while merging '{0}'. More information about the merge and the failure can be found in the merge log: '{1}'. |
255 | /// </summary> | 255 | /// </summary> |
256 | internal static string EXP_UnexpectedMergerErrorInSourceFile { | 256 | public static string EXP_UnexpectedMergerErrorInSourceFile { |
257 | get { | 257 | get { |
258 | return ResourceManager.GetString("EXP_UnexpectedMergerErrorInSourceFile", resourceCulture); | 258 | return ResourceManager.GetString("EXP_UnexpectedMergerErrorInSourceFile", resourceCulture); |
259 | } | 259 | } |
@@ -262,7 +262,7 @@ namespace WixToolset { | |||
262 | /// <summary> | 262 | /// <summary> |
263 | /// Looks up a localized string similar to Encountered an unexpected merge error of type '{0}' for which there is currently no error message to display. More information about the merge and the failure can be found in the merge log: '{1}'. | 263 | /// Looks up a localized string similar to Encountered an unexpected merge error of type '{0}' for which there is currently no error message to display. More information about the merge and the failure can be found in the merge log: '{1}'. |
264 | /// </summary> | 264 | /// </summary> |
265 | internal static string EXP_UnexpectedMergerErrorWithType { | 265 | public static string EXP_UnexpectedMergerErrorWithType { |
266 | get { | 266 | get { |
267 | return ResourceManager.GetString("EXP_UnexpectedMergerErrorWithType", resourceCulture); | 267 | return ResourceManager.GetString("EXP_UnexpectedMergerErrorWithType", resourceCulture); |
268 | } | 268 | } |
diff --git a/src/WixToolset.Core/WixVariableResolver.cs b/src/WixToolset.Core/WixVariableResolver.cs index d437423c..357ff700 100644 --- a/src/WixToolset.Core/WixVariableResolver.cs +++ b/src/WixToolset.Core/WixVariableResolver.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 | 3 | namespace WixToolset.Core |
4 | { | 4 | { |
5 | using System; | 5 | using System; |
6 | using System.Collections.Generic; | 6 | using System.Collections.Generic; |
@@ -10,11 +10,12 @@ namespace WixToolset | |||
10 | using System.Text.RegularExpressions; | 10 | using System.Text.RegularExpressions; |
11 | using WixToolset.Data; | 11 | using WixToolset.Data; |
12 | using WixToolset.Data.Rows; | 12 | using WixToolset.Data.Rows; |
13 | using WixToolset.Extensibility; | ||
13 | 14 | ||
14 | /// <summary> | 15 | /// <summary> |
15 | /// WiX variable resolver. | 16 | /// WiX variable resolver. |
16 | /// </summary> | 17 | /// </summary> |
17 | public sealed class WixVariableResolver | 18 | internal sealed class WixVariableResolver : IBindVariableResolver |
18 | { | 19 | { |
19 | private Dictionary<string, string> wixVariables; | 20 | private Dictionary<string, string> wixVariables; |
20 | 21 | ||
@@ -31,7 +32,7 @@ namespace WixToolset | |||
31 | /// Gets or sets the localizer. | 32 | /// Gets or sets the localizer. |
32 | /// </summary> | 33 | /// </summary> |
33 | /// <value>The localizer.</value> | 34 | /// <value>The localizer.</value> |
34 | public Localizer Localizer { get; private set; } | 35 | private Localizer Localizer { get; } |
35 | 36 | ||
36 | /// <summary> | 37 | /// <summary> |
37 | /// Gets the count of variables added to the resolver. | 38 | /// Gets the count of variables added to the resolver. |
@@ -83,10 +84,7 @@ namespace WixToolset | |||
83 | /// <returns>The resolved value.</returns> | 84 | /// <returns>The resolved value.</returns> |
84 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly) | 85 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly) |
85 | { | 86 | { |
86 | bool isDefault = false; | 87 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, out var defaultIgnored, out var delayedIgnored); |
87 | bool delayedResolve = false; | ||
88 | |||
89 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); | ||
90 | } | 88 | } |
91 | 89 | ||
92 | /// <summary> | 90 | /// <summary> |
@@ -97,11 +95,9 @@ namespace WixToolset | |||
97 | /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> | 95 | /// <param name="localizationOnly">true to only resolve localization variables; false otherwise.</param> |
98 | /// <param name="isDefault">true if the resolved value was the default.</param> | 96 | /// <param name="isDefault">true if the resolved value was the default.</param> |
99 | /// <returns>The resolved value.</returns> | 97 | /// <returns>The resolved value.</returns> |
100 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault) | 98 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, out bool isDefault) |
101 | { | 99 | { |
102 | bool delayedResolve = false; | 100 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, out isDefault, out var ignored); |
103 | |||
104 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, ref isDefault, ref delayedResolve); | ||
105 | } | 101 | } |
106 | 102 | ||
107 | /// <summary> | 103 | /// <summary> |
@@ -114,9 +110,9 @@ namespace WixToolset | |||
114 | /// <param name="isDefault">true if the resolved value was the default.</param> | 110 | /// <param name="isDefault">true if the resolved value was the default.</param> |
115 | /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> | 111 | /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> |
116 | /// <returns>The resolved value.</returns> | 112 | /// <returns>The resolved value.</returns> |
117 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, ref bool isDefault, ref bool delayedResolve) | 113 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, out bool isDefault, out bool delayedResolve) |
118 | { | 114 | { |
119 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, ref isDefault, ref delayedResolve); | 115 | return this.ResolveVariables(sourceLineNumbers, value, localizationOnly, true, out isDefault, out delayedResolve); |
120 | } | 116 | } |
121 | 117 | ||
122 | /// <summary> | 118 | /// <summary> |
@@ -129,7 +125,7 @@ namespace WixToolset | |||
129 | /// <param name="isDefault">true if the resolved value was the default.</param> | 125 | /// <param name="isDefault">true if the resolved value was the default.</param> |
130 | /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> | 126 | /// <param name="delayedResolve">true if the value has variables that cannot yet be resolved.</param> |
131 | /// <returns>The resolved value.</returns> | 127 | /// <returns>The resolved value.</returns> |
132 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, ref bool isDefault, ref bool delayedResolve) | 128 | public string ResolveVariables(SourceLineNumber sourceLineNumbers, string value, bool localizationOnly, bool errorOnUnknown, out bool isDefault, out bool delayedResolve) |
133 | { | 129 | { |
134 | MatchCollection matches = Common.WixVariableRegex.Matches(value); | 130 | MatchCollection matches = Common.WixVariableRegex.Matches(value); |
135 | 131 | ||
@@ -190,10 +186,7 @@ namespace WixToolset | |||
190 | Messaging.Instance.OnMessage(WixWarnings.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId)); | 186 | Messaging.Instance.OnMessage(WixWarnings.DeprecatedLocalizationVariablePrefix(sourceLineNumbers, variableId)); |
191 | } | 187 | } |
192 | 188 | ||
193 | if (null != this.Localizer) | 189 | resolvedValue = this.Localizer?.GetLocalizedValue(variableId); |
194 | { | ||
195 | resolvedValue = this.Localizer.GetLocalizedValue(variableId); | ||
196 | } | ||
197 | } | 190 | } |
198 | else if (!localizationOnly && "wix" == variableNamespace) | 191 | else if (!localizationOnly && "wix" == variableNamespace) |
199 | { | 192 | { |
@@ -223,6 +216,7 @@ namespace WixToolset | |||
223 | } | 216 | } |
224 | else | 217 | else |
225 | { | 218 | { |
219 | |||
226 | // insert the resolved value if it was found or display an error | 220 | // insert the resolved value if it was found or display an error |
227 | if (null != resolvedValue) | 221 | if (null != resolvedValue) |
228 | { | 222 | { |
@@ -248,6 +242,19 @@ namespace WixToolset | |||
248 | } | 242 | } |
249 | 243 | ||
250 | /// <summary> | 244 | /// <summary> |
245 | /// Try to find localization information for dialog and (optional) control. | ||
246 | /// </summary> | ||
247 | /// <param name="dialog">Dialog identifier.</param> | ||
248 | /// <param name="control">Optional control identifier.</param> | ||
249 | /// <param name="localizedControl">Found localization information.</param> | ||
250 | /// <returns>True if localized control was found, otherwise false.</returns> | ||
251 | public bool TryGetLocalizedControl(string dialog, string control, out LocalizedControl localizedControl) | ||
252 | { | ||
253 | localizedControl = this.Localizer?.GetLocalizedControl(dialog, control); | ||
254 | return localizedControl != null; | ||
255 | } | ||
256 | |||
257 | /// <summary> | ||
251 | /// Resolve the delay variables in a value. | 258 | /// Resolve the delay variables in a value. |
252 | /// </summary> | 259 | /// </summary> |
253 | /// <param name="sourceLineNumbers">The source line information for the value.</param> | 260 | /// <param name="sourceLineNumbers">The source line information for the value.</param> |