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