aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs187
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs739
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs178
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs3
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs174
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs7
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs24
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs44
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs230
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs87
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs51
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs61
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs32
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs36
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs671
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs2
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs112
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs126
22 files changed, 1825 insertions, 947 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
index 74e2cdb5..f426b96d 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/AssignMediaCommand.cs
@@ -1,43 +1,44 @@
1// Copyright (c) .NET 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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.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;
8 using WixToolset.Core.Bind; 9 using WixToolset.Core.Bind;
9 using WixToolset.Data; 10 using WixToolset.Data;
10 using WixToolset.Data.Rows; 11 using WixToolset.Data.Rows;
12 using WixToolset.Data.Tuples;
11 13
12 /// <summary> 14 /// <summary>
13 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows. 15 /// AssignMediaCommand assigns files to cabs based on Media or MediaTemplate rows.
14 /// </summary> 16 /// </summary>
15 public class AssignMediaCommand 17 internal class AssignMediaCommand
16 { 18 {
17 public AssignMediaCommand() 19 public AssignMediaCommand(IntermediateSection section)
18 { 20 {
19 this.CabinetNameTemplate = "Cab{0}.cab"; 21 this.CabinetNameTemplate = "Cab{0}.cab";
22 this.Section = section;
20 } 23 }
21 24
22 public Output Output { private get; set; } 25 private IntermediateSection Section { get; }
26
27 public IEnumerable<FileFacade> FileFacades { private get; set; }
23 28
24 public bool FilesCompressed { private get; set; } 29 public bool FilesCompressed { private get; set; }
25 30
26 public string CabinetNameTemplate { private get; set; } 31 public string CabinetNameTemplate { private get; set; }
27 32
28 public IEnumerable<FileFacade> FileFacades { private get; set; }
29
30 public TableDefinitionCollection TableDefinitions { private get; set; }
31
32 /// <summary> 33 /// <summary>
33 /// Gets cabinets with their file rows. 34 /// Gets cabinets with their file rows.
34 /// </summary> 35 /// </summary>
35 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; } 36 public Dictionary<MediaTuple, IEnumerable<FileFacade>> FileFacadesByCabinetMedia { get; private set; }
36 37
37 /// <summary> 38 /// <summary>
38 /// Get media rows. 39 /// Get media rows.
39 /// </summary> 40 /// </summary>
40 public RowDictionary<MediaRow> MediaRows { get; private set; } 41 public Dictionary<int, MediaTuple> MediaRows { get; private set; }
41 42
42 /// <summary> 43 /// <summary>
43 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no. 44 /// Get uncompressed file rows. This will contain file rows of File elements that are marked with compression=no.
@@ -47,42 +48,41 @@ namespace WixToolset.Core.WindowsInstaller.Databases
47 48
48 public void Execute() 49 public void Execute()
49 { 50 {
50 Dictionary<MediaRow, List<FileFacade>> filesByCabinetMedia = new Dictionary<MediaRow, List<FileFacade>>(); 51 var filesByCabinetMedia = new Dictionary<MediaTuple, List<FileFacade>>();
51 52
52 RowDictionary<MediaRow> mediaRows = new RowDictionary<MediaRow>(); 53 var mediaRows = new Dictionary<int, MediaTuple>();
53 54
54 List<FileFacade> uncompressedFiles = new List<FileFacade>(); 55 List<FileFacade> uncompressedFiles = new List<FileFacade>();
55 56
56 MediaRow mergeModuleMediaRow = null; 57 var mediaTable = this.Section.Tuples.OfType<MediaTuple>().ToList();
57 Table mediaTable = this.Output.Tables["Media"]; 58 var mediaTemplateTable = this.Section.Tuples.OfType<WixMediaTemplateTuple>().ToList();
58 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"];
59 59
60 // If both tables are authored, it is an error. 60 // If both tables are authored, it is an error.
61 if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1)) 61 if ((mediaTemplateTable != null && mediaTemplateTable.Count > 0) && (mediaTable != null && mediaTable.Count > 1))
62 { 62 {
63 throw new WixException(WixErrors.MediaTableCollision(null)); 63 throw new WixException(WixErrors.MediaTableCollision(null));
64 } 64 }
65 65
66 // When building merge module, all the files go to "#MergeModule.CABinet". 66 // When building merge module, all the files go to "#MergeModule.CABinet".
67 if (OutputType.Module == this.Output.Type) 67 if (SectionType.Module == this.Section.Type)
68 { 68 {
69 Table mergeModuleMediaTable = new Table(this.TableDefinitions["Media"]); 69 var mergeModuleMediaRow = new MediaTuple();
70 mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null);
71 mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet"; 70 mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet";
72 71
73 filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>()); 72 this.Section.Tuples.Add(mergeModuleMediaRow);
74 }
75 73
76 if (OutputType.Module == this.Output.Type || null == mediaTemplateTable) 74 filesByCabinetMedia.Add(mergeModuleMediaRow, new List<FileFacade>(this.FileFacades));
75 }
76 else if (null == mediaTemplateTable)
77 { 77 {
78 this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); 78 this.ManuallyAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
79 } 79 }
80 else 80 else
81 { 81 {
82 this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles); 82 this.AutoAssignFiles(mediaTable, this.FileFacades, filesByCabinetMedia, mediaRows, uncompressedFiles);
83 } 83 }
84 84
85 this.FileFacadesByCabinetMedia = new Dictionary<MediaRow, IEnumerable<FileFacade>>(); 85 this.FileFacadesByCabinetMedia = new Dictionary<MediaTuple, IEnumerable<FileFacade>>();
86 86
87 foreach (var mediaRowWithFiles in filesByCabinetMedia) 87 foreach (var mediaRowWithFiles in filesByCabinetMedia)
88 { 88 {
@@ -98,7 +98,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
98 /// Assign files to cabinets based on MediaTemplate authoring. 98 /// Assign files to cabinets based on MediaTemplate authoring.
99 /// </summary> 99 /// </summary>
100 /// <param name="fileFacades">FileRowCollection</param> 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) 101 private void AutoAssignFiles(List<MediaTuple> mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, Dictionary<int, MediaTuple> mediaRows, List<FileFacade> uncompressedFiles)
102 { 102 {
103 const int MaxCabIndex = 999; 103 const int MaxCabIndex = 999;
104 104
@@ -107,13 +107,19 @@ namespace WixToolset.Core.WindowsInstaller.Databases
107 int maxPreCabSizeInMB = 0; 107 int maxPreCabSizeInMB = 0;
108 int currentCabIndex = 0; 108 int currentCabIndex = 0;
109 109
110 MediaRow currentMediaRow = null; 110 MediaTuple currentMediaRow = null;
111 111
112 Table mediaTemplateTable = this.Output.Tables["WixMediaTemplate"]; 112 var mediaTemplateTable = this.Section.Tuples.OfType<WixMediaTemplateTuple>();
113
114 // Remove all previous media tuples since they will be replaced with
115 // media template.
116 foreach (var mediaTuple in mediaTable)
117 {
118 this.Section.Tuples.Remove(mediaTuple);
119 }
113 120
114 // Auto assign files to cabinets based on maximum uncompressed media size 121 // Auto assign files to cabinets based on maximum uncompressed media size
115 mediaTable.Rows.Clear(); 122 var mediaTemplateRow = mediaTemplateTable.Single();
116 WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0];
117 123
118 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) 124 if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate))
119 { 125 {
@@ -149,9 +155,9 @@ namespace WixToolset.Core.WindowsInstaller.Databases
149 { 155 {
150 // When building a product, if the current file is not to be compressed or if 156 // 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. 157 // the package set not to be compressed, don't cab it.
152 if (OutputType.Product == this.Output.Type && 158 if (SectionType.Product == this.Section.Type &&
153 (!facade.File.Compressed.Value || 159 ((facade.File.Compressed.HasValue && !facade.File.Compressed.Value) ||
154 (!facade.File.Compressed.HasValue && !this.FilesCompressed))) 160 (!facade.File.Compressed.HasValue && !this.FilesCompressed)))
155 { 161 {
156 uncompressedFiles.Add(facade); 162 uncompressedFiles.Add(facade);
157 continue; 163 continue;
@@ -172,8 +178,8 @@ namespace WixToolset.Core.WindowsInstaller.Databases
172 if (currentPreCabSize > maxPreCabSizeInBytes) 178 if (currentPreCabSize > maxPreCabSizeInBytes)
173 { 179 {
174 // Overflow due to current file 180 // Overflow due to current file
175 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); 181 currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex);
176 mediaRows.Add(currentMediaRow); 182 mediaRows.Add(currentMediaRow.DiskId, currentMediaRow);
177 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>()); 183 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
178 184
179 List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow]; 185 List<FileFacade> cabinetFileRows = filesByCabinetMedia[currentMediaRow];
@@ -188,8 +194,8 @@ namespace WixToolset.Core.WindowsInstaller.Databases
188 if (currentMediaRow == null) 194 if (currentMediaRow == null)
189 { 195 {
190 // Create new cab and MediaRow 196 // Create new cab and MediaRow
191 currentMediaRow = this.AddMediaRow(mediaTemplateRow, mediaTable, ++currentCabIndex); 197 currentMediaRow = this.AddMediaRow(mediaTemplateRow, ++currentCabIndex);
192 mediaRows.Add(currentMediaRow); 198 mediaRows.Add(currentMediaRow.DiskId, currentMediaRow);
193 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>()); 199 filesByCabinetMedia.Add(currentMediaRow, new List<FileFacade>());
194 } 200 }
195 201
@@ -201,11 +207,13 @@ namespace WixToolset.Core.WindowsInstaller.Databases
201 } 207 }
202 208
203 // If there are uncompressed files and no MediaRow, create a default one. 209 // If there are uncompressed files and no MediaRow, create a default one.
204 if (uncompressedFiles.Count > 0 && mediaTable.Rows.Count == 0) 210 if (uncompressedFiles.Count > 0 && !this.Section.Tuples.OfType<MediaTuple>().Any())
205 { 211 {
206 MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null); 212 var defaultMediaRow = new MediaTuple(null, new Identifier(1, AccessModifier.Private));
207 defaultMediaRow.DiskId = 1; 213 defaultMediaRow.DiskId = 1;
208 mediaRows.Add(defaultMediaRow); 214
215 mediaRows.Add(1, defaultMediaRow);
216 this.Section.Tuples.Add(defaultMediaRow);
209 } 217 }
210 } 218 }
211 219
@@ -213,79 +221,65 @@ namespace WixToolset.Core.WindowsInstaller.Databases
213 /// Assign files to cabinets based on Media authoring. 221 /// Assign files to cabinets based on Media authoring.
214 /// </summary> 222 /// </summary>
215 /// <param name="mediaTable"></param> 223 /// <param name="mediaTable"></param>
216 /// <param name="mergeModuleMediaRow"></param>
217 /// <param name="fileFacades"></param> 224 /// <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) 225 private void ManuallyAssignFiles(List<MediaTuple> mediaTable, IEnumerable<FileFacade> fileFacades, Dictionary<MediaTuple, List<FileFacade>> filesByCabinetMedia, Dictionary<int, MediaTuple> mediaRows, List<FileFacade> uncompressedFiles)
219 { 226 {
220 if (OutputType.Module != this.Output.Type) 227 if (mediaTable.Any())
221 { 228 {
222 if (null != mediaTable) 229 var cabinetMediaRows = new Dictionary<string, MediaTuple>(StringComparer.OrdinalIgnoreCase);
230 foreach (var mediaRow in mediaTable)
223 { 231 {
224 Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase); 232 // If the Media row has a cabinet, make sure it is unique across all Media rows.
225 foreach (MediaRow mediaRow in mediaTable.Rows) 233 if (!String.IsNullOrEmpty(mediaRow.Cabinet))
226 { 234 {
227 // If the Media row has a cabinet, make sure it is unique across all Media rows. 235 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out var existingRow))
228 if (!String.IsNullOrEmpty(mediaRow.Cabinet))
229 { 236 {
230 MediaRow existingRow; 237 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet));
231 if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow)) 238 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet));
232 { 239 }
233 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); 240 else
234 Messaging.Instance.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); 241 {
235 } 242 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
236 else
237 {
238 cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow);
239 }
240 } 243 }
241
242 mediaRows.Add(mediaRow);
243 } 244 }
245
246 mediaRows.Add(mediaRow.DiskId, mediaRow);
244 } 247 }
248 }
245 249
246 foreach (MediaRow mediaRow in mediaRows.Values) 250 foreach (var mediaRow in mediaRows.Values)
251 {
252 if (null != mediaRow.Cabinet)
247 { 253 {
248 if (null != mediaRow.Cabinet) 254 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
249 {
250 filesByCabinetMedia.Add(mediaRow, new List<FileFacade>());
251 }
252 } 255 }
253 } 256 }
254 257
255 foreach (FileFacade facade in fileFacades) 258 foreach (FileFacade facade in fileFacades)
256 { 259 {
257 if (OutputType.Module == this.Output.Type) 260 if (!mediaRows.TryGetValue(facade.WixFile.DiskId, out var mediaRow))
258 { 261 {
259 filesByCabinetMedia[mergeModuleMediaRow].Add(facade); 262 Messaging.Instance.OnMessage(WixErrors.MissingMedia(facade.File.SourceLineNumbers, facade.WixFile.DiskId));
263 continue;
260 } 264 }
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 265
270 // When building a product, if the current file is not to be compressed or if 266 // 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. 267 // the package set not to be compressed, don't cab it.
272 if (OutputType.Product == this.Output.Type && 268 if (SectionType.Product == this.Section.Type &&
273 (!facade.File.Compressed.Value || 269 (!facade.File.Compressed.Value ||
274 (!facade.File.Compressed.HasValue && !this.FilesCompressed))) 270 (!facade.File.Compressed.HasValue && !this.FilesCompressed)))
271 {
272 uncompressedFiles.Add(facade);
273 }
274 else // file is marked compressed.
275 {
276 if (filesByCabinetMedia.TryGetValue(mediaRow, out var cabinetFiles))
275 { 277 {
276 uncompressedFiles.Add(facade); 278 cabinetFiles.Add(facade);
277 } 279 }
278 else // file is marked compressed. 280 else
279 { 281 {
280 List<FileFacade> cabinetFiles; 282 Messaging.Instance.OnMessage(WixErrors.ExpectedMediaCabinet(facade.File.SourceLineNumbers, facade.File.File, facade.WixFile.DiskId));
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 } 283 }
290 } 284 }
291 } 285 }
@@ -297,17 +291,20 @@ namespace WixToolset.Core.WindowsInstaller.Databases
297 /// <param name="mediaTable"></param> 291 /// <param name="mediaTable"></param>
298 /// <param name="cabIndex"></param> 292 /// <param name="cabIndex"></param>
299 /// <returns></returns> 293 /// <returns></returns>
300 private MediaRow AddMediaRow(WixMediaTemplateRow mediaTemplateRow, Table mediaTable, int cabIndex) 294 private MediaTuple AddMediaRow(WixMediaTemplateTuple mediaTemplateRow, int cabIndex)
301 { 295 {
302 MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); 296 var currentMediaRow = new MediaTuple(mediaTemplateRow.SourceLineNumbers, new Identifier(cabIndex, AccessModifier.Private));
303 currentMediaRow.DiskId = cabIndex; 297 currentMediaRow.DiskId = cabIndex;
304 currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex); 298 currentMediaRow.Cabinet = String.Format(CultureInfo.InvariantCulture, this.CabinetNameTemplate, cabIndex);
305 299
306 Table wixMediaTable = this.Output.EnsureTable(this.TableDefinitions["WixMedia"]); 300 this.Section.Tuples.Add(currentMediaRow);
307 WixMediaRow row = (WixMediaRow)wixMediaTable.CreateRow(mediaTemplateRow.SourceLineNumbers); 301
308 row.DiskId = cabIndex; 302 var row = new WixMediaTuple(mediaTemplateRow.SourceLineNumbers, new Identifier(cabIndex, AccessModifier.Private));
303 row.DiskId_ = cabIndex;
309 row.CompressionLevel = mediaTemplateRow.CompressionLevel; 304 row.CompressionLevel = mediaTemplateRow.CompressionLevel;
310 305
306 this.Section.Tuples.Add(row);
307
311 return currentMediaRow; 308 return currentMediaRow;
312 } 309 }
313 } 310 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 5a61b63c..30a19a4b 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -3,21 +3,15 @@
3namespace WixToolset.Core.WindowsInstaller.Bind 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections;
7 using System.Collections.Generic; 6 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO; 7 using System.IO;
11 using System.Linq; 8 using System.Linq;
12 using WixToolset.Bind;
13 using WixToolset.Core.Bind; 9 using WixToolset.Core.Bind;
14 using WixToolset.Core.WindowsInstaller.Databases;
15 using WixToolset.Data; 10 using WixToolset.Data;
16 using WixToolset.Data.Bind; 11 using WixToolset.Data.Bind;
17 using WixToolset.Data.Rows; 12 using WixToolset.Data.Tuples;
18 using WixToolset.Extensibility; 13 using WixToolset.Extensibility;
19 using WixToolset.Extensibility.Services; 14 using WixToolset.Extensibility.Services;
20 using WixToolset.Msi;
21 15
22 /// <summary> 16 /// <summary>
23 /// Binds a databse. 17 /// Binds a databse.
@@ -25,11 +19,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
25 internal class BindDatabaseCommand 19 internal class BindDatabaseCommand
26 { 20 {
27 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. 21 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
28 private static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); 22 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
29 23
30 public BindDatabaseCommand(IBindContext context, Validator validator) 24 public BindDatabaseCommand(IBindContext context, Validator validator)
31 { 25 {
32 this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); 26 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions();
33 27
34 this.BindPaths = context.BindPaths; 28 this.BindPaths = context.BindPaths;
35 this.CabbingThreadCount = context.CabbingThreadCount; 29 this.CabbingThreadCount = context.CabbingThreadCount;
@@ -73,8 +67,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
73 67
74 private Intermediate Intermediate { get; } 68 private Intermediate Intermediate { get; }
75 69
76 private Output Output { get; }
77
78 private string OutputPath { get; } 70 private string OutputPath { get; }
79 71
80 private bool SuppressAddingValidationRows { get; } 72 private bool SuppressAddingValidationRows { get; }
@@ -95,22 +87,16 @@ namespace WixToolset.Core.WindowsInstaller.Bind
95 87
96 public void Execute() 88 public void Execute()
97 { 89 {
98 this.Intermediate.Save(this.OutputPath); 90 var section = this.Intermediate.Sections.Single();
99#if FINISH
100 List<FileTransfer> fileTransfers = new List<FileTransfer>();
101 91
102 HashSet<string> suppressedTableNames = new HashSet<string>(); 92 var fileTransfers = new List<FileTransfer>();
103 93
104 // If there are any fields to resolve later, create the cache to populate during bind. 94 var suppressedTableNames = new HashSet<string>();
105 IDictionary<string, string> variableCache = null;
106 if (this.DelayedFields.Any())
107 {
108 variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
109 }
110 95
111 this.LocalizeUI(this.Output.Tables); 96 // If there are any fields to resolve later, create the cache to populate during bind.
97 var variableCache = this.DelayedFields.Any() ? new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) : null;
112 98
113 this.Output = CreateOutputFromIR(this.Intermediate); 99 this.LocalizeUI(section);
114 100
115 // Process the summary information table before the other tables. 101 // Process the summary information table before the other tables.
116 bool compressed; 102 bool compressed;
@@ -118,8 +104,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
118 int installerVersion; 104 int installerVersion;
119 string modularizationGuid; 105 string modularizationGuid;
120 { 106 {
121 BindSummaryInfoCommand command = new BindSummaryInfoCommand(); 107 var command = new BindSummaryInfoCommand(section);
122 command.Output = this.Output;
123 command.Execute(); 108 command.Execute();
124 109
125 compressed = command.Compressed; 110 compressed = command.Compressed;
@@ -128,42 +113,61 @@ namespace WixToolset.Core.WindowsInstaller.Bind
128 modularizationGuid = command.ModularizationGuid; 113 modularizationGuid = command.ModularizationGuid;
129 } 114 }
130 115
131 // Stop processing if an error previously occurred. 116 // Add binder variables for all properties.
132 if (Messaging.Instance.EncounteredError) 117 if (SectionType.Product == section.Type || variableCache != null)
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 { 118 {
140 // Gather all the suppress modularization identifiers 119 foreach (var propertyRow in section.Tuples.OfType<PropertyTuple>())
141 HashSet<string> suppressModularizationIdentifiers = null;
142 Table wixSuppressModularizationTable = this.Output.Tables["WixSuppressModularization"];
143 if (null != wixSuppressModularizationTable)
144 { 120 {
145 suppressModularizationIdentifiers = new HashSet<string>(wixSuppressModularizationTable.Rows.Select(row => (string)row[0])); 121 // Set the ProductCode if it is to be generated.
146 } 122 if ("ProductCode".Equals(propertyRow.Property, StringComparison.Ordinal) && "*".Equals(propertyRow.Value, StringComparison.Ordinal))
123 {
124 propertyRow.Value = Common.GenerateGuid();
147 125
148 foreach (Table table in this.Output.Tables) 126#if TODO_FIX_INSTANCE_TRANSFORM
149 { 127 // Update the target ProductCode in any instance transforms.
150 table.Modularize(modularizationGuid, suppressModularizationIdentifiers); 128 foreach (SubStorage subStorage in this.Output.SubStorages)
129 {
130 Output subStorageOutput = subStorage.Data;
131 if (OutputType.Transform != subStorageOutput.Type)
132 {
133 continue;
134 }
135
136 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"];
137 foreach (Row row in instanceSummaryInformationTable.Rows)
138 {
139 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0))
140 {
141 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
142 break;
143 }
144 }
145 }
146#endif
147 }
148
149 // Add the property name and value to the variableCache.
150 if (variableCache != null)
151 {
152 var key = String.Concat("property.", propertyRow.Property);
153 variableCache[key] = propertyRow.Value;
154 }
151 } 155 }
152 } 156 }
153 157
154 // This must occur after all variables and source paths have been resolved and after modularization. 158 // Sequence all the actions.
155 List<FileFacade> fileFacades;
156 { 159 {
157 GetFileFacadesCommand command = new GetFileFacadesCommand(); 160 var command = new SequenceActionsCommand(section);
158 command.FileTable = this.Output.Tables["File"]; 161 command.Messaging = Messaging.Instance;
159 command.WixFileTable = this.Output.Tables["WixFile"];
160 command.WixDeltaPatchFileTable = this.Output.Tables["WixDeltaPatchFile"];
161 command.WixDeltaPatchSymbolPathsTable = this.Output.Tables["WixDeltaPatchSymbolPaths"];
162 command.Execute(); 162 command.Execute();
163 }
163 164
164 fileFacades = command.FileFacades; 165 {
166 var command = new CreateSpecialPropertiesCommand(section);
167 command.Execute();
165 } 168 }
166 169
170#if TODO_FINISH_PATCH
167 ////if (OutputType.Patch == this.Output.Type) 171 ////if (OutputType.Patch == this.Output.Type)
168 ////{ 172 ////{
169 //// foreach (SubStorage substorage in this.Output.SubStorages) 173 //// foreach (SubStorage substorage in this.Output.SubStorages)
@@ -183,87 +187,72 @@ namespace WixToolset.Core.WindowsInstaller.Bind
183 //// this.MergeUnrealTables(transform.Tables); 187 //// this.MergeUnrealTables(transform.Tables);
184 //// } 188 //// }
185 ////} 189 ////}
186 190#endif
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 191
194 if (Messaging.Instance.EncounteredError) 192 if (Messaging.Instance.EncounteredError)
195 { 193 {
196 return; 194 return;
197 } 195 }
198 196
199 // Add binder variables for all properties. 197 Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation());
200 Table propertyTable = this.Output.Tables["Property"]; 198
201 if (null != propertyTable) 199 // This must occur after all variables and source paths have been resolved.
200 List<FileFacade> fileFacades;
202 { 201 {
203 foreach (PropertyRow propertyRow in propertyTable.Rows) 202 var command = new GetFileFacadesCommand(section);
204 { 203 command.Execute();
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 204
210 // Update the target ProductCode in any instance transforms. 205 fileFacades = command.FileFacades;
211 foreach (SubStorage subStorage in this.Output.SubStorages) 206 }
212 {
213 Output subStorageOutput = subStorage.Data;
214 if (OutputType.Transform != subStorageOutput.Type)
215 {
216 continue;
217 }
218 207
219 Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; 208 // Extract files that come from binary .wixlibs and WixExtensions (this does not extract files from merge modules).
220 foreach (Row row in instanceSummaryInformationTable.Rows) 209 {
221 { 210 var command = new ExtractEmbeddedFilesCommand(this.ExpectedEmbeddedFiles);
222 if ((int)SummaryInformation.Transform.ProductCodes == row.FieldAsInteger(0)) 211 command.Execute();
223 { 212 }
224 row[1] = row.FieldAsString(1).Replace("*", propertyRow.Value);
225 break;
226 }
227 }
228 }
229 }
230 213
231 // Add the property name and value to the variableCache. 214 // Gather information about files that did not come from merge modules (i.e. rows with a reference to the File table).
232 if (null != variableCache) 215 {
233 { 216 var command = new UpdateFileFacadesCommand(section);
234 string key = String.Concat("property.", Common.Demodularize(this.Output.Type, modularizationGuid, propertyRow.Property)); 217 command.FileFacades = fileFacades;
235 variableCache[key] = propertyRow.Value; 218 command.UpdateFileFacades = fileFacades.Where(f => !f.FromModule);
236 } 219 command.OverwriteHash = true;
237 } 220 command.TableDefinitions = this.TableDefinitions;
221 command.VariableCache = variableCache;
222 command.Execute();
238 } 223 }
239 224
240 // Extract files that come from cabinet files (this does not extract files from merge modules). 225 // Now that the variable cache is populated, resolve any delayed fields.
226 if (this.DelayedFields.Any())
241 { 227 {
242 ExtractEmbeddedFilesCommand command = new ExtractEmbeddedFilesCommand(); 228 var command = new ResolveDelayedFieldsCommand(this.DelayedFields, variableCache);
243 command.FilesWithEmbeddedFiles = this.ExpectedEmbeddedFiles;
244 command.Execute(); 229 command.Execute();
245 } 230 }
246 231
247 if (OutputType.Product == this.Output.Type) 232 // Set generated component guids.
233 {
234 var command = new CalculateComponentGuids(section);
235 command.Execute();
236 }
237
238 if (SectionType.Product == section.Type)
248 { 239 {
249 // Retrieve files and their information from merge modules. 240 // Retrieve files and their information from merge modules.
250 Table wixMergeTable = this.Output.Tables["WixMerge"]; 241 var wixMergeTuples = section.Tuples.OfType<WixMergeTuple>().ToList();
251 242
252 if (null != wixMergeTable) 243 if (wixMergeTuples.Any())
253 { 244 {
254 ExtractMergeModuleFilesCommand command = new ExtractMergeModuleFilesCommand(); 245 var command = new ExtractMergeModuleFilesCommand(section, wixMergeTuples);
255 command.FileFacades = fileFacades; 246 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; 247 command.OutputInstallerVersion = installerVersion;
260 command.SuppressLayout = this.SuppressLayout; 248 command.SuppressLayout = this.SuppressLayout;
261 command.TempFilesLocation = this.IntermediateFolder; 249 command.IntermediateFolder = this.IntermediateFolder;
262 command.Execute(); 250 command.Execute();
263 251
264 fileFacades.AddRange(command.MergeModulesFileFacades); 252 fileFacades.AddRange(command.MergeModulesFileFacades);
265 } 253 }
266 } 254 }
255#if TODO_FINISH_PATCH
267 else if (OutputType.Patch == this.Output.Type) 256 else if (OutputType.Patch == this.Output.Type)
268 { 257 {
269 // Merge transform data into the output object. 258 // Merge transform data into the output object.
@@ -271,6 +260,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
271 260
272 fileFacades.AddRange(filesFromTransform); 261 fileFacades.AddRange(filesFromTransform);
273 } 262 }
263#endif
274 264
275 // stop processing if an error previously occurred 265 // stop processing if an error previously occurred
276 if (Messaging.Instance.EncounteredError) 266 if (Messaging.Instance.EncounteredError)
@@ -278,51 +268,19 @@ namespace WixToolset.Core.WindowsInstaller.Bind
278 return; 268 return;
279 } 269 }
280 270
281 Messaging.Instance.OnMessage(WixVerboses.UpdatingFileInformation()); 271#if TODO_FIX_INSTANCE_TRANSFORM
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. 272 // With the Component Guids set now we can create instance transforms.
300 this.CreateInstanceTransforms(this.Output); 273 this.CreateInstanceTransforms(this.Output);
301 274#endif
302 this.ValidateComponentGuids(this.Output);
303
304 this.UpdateControlText(this.Output);
305
306 if (this.DelayedFields.Any())
307 {
308 ResolveDelayedFieldsCommand command = new ResolveDelayedFieldsCommand();
309 command.OutputType = this.Output.Type;
310 command.DelayedFields = this.DelayedFields;
311 command.ModularizationGuid = null;
312 command.VariableCache = variableCache;
313 command.Execute();
314 }
315 275
316 // Assign files to media. 276 // Assign files to media.
317 RowDictionary<MediaRow> assignedMediaRows; 277 Dictionary<int, MediaTuple> assignedMediaRows;
318 Dictionary<MediaRow, IEnumerable<FileFacade>> filesByCabinetMedia; 278 Dictionary<MediaTuple, IEnumerable<FileFacade>> filesByCabinetMedia;
319 IEnumerable<FileFacade> uncompressedFiles; 279 IEnumerable<FileFacade> uncompressedFiles;
320 { 280 {
321 AssignMediaCommand command = new AssignMediaCommand(); 281 var command = new AssignMediaCommand(section);
322 command.FilesCompressed = compressed;
323 command.FileFacades = fileFacades; 282 command.FileFacades = fileFacades;
324 command.Output = this.Output; 283 command.FilesCompressed = compressed;
325 command.TableDefinitions = this.TableDefinitions;
326 command.Execute(); 284 command.Execute();
327 285
328 assignedMediaRows = command.MediaRows; 286 assignedMediaRows = command.MediaRows;
@@ -330,15 +288,40 @@ namespace WixToolset.Core.WindowsInstaller.Bind
330 uncompressedFiles = command.UncompressedFileFacades; 288 uncompressedFiles = command.UncompressedFileFacades;
331 } 289 }
332 290
333 // Update file sequence.
334 this.UpdateMediaSequences(this.Output.Type, fileFacades, assignedMediaRows);
335
336 // stop processing if an error previously occurred 291 // stop processing if an error previously occurred
337 if (Messaging.Instance.EncounteredError) 292 if (Messaging.Instance.EncounteredError)
338 { 293 {
339 return; 294 return;
340 } 295 }
341 296
297 // Try to put as much above here as possible, updating the IR is better.
298 Output output;
299 {
300 var command = new CreateOutputFromIRCommand(section, this.TableDefinitions);
301 command.Execute();
302
303 output = command.Output;
304 }
305
306 // Update file sequence.
307 {
308 var command = new UpdateMediaSequencesCommand(output, fileFacades, assignedMediaRows);
309 command.Execute();
310 }
311
312 // Modularize identifiers and add tables with real streams to the import tables.
313 if (OutputType.Module == output.Type)
314 {
315 // Gather all the suppress modularization identifiers
316 var suppressModularizationIdentifiers = new HashSet<string>(section.Tuples.OfType<WixSuppressModularizationTuple>().Select(s => s.WixSuppressModularization));
317
318 foreach (var table in output.Tables)
319 {
320 table.Modularize(modularizationGuid, suppressModularizationIdentifiers);
321 }
322 }
323
324#if TODO_FINISH_UPDATE
342 // Extended binder extensions can be called now that fields are resolved. 325 // Extended binder extensions can be called now that fields are resolved.
343 { 326 {
344 Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]); 327 Table updatedFiles = this.Output.EnsureTable(this.TableDefinitions["WixBindUpdatedFiles"]);
@@ -372,27 +355,27 @@ namespace WixToolset.Core.WindowsInstaller.Bind
372 command.Execute(); 355 command.Execute();
373 } 356 }
374 } 357 }
358#endif
375 359
376 // stop processing if an error previously occurred 360 // Stop processing if an error previously occurred.
377 if (Messaging.Instance.EncounteredError) 361 if (Messaging.Instance.EncounteredError)
378 { 362 {
379 return; 363 return;
380 } 364 }
381 365
366 // Ensure the intermediate folder is created since delta patches will be
367 // created there.
382 Directory.CreateDirectory(this.IntermediateFolder); 368 Directory.CreateDirectory(this.IntermediateFolder);
383 369
384 if (OutputType.Patch == this.Output.Type && this.DeltaBinaryPatch) 370 if (SectionType.Patch == section.Type && this.DeltaBinaryPatch)
385 { 371 {
386 CreateDeltaPatchesCommand command = new CreateDeltaPatchesCommand(); 372 var command = new CreateDeltaPatchesCommand(fileFacades, this.IntermediateFolder, section.Tuples.OfType<WixPatchIdTuple>().FirstOrDefault());
387 command.FileFacades = fileFacades;
388 command.WixPatchIdTable = this.Output.Tables["WixPatchId"];
389 command.TempFilesLocation = this.IntermediateFolder;
390 command.Execute(); 373 command.Execute();
391 } 374 }
392 375
393 // create cabinet files and process uncompressed files 376 // create cabinet files and process uncompressed files
394 string layoutDirectory = Path.GetDirectoryName(this.OutputPath); 377 string layoutDirectory = Path.GetDirectoryName(this.OutputPath);
395 if (!this.SuppressLayout || OutputType.Module == this.Output.Type) 378 if (!this.SuppressLayout || OutputType.Module == output.Type)
396 { 379 {
397 Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles()); 380 Messaging.Instance.OnMessage(WixVerboses.CreatingCabinetFiles());
398 381
@@ -400,7 +383,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
400 command.CabbingThreadCount = this.CabbingThreadCount; 383 command.CabbingThreadCount = this.CabbingThreadCount;
401 command.CabCachePath = this.CabCachePath; 384 command.CabCachePath = this.CabCachePath;
402 command.DefaultCompressionLevel = this.DefaultCompressionLevel; 385 command.DefaultCompressionLevel = this.DefaultCompressionLevel;
403 command.Output = this.Output; 386 command.Output = output;
404 command.BackendExtensions = this.BackendExtensions; 387 command.BackendExtensions = this.BackendExtensions;
405 command.LayoutDirectory = layoutDirectory; 388 command.LayoutDirectory = layoutDirectory;
406 command.Compressed = compressed; 389 command.Compressed = compressed;
@@ -408,39 +391,35 @@ namespace WixToolset.Core.WindowsInstaller.Bind
408 command.ResolveMedia = this.ResolveMedia; 391 command.ResolveMedia = this.ResolveMedia;
409 command.TableDefinitions = this.TableDefinitions; 392 command.TableDefinitions = this.TableDefinitions;
410 command.TempFilesLocation = this.IntermediateFolder; 393 command.TempFilesLocation = this.IntermediateFolder;
411 command.WixMediaTable = this.Output.Tables["WixMedia"]; 394 command.WixMediaTable = output.Tables["WixMedia"];
412 command.Execute(); 395 command.Execute();
413 396
414 fileTransfers.AddRange(command.FileTransfers); 397 fileTransfers.AddRange(command.FileTransfers);
415 } 398 }
416 399
400#if TODO_FINISH_PATCH
417 if (OutputType.Patch == this.Output.Type) 401 if (OutputType.Patch == this.Output.Type)
418 { 402 {
419 // copy output data back into the transforms 403 // copy output data back into the transforms
420 this.CopyToTransformData(this.Output); 404 this.CopyToTransformData(this.Output);
421 } 405 }
406#endif
422 407
423 // stop processing if an error previously occurred 408 // Add back suppressed tables which must be present prior to merging in modules.
424 if (Messaging.Instance.EncounteredError) 409 if (OutputType.Product == output.Type)
425 {
426 return;
427 }
428
429 // add back suppressed tables which must be present prior to merging in modules
430 if (OutputType.Product == this.Output.Type)
431 { 410 {
432 Table wixMergeTable = this.Output.Tables["WixMerge"]; 411 Table wixMergeTable = output.Tables["WixMerge"];
433 412
434 if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) 413 if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count)
435 { 414 {
436 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) 415 foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable)))
437 { 416 {
438 string sequenceTableName = sequence.ToString(); 417 string sequenceTableName = sequence.ToString();
439 Table sequenceTable = this.Output.Tables[sequenceTableName]; 418 Table sequenceTable = output.Tables[sequenceTableName];
440 419
441 if (null == sequenceTable) 420 if (null == sequenceTable)
442 { 421 {
443 sequenceTable = this.Output.EnsureTable(this.TableDefinitions[sequenceTableName]); 422 sequenceTable = output.EnsureTable(this.TableDefinitions[sequenceTableName]);
444 } 423 }
445 424
446 if (0 == sequenceTable.Rows.Count) 425 if (0 == sequenceTable.Rows.Count)
@@ -456,67 +435,59 @@ namespace WixToolset.Core.WindowsInstaller.Bind
456 // extension.PostBind(this.Context); 435 // extension.PostBind(this.Context);
457 //} 436 //}
458 437
459 // generate database file 438 this.ValidateComponentGuids(output);
439
440 // stop processing if an error previously occurred
441 if (Messaging.Instance.EncounteredError)
442 {
443 return;
444 }
445
446 // Generate database file.
460 Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase()); 447 Messaging.Instance.OnMessage(WixVerboses.GeneratingDatabase());
461 string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath)); 448 string tempDatabaseFile = Path.Combine(this.IntermediateFolder, Path.GetFileName(this.OutputPath));
462 this.GenerateDatabase(this.Output, tempDatabaseFile, false, false); 449 this.GenerateDatabase(output, tempDatabaseFile, false, false);
463 450
464 FileTransfer transfer; 451 if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, output.Type.ToString(), null, out var transfer)) // note where this database needs to move in the future
465 if (FileTransfer.TryCreate(tempDatabaseFile, this.OutputPath, true, this.Output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future
466 { 452 {
467 transfer.Built = true; 453 transfer.Built = true;
468 fileTransfers.Add(transfer); 454 fileTransfers.Add(transfer);
469 } 455 }
470 456
471 // stop processing if an error previously occurred 457 // Stop processing if an error previously occurred.
472 if (Messaging.Instance.EncounteredError) 458 if (Messaging.Instance.EncounteredError)
473 { 459 {
474 return; 460 return;
475 } 461 }
476 462
477 // Output the output to a file 463 // Output the output to a file.
478 Pdb pdb = new Pdb(); 464 Pdb pdb = new Pdb();
479 pdb.Output = this.Output; 465 pdb.Output = output;
480 if (!String.IsNullOrEmpty(this.PdbFile)) 466 if (!String.IsNullOrEmpty(this.PdbFile))
481 { 467 {
482 pdb.Save(this.PdbFile); 468 pdb.Save(this.PdbFile);
483 } 469 }
484 470
485 // Merge modules. 471 // Merge modules.
486 if (OutputType.Product == this.Output.Type) 472 if (OutputType.Product == output.Type)
487 { 473 {
488 Messaging.Instance.OnMessage(WixVerboses.MergingModules()); 474 Messaging.Instance.OnMessage(WixVerboses.MergingModules());
489 475
490 MergeModulesCommand command = new MergeModulesCommand(); 476 var command = new MergeModulesCommand();
491 command.FileFacades = fileFacades; 477 command.FileFacades = fileFacades;
492 command.Output = this.Output; 478 command.Output = output;
493 command.OutputPath = tempDatabaseFile; 479 command.OutputPath = tempDatabaseFile;
494 command.SuppressedTableNames = suppressedTableNames; 480 command.SuppressedTableNames = suppressedTableNames;
495 command.Execute(); 481 command.Execute();
496
497 // stop processing if an error previously occurred
498 if (Messaging.Instance.EncounteredError)
499 {
500 return;
501 }
502 } 482 }
503 483
504 // inspect the MSI prior to running ICEs
505 //InspectorCore inspectorCore = new InspectorCore();
506 //foreach (InspectorExtension inspectorExtension in this.InspectorExtensions)
507 //{
508 // inspectorExtension.Core = inspectorCore;
509 // inspectorExtension.InspectDatabase(tempDatabaseFile, pdb);
510
511 // inspectorExtension.Core = null; // reset.
512 //}
513
514 if (Messaging.Instance.EncounteredError) 484 if (Messaging.Instance.EncounteredError)
515 { 485 {
516 return; 486 return;
517 } 487 }
518 488
519 // validate the output if there is an MSI validator 489#if TODO_FINISH_VALIDATION
490 // Validate the output if there is an MSI validator.
520 if (null != this.Validator) 491 if (null != this.Validator)
521 { 492 {
522 Stopwatch stopwatch = Stopwatch.StartNew(); 493 Stopwatch stopwatch = Stopwatch.StartNew();
@@ -537,19 +508,18 @@ namespace WixToolset.Core.WindowsInstaller.Bind
537 return; 508 return;
538 } 509 }
539 } 510 }
511#endif
540 512
541 // Process uncompressed files. 513 // Process uncompressed files.
542 if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any()) 514 if (!Messaging.Instance.EncounteredError && !this.SuppressLayout && uncompressedFiles.Any())
543 { 515 {
544 var command = new ProcessUncompressedFilesCommand(); 516 var command = new ProcessUncompressedFilesCommand(section);
545 command.Compressed = compressed; 517 command.Compressed = compressed;
546 command.FileFacades = uncompressedFiles; 518 command.FileFacades = uncompressedFiles;
547 command.LayoutDirectory = layoutDirectory; 519 command.LayoutDirectory = layoutDirectory;
548 command.LongNamesInImage = longNames; 520 command.LongNamesInImage = longNames;
549 command.MediaRows = assignedMediaRows;
550 command.ResolveMedia = this.ResolveMedia; 521 command.ResolveMedia = this.ResolveMedia;
551 command.DatabasePath = tempDatabaseFile; 522 command.DatabasePath = tempDatabaseFile;
552 command.WixMediaTable = this.Output.Tables["WixMedia"];
553 command.Execute(); 523 command.Execute();
554 524
555 fileTransfers.AddRange(command.FileTransfers); 525 fileTransfers.AddRange(command.FileTransfers);
@@ -557,94 +527,92 @@ namespace WixToolset.Core.WindowsInstaller.Bind
557 527
558 this.FileTransfers = fileTransfers; 528 this.FileTransfers = fileTransfers;
559 this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList(); 529 this.ContentFilePaths = fileFacades.Select(r => r.WixFile.Source).ToList();
530
531 // TODO: Eventually this gets removed
532 var intermediate = new Intermediate(this.Intermediate.Id, new[] { section }, this.Intermediate.Localizations.ToDictionary(l => l.Culture, StringComparer.OrdinalIgnoreCase), this.Intermediate.EmbedFilePaths);
533 intermediate.Save(Path.ChangeExtension(this.OutputPath, "wir"));
560 } 534 }
561 535
562 /// <summary> 536 /// <summary>
563 /// Localize dialogs and controls. 537 /// Localize dialogs and controls.
564 /// </summary> 538 /// </summary>
565 /// <param name="tables">The tables to localize.</param> 539 /// <param name="tables">The tables to localize.</param>
566 private void LocalizeUI(TableIndexedCollection tables) 540 private void LocalizeUI(IntermediateSection section)
567 { 541 {
568 Table dialogTable = tables["Dialog"]; 542 foreach (var row in section.Tuples.OfType<DialogTuple>())
569 if (null != dialogTable)
570 { 543 {
571 foreach (Row row in dialogTable.Rows) 544 string dialog = row.Dialog;
572 {
573 string dialog = (string)row[0];
574 545
575 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl)) 546 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, null, out LocalizedControl localizedControl))
547 {
548 if (CompilerConstants.IntegerNotSet != localizedControl.X)
576 { 549 {
577 if (CompilerConstants.IntegerNotSet != localizedControl.X) 550 row.HCentering = localizedControl.X;
578 { 551 }
579 row[1] = localizedControl.X;
580 }
581 552
582 if (CompilerConstants.IntegerNotSet != localizedControl.Y) 553 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
583 { 554 {
584 row[2] = localizedControl.Y; 555 row.VCentering = localizedControl.Y;
585 } 556 }
586 557
587 if (CompilerConstants.IntegerNotSet != localizedControl.Width) 558 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
588 { 559 {
589 row[3] = localizedControl.Width; 560 row.Width = localizedControl.Width;
590 } 561 }
591 562
592 if (CompilerConstants.IntegerNotSet != localizedControl.Height) 563 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
593 { 564 {
594 row[4] = localizedControl.Height; 565 row.Height = localizedControl.Height;
595 } 566 }
596 567
597 row[5] = (int)row[5] | localizedControl.Attributes; 568 row.Attributes = row.Attributes | localizedControl.Attributes;
598 569
599 if (!String.IsNullOrEmpty(localizedControl.Text)) 570 if (!String.IsNullOrEmpty(localizedControl.Text))
600 { 571 {
601 row[6] = localizedControl.Text; 572 row.Title = localizedControl.Text;
602 }
603 } 573 }
604 } 574 }
605 } 575 }
606 576
607 Table controlTable = tables["Control"]; 577
608 if (null != controlTable) 578 foreach (var row in section.Tuples.OfType<ControlTuple>())
609 { 579 {
610 foreach (Row row in controlTable.Rows) 580 string dialog = row.Dialog_;
611 { 581 string control = row.Control;
612 string dialog = (string)row[0];
613 string control = (string)row[1];
614 582
615 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl)) 583 if (this.WixVariableResolver.TryGetLocalizedControl(dialog, control, out LocalizedControl localizedControl))
584 {
585 if (CompilerConstants.IntegerNotSet != localizedControl.X)
616 { 586 {
617 if (CompilerConstants.IntegerNotSet != localizedControl.X) 587 row.X = localizedControl.X;
618 { 588 }
619 row[3] = localizedControl.X.ToString();
620 }
621 589
622 if (CompilerConstants.IntegerNotSet != localizedControl.Y) 590 if (CompilerConstants.IntegerNotSet != localizedControl.Y)
623 { 591 {
624 row[4] = localizedControl.Y.ToString(); 592 row.Y = localizedControl.Y;
625 } 593 }
626 594
627 if (CompilerConstants.IntegerNotSet != localizedControl.Width) 595 if (CompilerConstants.IntegerNotSet != localizedControl.Width)
628 { 596 {
629 row[5] = localizedControl.Width.ToString(); 597 row.Width = localizedControl.Width;
630 } 598 }
631 599
632 if (CompilerConstants.IntegerNotSet != localizedControl.Height) 600 if (CompilerConstants.IntegerNotSet != localizedControl.Height)
633 { 601 {
634 row[6] = localizedControl.Height.ToString(); 602 row.Height = localizedControl.Height;
635 } 603 }
636 604
637 row[7] = (int)row[7] | localizedControl.Attributes; 605 row.Attributes = row.Attributes | localizedControl.Attributes;
638 606
639 if (!String.IsNullOrEmpty(localizedControl.Text)) 607 if (!String.IsNullOrEmpty(localizedControl.Text))
640 { 608 {
641 row[9] = localizedControl.Text; 609 row.Text = localizedControl.Text;
642 }
643 } 610 }
644 } 611 }
645 } 612 }
646 } 613 }
647 614
615#if TODO_FINISH_PATCH
648 /// <summary> 616 /// <summary>
649 /// Copy file data between transform substorages and the patch output object 617 /// Copy file data between transform substorages and the patch output object
650 /// </summary> 618 /// </summary>
@@ -674,269 +642,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
674 command.TableDefinitions = this.TableDefinitions; 642 command.TableDefinitions = this.TableDefinitions;
675 command.Execute(); 643 command.Execute();
676 } 644 }
645#endif
677 646
678 private void UpdateMediaSequences(OutputType outputType, IEnumerable<FileFacade> fileFacades, RowDictionary<MediaRow> mediaRows)
679 {
680 // Calculate sequence numbers and media disk id layout for all file media information objects.
681 if (OutputType.Module == outputType)
682 {
683 int lastSequence = 0;
684 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.
685 {
686 facade.File.Sequence = ++lastSequence;
687 }
688 }
689 else
690 {
691 int lastSequence = 0;
692 MediaRow mediaRow = null;
693 Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>();
694
695 // sequence the non-patch-added files
696 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.
697 {
698 if (null == mediaRow)
699 {
700 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
701 if (OutputType.Patch == outputType)
702 {
703 // patch Media cannot start at zero
704 lastSequence = mediaRow.LastSequence;
705 }
706 }
707 else if (mediaRow.DiskId != facade.WixFile.DiskId)
708 {
709 mediaRow.LastSequence = lastSequence;
710 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
711 }
712
713 if (0 < facade.WixFile.PatchGroup)
714 {
715 List<FileFacade> patchGroup = patchGroups[facade.WixFile.PatchGroup];
716
717 if (null == patchGroup)
718 {
719 patchGroup = new List<FileFacade>();
720 patchGroups.Add(facade.WixFile.PatchGroup, patchGroup);
721 }
722
723 patchGroup.Add(facade);
724 }
725 else
726 {
727 facade.File.Sequence = ++lastSequence;
728 }
729 }
730
731 if (null != mediaRow)
732 {
733 mediaRow.LastSequence = lastSequence;
734 mediaRow = null;
735 }
736
737 // sequence the patch-added files
738 foreach (List<FileFacade> patchGroup in patchGroups.Values)
739 {
740 foreach (FileFacade facade in patchGroup)
741 {
742 if (null == mediaRow)
743 {
744 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
745 }
746 else if (mediaRow.DiskId != facade.WixFile.DiskId)
747 {
748 mediaRow.LastSequence = lastSequence;
749 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
750 }
751
752 facade.File.Sequence = ++lastSequence;
753 }
754 }
755
756 if (null != mediaRow)
757 {
758 mediaRow.LastSequence = lastSequence;
759 }
760 }
761 }
762
763 /// <summary>
764 /// Set the guids for components with generatable guids.
765 /// </summary>
766 /// <param name="output">Internal representation of the database to operate on.</param>
767 private void SetComponentGuids(Output output)
768 {
769 Table componentTable = output.Tables["Component"];
770 if (null != componentTable)
771 {
772 Hashtable registryKeyRows = null;
773 Hashtable directories = null;
774 Hashtable componentIdGenSeeds = null;
775 Dictionary<string, List<FileRow>> fileRows = null;
776
777 // find components with generatable guids
778 foreach (ComponentRow componentRow in componentTable.Rows)
779 {
780 // component guid will be generated
781 if ("*" == componentRow.Guid)
782 {
783 if (null == componentRow.KeyPath || componentRow.IsOdbcDataSourceKeyPath)
784 {
785 Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers));
786 }
787 else if (componentRow.IsRegistryKeyPath)
788 {
789 if (null == registryKeyRows)
790 {
791 Table registryTable = output.Tables["Registry"];
792
793 registryKeyRows = new Hashtable(registryTable.Rows.Count);
794
795 foreach (Row registryRow in registryTable.Rows)
796 {
797 registryKeyRows.Add((string)registryRow[0], registryRow);
798 }
799 }
800
801 Row foundRow = registryKeyRows[componentRow.KeyPath] as Row;
802
803 string bitness = componentRow.Is64Bit ? "64" : String.Empty;
804 if (null != foundRow)
805 {
806 string regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]);
807 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant();
808 }
809 }
810 else // must be a File KeyPath
811 {
812 // if the directory table hasn't been loaded into an indexed hash
813 // of directory ids to target names do that now.
814 if (null == directories)
815 {
816 Table directoryTable = output.Tables["Directory"];
817
818 int numDirectoryTableRows = (null != directoryTable) ? directoryTable.Rows.Count : 0;
819
820 directories = new Hashtable(numDirectoryTableRows);
821
822 // get the target paths for all directories
823 if (null != directoryTable)
824 {
825 foreach (Row row in directoryTable.Rows)
826 {
827 // if the directory Id already exists, we will skip it here since
828 // checking for duplicate primary keys is done later when importing tables
829 // into database
830 if (directories.ContainsKey(row[0]))
831 {
832 continue;
833 }
834
835 string targetName = Common.GetName((string)row[2], false, true);
836 directories.Add(row[0], new ResolvedDirectory((string)row[1], targetName));
837 }
838 }
839 }
840
841 // if the component id generation seeds have not been indexed
842 // from the WixDirectory table do that now.
843 if (null == componentIdGenSeeds)
844 {
845 Table wixDirectoryTable = output.Tables["WixDirectory"];
846
847 int numWixDirectoryRows = (null != wixDirectoryTable) ? wixDirectoryTable.Rows.Count : 0;
848
849 componentIdGenSeeds = new Hashtable(numWixDirectoryRows);
850
851 // if there are any WixDirectory rows, build up the Component Guid
852 // generation seeds indexed by Directory/@Id.
853 if (null != wixDirectoryTable)
854 {
855 foreach (Row row in wixDirectoryTable.Rows)
856 {
857 componentIdGenSeeds.Add(row[0], (string)row[1]);
858 }
859 }
860 }
861
862 // if the file rows have not been indexed by File.Component yet
863 // then do that now
864 if (null == fileRows)
865 {
866 Table fileTable = output.Tables["File"];
867
868 int numFileRows = (null != fileTable) ? fileTable.Rows.Count : 0;
869
870 fileRows = new Dictionary<string, List<FileRow>>(numFileRows);
871
872 if (null != fileTable)
873 {
874 foreach (FileRow file in fileTable.Rows)
875 {
876 List<FileRow> files;
877 if (!fileRows.TryGetValue(file.Component, out files))
878 {
879 files = new List<FileRow>();
880 fileRows.Add(file.Component, files);
881 }
882
883 files.Add(file);
884 }
885 }
886 }
887
888 // validate component meets all the conditions to have a generated guid
889 List<FileRow> currentComponentFiles = fileRows[componentRow.Component];
890 int numFilesInComponent = currentComponentFiles.Count;
891 string path = null;
892
893 foreach (FileRow fileRow in currentComponentFiles)
894 {
895 if (fileRow.File == componentRow.KeyPath)
896 {
897 // calculate the key file's canonical target path
898 string directoryPath = Binder.GetDirectoryPath(directories, componentIdGenSeeds, componentRow.Directory, true);
899 string fileName = Common.GetName(fileRow.FileName, false, true).ToLower(CultureInfo.InvariantCulture);
900 path = Path.Combine(directoryPath, fileName);
901
902 // find paths that are not canonicalized
903 if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
904 path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
905 path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
906 path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
907 path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
908 path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
909 {
910 Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component, path));
911 }
912
913 // if component has more than one file, the key path must be versioned
914 if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version))
915 {
916 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers));
917 }
918 }
919 else
920 {
921 // not a key path, so it must be an unversioned file if component has more than one file
922 if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version))
923 {
924 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers));
925 }
926 }
927 }
928
929 // if the rules were followed, reward with a generated guid
930 if (!Messaging.Instance.EncounteredError)
931 {
932 componentRow.Guid = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant();
933 }
934 }
935 }
936 }
937 }
938 }
939 647
648#if TODO_FIX_INSTANCE_TRANSFORM
940 /// <summary> 649 /// <summary>
941 /// Creates instance transform substorages in the output. 650 /// Creates instance transform substorages in the output.
942 /// </summary> 651 /// </summary>
@@ -1171,6 +880,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1171 } 880 }
1172 } 881 }
1173 } 882 }
883#endif
1174 884
1175 /// <summary> 885 /// <summary>
1176 /// Validate that there are no duplicate GUIDs in the output. 886 /// Validate that there are no duplicate GUIDs in the output.
@@ -1186,7 +896,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1186 { 896 {
1187 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count); 897 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
1188 898
1189 foreach (ComponentRow row in componentTable.Rows) 899 foreach (Data.Rows.ComponentRow row in componentTable.Rows)
1190 { 900 {
1191 // we don't care about unmanaged components and if there's a * GUID remaining, 901 // we don't care about unmanaged components and if there's a * GUID remaining,
1192 // there's already an error that prevented it from being replaced with a real GUID. 902 // there's already an error that prevented it from being replaced with a real GUID.
@@ -1221,7 +931,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1221 /// <param name="output">Internal representation of the msi database to operate upon.</param> 931 /// <param name="output">Internal representation of the msi database to operate upon.</param>
1222 private void UpdateControlText(Output output) 932 private void UpdateControlText(Output output)
1223 { 933 {
1224 UpdateControlTextCommand command = new UpdateControlTextCommand(); 934 var command = new UpdateControlTextCommand();
1225 command.BBControlTable = output.Tables["BBControl"]; 935 command.BBControlTable = output.Tables["BBControl"];
1226 command.WixBBControlTable = output.Tables["WixBBControl"]; 936 command.WixBBControlTable = output.Tables["WixBBControl"];
1227 command.ControlTable = output.Tables["Control"]; 937 command.ControlTable = output.Tables["Control"];
@@ -1229,7 +939,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1229 command.Execute(); 939 command.Execute();
1230 } 940 }
1231 941
1232 private string ResolveMedia(MediaRow mediaRow, string mediaLayoutDirectory, string layoutDirectory) 942 private string ResolveMedia(MediaTuple mediaRow, string mediaLayoutDirectory, string layoutDirectory)
1233 { 943 {
1234 string layout = null; 944 string layout = null;
1235 945
@@ -1282,7 +992,6 @@ namespace WixToolset.Core.WindowsInstaller.Bind
1282 command.TempFilesLocation = this.IntermediateFolder; 992 command.TempFilesLocation = this.IntermediateFolder;
1283 command.Codepage = this.Codepage; 993 command.Codepage = this.Codepage;
1284 command.Execute(); 994 command.Execute();
1285#endif
1286 } 995 }
1287 } 996 }
1288} 997}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs
index 5471792d..c9286a38 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindSummaryInfoCommand.cs
@@ -1,20 +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. 1// Copyright (c) .NET Foundation 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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Globalization; 6 using System.Globalization;
7 using System.Linq;
7 using WixToolset.Data; 8 using WixToolset.Data;
9 using WixToolset.Data.Tuples;
8 10
9 /// <summary> 11 /// <summary>
10 /// Binds the summary information table of a database. 12 /// Binds the summary information table of a database.
11 /// </summary> 13 /// </summary>
12 internal class BindSummaryInfoCommand 14 internal class BindSummaryInfoCommand
13 { 15 {
14 /// <summary> 16 public BindSummaryInfoCommand(IntermediateSection section)
15 /// The output to bind. 17 {
16 /// </summary> 18 this.Section = section;
17 public Output Output { private get; set; } 19 }
20
21 private IntermediateSection Section { get; }
18 22
19 /// <summary> 23 /// <summary>
20 /// Returns a flag indicating if files are compressed by default. 24 /// Returns a flag indicating if files are compressed by default.
@@ -40,95 +44,97 @@ namespace WixToolset.Core.WindowsInstaller.Databases
40 this.InstallerVersion = 0; 44 this.InstallerVersion = 0;
41 this.ModularizationGuid = null; 45 this.ModularizationGuid = null;
42 46
43 Table summaryInformationTable = this.Output.Tables["_SummaryInformation"]; 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);
44 51
45 if (null != summaryInformationTable) 52 foreach (var summaryInformationRow in this.Section.Tuples.OfType<_SummaryInformationTuple>())
46 { 53 {
47 bool foundCreateDataTime = false; 54 switch (summaryInformationRow.PropertyId)
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 { 55 {
54 switch (summaryInformationRow.FieldAsInteger(0)) 56 case 1: // PID_CODEPAGE
55 {
56 case 1: // PID_CODEPAGE
57 // make sure the code page is an int and not a web name or null 57 // make sure the code page is an int and not a web name or null
58 string codepage = summaryInformationRow.FieldAsString(1); 58 var codepage = summaryInformationRow.Value;
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 59
109 // add a summary information row for the create time/date property if its not already set 60 if (String.IsNullOrEmpty(codepage))
110 if (!foundCreateDataTime) 61 {
111 { 62 codepage = "0";
112 Row createTimeDateRow = summaryInformationTable.CreateRow(null); 63 }
113 createTimeDateRow[0] = 12; 64 else
114 createTimeDateRow[1] = now; 65 {
115 } 66 summaryInformationRow.Value = Common.GetValidCodePage(codepage, false, false, summaryInformationRow.SourceLineNumbers).ToString(CultureInfo.InvariantCulture);
67 }
68 break;
69 case 9: // PID_REVNUMBER
70 var packageCode = summaryInformationRow.Value;
116 71
117 // add a summary information row for the last save time/date property if its not already set 72 if (SectionType.Module == this.Section.Type)
118 if (!foundLastSaveDataTime) 73 {
119 { 74 this.ModularizationGuid = packageCode.Substring(1, 36).Replace('-', '_');
120 Row lastSaveTimeDateRow = summaryInformationTable.CreateRow(null); 75 }
121 lastSaveTimeDateRow[0] = 13; 76 else if ("*" == packageCode)
122 lastSaveTimeDateRow[1] = now; 77 {
78 // set the revision number (package/patch code) if it should be automatically generated
79 summaryInformationRow.Value = 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[_SummaryInformationTupleFields.Value].AsNumber();
90 break;
91 case 15: // PID_WORDCOUNT
92 if (SectionType.Patch == this.Section.Type)
93 {
94 this.LongNames = true;
95 this.Compressed = true;
96 }
97 else
98 {
99 var attributes = summaryInformationRow[_SummaryInformationTupleFields.Value].AsNumber();
100 this.LongNames = (0 == (attributes & 1));
101 this.Compressed = (2 == (attributes & 2));
102 }
103 break;
104 case 18: // PID_APPNAME
105 foundCreatingApplication = true;
106 break;
123 } 107 }
108 }
124 109
125 // add a summary information row for the creating application property if its not already set 110 // add a summary information row for the create time/date property if its not already set
126 if (!foundCreatingApplication) 111 if (!foundCreateDataTime)
127 { 112 {
128 Row creatingApplicationRow = summaryInformationTable.CreateRow(null); 113 var createTimeDateRow = new _SummaryInformationTuple(null, new Identifier(12, AccessModifier.Private));
129 creatingApplicationRow[0] = 18; 114 createTimeDateRow.PropertyId = 12;
130 creatingApplicationRow[1] = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString()); 115 createTimeDateRow.Value = now;
131 } 116
117 this.Section.Tuples.Add(createTimeDateRow);
118 }
119
120 // add a summary information row for the last save time/date property if its not already set
121 if (!foundLastSaveDataTime)
122 {
123 var lastSaveTimeDateRow = new _SummaryInformationTuple(null, new Identifier(13, AccessModifier.Private));
124 lastSaveTimeDateRow.PropertyId = 13;
125 lastSaveTimeDateRow.Value = now;
126
127 this.Section.Tuples.Add(lastSaveTimeDateRow);
128 }
129
130 // add a summary information row for the creating application property if its not already set
131 if (!foundCreatingApplication)
132 {
133 var creatingApplicationRow = new _SummaryInformationTuple(null, new Identifier(18, AccessModifier.Private));
134 creatingApplicationRow.PropertyId = 18;
135 creatingApplicationRow.Value = String.Format(CultureInfo.InvariantCulture, AppCommon.GetCreatingApplicationString());
136
137 this.Section.Tuples.Add(creatingApplicationRow);
132 } 138 }
133 } 139 }
134 } 140 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
index 425d1f9c..b4027834 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
index b2cc76fc..c25a497e 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetBuilder.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections; 6 using System.Collections;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
index dcafcd36..405b840b 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CabinetWorkItem.cs
@@ -1,11 +1,10 @@
1// Copyright (c) .NET 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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System.Collections.Generic; 5 using System.Collections.Generic;
6 using WixToolset.Core.Bind; 6 using WixToolset.Core.Bind;
7 using WixToolset.Data; 7 using WixToolset.Data;
8 using WixToolset.Data.Rows;
9 8
10 /// <summary> 9 /// <summary>
11 /// A cabinet builder work item. 10 /// A cabinet builder work item.
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs
new file mode 100644
index 00000000..0c0aea1f
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CalculateComponentGuids.cs
@@ -0,0 +1,174 @@
1// Copyright (c) .NET Foundation 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.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using WixToolset.Bind;
10 using WixToolset.Core.Native;
11 using WixToolset.Data;
12 using WixToolset.Data.Tuples;
13
14 /// <summary>
15 /// Set the guids for components with generatable guids.
16 /// </summary>
17 internal class CalculateComponentGuids
18 {
19 public CalculateComponentGuids(IntermediateSection section)
20 {
21 this.Section = section;
22 }
23
24 private IntermediateSection Section { get; }
25
26 public void Execute()
27 {
28 Dictionary<string, RegistryTuple> registryKeyRows = null;
29 Dictionary<string, ResolvedDirectory> targetPathsByDirectoryId = null;
30 Dictionary<string, string> componentIdGenSeeds = null;
31 Dictionary<string, List<FileTuple>> filesByComponentId = null;
32
33 // Find components with generatable guids.
34 foreach (var componentRow in this.Section.Tuples.OfType<ComponentTuple>())
35 {
36 // Skip components that do not specify generate guid.
37 if (componentRow.ComponentId != "*")
38 {
39 continue;
40 }
41
42 var odbcDataSourceKeyPath = (componentRow.Attributes & MsiInterop.MsidbComponentAttributesODBCDataSource) != 0;
43
44 if (String.IsNullOrEmpty(componentRow.KeyPath) || odbcDataSourceKeyPath)
45 {
46 Messaging.Instance.OnMessage(WixErrors.IllegalComponentWithAutoGeneratedGuid(componentRow.SourceLineNumbers));
47 continue;
48 }
49
50 var registryKeyPath = (componentRow.Attributes & MsiInterop.MsidbComponentAttributesRegistryKeyPath) != 0;
51
52 if (registryKeyPath)
53 {
54 if (registryKeyRows is null)
55 {
56 registryKeyRows = this.Section.Tuples.OfType<RegistryTuple>().ToDictionary(t => t.Registry);
57 }
58
59 if (registryKeyRows.TryGetValue(componentRow.KeyPath, out var foundRow))
60 {
61 var is64Bit = (componentRow.Attributes & MsiInterop.MsidbComponentAttributes64bit) != 0;
62 var bitness = is64Bit ? "64" : String.Empty;
63 var regkey = String.Concat(bitness, foundRow[1], "\\", foundRow[2], "\\", foundRow[3]);
64 componentRow.ComponentId = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, regkey.ToLowerInvariant()).ToString("B").ToUpperInvariant();
65 }
66 }
67 else // must be a File KeyPath.
68 {
69 // If the directory table hasn't been loaded into an indexed hash
70 // of directory ids to target names do that now.
71 if (targetPathsByDirectoryId is null)
72 {
73 var directories = this.Section.Tuples.OfType<DirectoryTuple>().ToList();
74
75 targetPathsByDirectoryId = new Dictionary<string, ResolvedDirectory>(directories.Count);
76
77 // Get the target paths for all directories.
78 foreach (var row in directories)
79 {
80 // If the directory Id already exists, we will skip it here since
81 // checking for duplicate primary keys is done later when importing tables
82 // into database
83 if (targetPathsByDirectoryId.ContainsKey(row.Directory))
84 {
85 continue;
86 }
87
88 var targetName = Common.GetName(row.DefaultDir, false, true);
89 targetPathsByDirectoryId.Add(row.Directory, new ResolvedDirectory(row.Directory_Parent, targetName));
90 }
91 }
92
93 // If the component id generation seeds have not been indexed
94 // from the WixDirectory table do that now.
95 if (componentIdGenSeeds is null)
96 {
97 // If there are any WixDirectory rows, build up the Component Guid
98 // generation seeds indexed by Directory/@Id.
99 componentIdGenSeeds = this.Section.Tuples.OfType<WixDirectoryTuple>()
100 .Where(t => !String.IsNullOrEmpty(t.ComponentGuidGenerationSeed))
101 .ToDictionary(t => t.Directory_, t => t.ComponentGuidGenerationSeed);
102 }
103
104 // if the file rows have not been indexed by File.Component yet
105 // then do that now
106 if (filesByComponentId is null)
107 {
108 var files = this.Section.Tuples.OfType<FileTuple>().ToList();
109
110 filesByComponentId = new Dictionary<string, List<FileTuple>>(files.Count);
111
112 foreach (var file in files)
113 {
114 if (!filesByComponentId.TryGetValue(file.Component_, out var componentFiles))
115 {
116 componentFiles = new List<FileTuple>();
117 filesByComponentId.Add(file.Component_, componentFiles);
118 }
119
120 componentFiles.Add(file);
121 }
122 }
123
124 // validate component meets all the conditions to have a generated guid
125 var currentComponentFiles = filesByComponentId[componentRow.Component];
126 var numFilesInComponent = currentComponentFiles.Count;
127 string path = null;
128
129 foreach (var fileRow in currentComponentFiles)
130 {
131 if (fileRow.File == componentRow.KeyPath)
132 {
133 // calculate the key file's canonical target path
134 string directoryPath = Binder.GetDirectoryPath(targetPathsByDirectoryId, componentIdGenSeeds, componentRow.Directory_, true);
135 string fileName = Common.GetName(fileRow.LongFileName, false, true).ToLowerInvariant();
136 path = Path.Combine(directoryPath, fileName);
137
138 // find paths that are not canonicalized
139 if (path.StartsWith(@"PersonalFolder\my pictures", StringComparison.Ordinal) ||
140 path.StartsWith(@"ProgramFilesFolder\common files", StringComparison.Ordinal) ||
141 path.StartsWith(@"ProgramMenuFolder\startup", StringComparison.Ordinal) ||
142 path.StartsWith("TARGETDIR", StringComparison.Ordinal) ||
143 path.StartsWith(@"StartMenuFolder\programs", StringComparison.Ordinal) ||
144 path.StartsWith(@"WindowsFolder\fonts", StringComparison.Ordinal))
145 {
146 Messaging.Instance.OnMessage(WixErrors.IllegalPathForGeneratedComponentGuid(componentRow.SourceLineNumbers, fileRow.Component_, path));
147 }
148
149 // if component has more than one file, the key path must be versioned
150 if (1 < numFilesInComponent && String.IsNullOrEmpty(fileRow.Version))
151 {
152 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentUnversionedKeypath(componentRow.SourceLineNumbers));
153 }
154 }
155 else
156 {
157 // not a key path, so it must be an unversioned file if component has more than one file
158 if (1 < numFilesInComponent && !String.IsNullOrEmpty(fileRow.Version))
159 {
160 Messaging.Instance.OnMessage(WixErrors.IllegalGeneratedGuidComponentVersionedNonkeypath(componentRow.SourceLineNumbers));
161 }
162 }
163 }
164
165 // if the rules were followed, reward with a generated guid
166 if (!Messaging.Instance.EncounteredError)
167 {
168 componentRow.ComponentId = Uuid.NewUuid(BindDatabaseCommand.WixComponentGuidNamespace, path).ToString("B").ToUpperInvariant();
169 }
170 }
171 }
172 }
173 }
174}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
index d4d3799f..9a8e2bba 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ConfigurationCallback.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections; 6 using System.Collections;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
index 6388a352..0dcddb99 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -10,6 +10,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
10 using WixToolset.Extensibility; 10 using WixToolset.Extensibility;
11 using WixToolset.Core.Native; 11 using WixToolset.Core.Native;
12 using WixToolset.Core.Bind; 12 using WixToolset.Core.Bind;
13 using WixToolset.Data.Tuples;
13 14
14 internal class CopyTransformDataCommand 15 internal class CopyTransformDataCommand
15 { 16 {
@@ -29,7 +30,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
29 30
30 List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null; 31 List<FileFacade> allFileRows = this.CopyOutFileRows ? new List<FileFacade>() : null;
31 32
32#if false // TODO: Fix this patching related code to work correctly with FileFacades. 33#if REVISIT_FOR_PATCHING // TODO: Fix this patching related code to work correctly with FileFacades.
33 bool copyToPatch = (allFileRows != null); 34 bool copyToPatch = (allFileRows != null);
34 bool copyFromPatch = !copyToPatch; 35 bool copyFromPatch = !copyToPatch;
35 36
@@ -454,7 +455,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
454 } 455 }
455 456
456 Row patchAction = iesTable.CreateRow(null); 457 Row patchAction = iesTable.CreateRow(null);
457 WixActionRow wixPatchAction = WindowsInstallerStandard.GetStandardActions()[table, "PatchFiles"]; 458 WixActionRow wixPatchAction = WindowsInstallerStandardInternal.GetStandardActionRows()[table, "PatchFiles"];
458 int sequence = wixPatchAction.Sequence; 459 int sequence = wixPatchAction.Sequence;
459 // Test for default sequence value's appropriateness 460 // Test for default sequence value's appropriateness
460 if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence)) 461 if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence))
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
index 02015744..b5a436c5 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -10,10 +10,10 @@ namespace WixToolset.Core.WindowsInstaller.Databases
10 using System.Runtime.InteropServices; 10 using System.Runtime.InteropServices;
11 using System.Threading; 11 using System.Threading;
12 using WixToolset.Core.Bind; 12 using WixToolset.Core.Bind;
13 using WixToolset.Core.WindowsInstaller.Bind;
14 using WixToolset.Data; 13 using WixToolset.Data;
15 using WixToolset.Data.Bind; 14 using WixToolset.Data.Bind;
16 using WixToolset.Data.Rows; 15 using WixToolset.Data.Rows;
16 using WixToolset.Data.Tuples;
17 using WixToolset.Extensibility; 17 using WixToolset.Extensibility;
18 18
19 /// <summary> 19 /// <summary>
@@ -60,9 +60,9 @@ namespace WixToolset.Core.WindowsInstaller.Databases
60 60
61 public bool Compressed { private get; set; } 61 public bool Compressed { private get; set; }
62 62
63 public Dictionary<MediaRow, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; } 63 public Dictionary<MediaTuple, IEnumerable<FileFacade>> FileRowsByCabinet { private get; set; }
64 64
65 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } 65 public Func<MediaTuple, string, string, string> ResolveMedia { private get; set; }
66 66
67 public TableDefinitionCollection TableDefinitions { private get; set; } 67 public TableDefinitionCollection TableDefinitions { private get; set; }
68 68
@@ -77,7 +77,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
77 /// <returns>The uncompressed file rows.</returns> 77 /// <returns>The uncompressed file rows.</returns>
78 public void Execute() 78 public void Execute()
79 { 79 {
80 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); 80 var wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable);
81 81
82 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>(); 82 this.lastCabinetAddedToMediaTable = new Dictionary<string, string>();
83 83
@@ -87,22 +87,19 @@ namespace WixToolset.Core.WindowsInstaller.Databases
87 CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); 87 CabinetBuilder cabinetBuilder = new CabinetBuilder(this.CabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack));
88 88
89 // Supply Compile MediaTemplate Attributes to Cabinet Builder 89 // Supply Compile MediaTemplate Attributes to Cabinet Builder
90 int MaximumCabinetSizeForLargeFileSplitting; 90 this.GetMediaTemplateAttributes(out var MaximumCabinetSizeForLargeFileSplitting, out var MaximumUncompressedMediaSize);
91 int MaximumUncompressedMediaSize;
92 this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize);
93 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting; 91 cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting;
94 cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize; 92 cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize;
95 93
96 foreach (var entry in this.FileRowsByCabinet) 94 foreach (var entry in this.FileRowsByCabinet)
97 { 95 {
98 MediaRow mediaRow = entry.Key; 96 var mediaRow = entry.Key;
99 IEnumerable<FileFacade> files = entry.Value; 97 IEnumerable<FileFacade> files = entry.Value;
100 CompressionLevel compressionLevel = this.DefaultCompressionLevel; 98 CompressionLevel compressionLevel = this.DefaultCompressionLevel;
101 99
102 WixMediaRow wixMediaRow = null;
103 string mediaLayoutFolder = null; 100 string mediaLayoutFolder = null;
104 101
105 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) 102 if (wixMediaRows.TryGetValue(mediaRow.Id.Id, out var wixMediaRow))
106 { 103 {
107 mediaLayoutFolder = wixMediaRow.Layout; 104 mediaLayoutFolder = wixMediaRow.Layout;
108 105
@@ -185,7 +182,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
185 /// <param name="fileFacades">Collection of files in this cabinet.</param> 182 /// <param name="fileFacades">Collection of files in this cabinet.</param>
186 /// <param name="fileTransfers">Array of files to be transfered.</param> 183 /// <param name="fileTransfers">Array of files to be transfered.</param>
187 /// <returns>created CabinetWorkItem object</returns> 184 /// <returns>created CabinetWorkItem object</returns>
188 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers) 185 private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaTuple mediaRow, CompressionLevel compressionLevel, IEnumerable<FileFacade> fileFacades, List<FileTransfer> fileTransfers)
189 { 186 {
190 CabinetWorkItem cabinetWorkItem = null; 187 CabinetWorkItem cabinetWorkItem = null;
191 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); 188 string tempCabinetFileX = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet);
@@ -254,8 +251,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
254 else 251 else
255 { 252 {
256 string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); 253 string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet);
257 FileTransfer transfer; 254 if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out var transfer))
258 if (FileTransfer.TryCreate(resolvedCabinet.Path, destinationPath, CabinetBuildOption.BuildAndMove == resolvedCabinet.BuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer))
259 { 255 {
260 transfer.Built = true; 256 transfer.Built = true;
261 fileTransfers.Add(transfer); 257 fileTransfers.Add(transfer);
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs
index 42a69310..7c7b07cc 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateDeltaPatchesCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -9,49 +9,40 @@ namespace WixToolset.Core.WindowsInstaller.Databases
9 using WixToolset.Core.Bind; 9 using WixToolset.Core.Bind;
10 using WixToolset.Data; 10 using WixToolset.Data;
11 using WixToolset.Data.Rows; 11 using WixToolset.Data.Rows;
12 using WixToolset.Data.Tuples;
12 13
13 /// <summary> 14 /// <summary>
14 /// Creates delta patches and updates the appropriate rows to point to the newly generated patches. 15 /// Creates delta patches and updates the appropriate rows to point to the newly generated patches.
15 /// </summary> 16 /// </summary>
16 internal class CreateDeltaPatchesCommand 17 internal class CreateDeltaPatchesCommand
17 { 18 {
18 public IEnumerable<FileFacade> FileFacades { private get; set; } 19 public CreateDeltaPatchesCommand(List<FileFacade> fileFacades, string intermediateFolder, WixPatchIdTuple wixPatchId)
20 {
21 this.FileFacades = fileFacades;
22 this.IntermediateFolder = intermediateFolder;
23 this.WixPatchId = wixPatchId;
24 }
19 25
20 public Table WixPatchIdTable { private get; set; } 26 private IEnumerable<FileFacade> FileFacades { get; }
21 27
22 public string TempFilesLocation { private get; set; } 28 private WixPatchIdTuple WixPatchId { get; }
29
30 private string IntermediateFolder { get; }
23 31
24 public void Execute() 32 public void Execute()
25 { 33 {
26#if REVISIT_FOR_PATCHING 34 var optimizePatchSizeForLargeFiles = this.WixPatchId?.OptimizePatchSizeForLargeFiles ?? false;
27 bool optimizePatchSizeForLargeFiles = false; 35 var apiPatchingSymbolFlags = (PatchSymbolFlagsType)(this.WixPatchId?.ApiPatchingSymbolFlags ?? 0);
28 PatchSymbolFlagsType apiPatchingSymbolFlags = 0;
29
30 if (null != this.WixPatchIdTable)
31 {
32 Row row = this.WixPatchIdTable.Rows[0];
33 if (null != row)
34 {
35 if (null != row[2])
36 {
37 optimizePatchSizeForLargeFiles = (1 == Convert.ToUInt32(row[2], CultureInfo.InvariantCulture));
38 }
39
40 if (null != row[3])
41 {
42 apiPatchingSymbolFlags = (PatchSymbolFlagsType)Convert.ToUInt32(row[3], CultureInfo.InvariantCulture);
43 }
44 }
45 }
46 36
37#if REVISIT_FOR_PATCHING
47 foreach (FileFacade facade in this.FileFacades) 38 foreach (FileFacade facade in this.FileFacades)
48 { 39 {
49 if (RowOperation.Modify == facade.File.Operation && 40 if (RowOperation.Modify == facade.File.Operation &&
50 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile)) 41 0 != (facade.WixFile.PatchAttributes & PatchAttributeType.IncludeWholeFile))
51 { 42 {
52 string deltaBase = String.Concat("delta_", facade.File.File); 43 string deltaBase = String.Concat("delta_", facade.File.File);
53 string deltaFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".dpf")); 44 string deltaFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".dpf"));
54 string headerFile = Path.Combine(this.TempFilesLocation, String.Concat(deltaBase, ".phd")); 45 string headerFile = Path.Combine(this.IntermediateFolder, String.Concat(deltaBase, ".phd"));
55 46
56 bool retainRangeWarning = false; 47 bool retainRangeWarning = false;
57 48
@@ -84,6 +75,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
84 } 75 }
85 } 76 }
86#endif 77#endif
78
87 throw new NotImplementedException(); 79 throw new NotImplementedException();
88 } 80 }
89 } 81 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
new file mode 100644
index 00000000..85b3b25a
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
@@ -0,0 +1,230 @@
1// Copyright (c) .NET Foundation 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.WindowsInstaller.Bind
4{
5 using System;
6 using System.Linq;
7 using WixToolset.Core.Native;
8 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Data.Tuples;
11
12 internal class CreateOutputFromIRCommand
13 {
14 public CreateOutputFromIRCommand(IntermediateSection section, TableDefinitionCollection tableDefinitions)
15 {
16 this.Section = section;
17 this.TableDefinitions = tableDefinitions;
18 }
19
20 private TableDefinitionCollection TableDefinitions { get; }
21
22 private IntermediateSection Section { get; }
23
24 public Output Output { get; private set; }
25
26 public void Execute()
27 {
28 var output = new Output(this.Section.Tuples.First().SourceLineNumbers);
29 output.Codepage = this.Section.Codepage;
30 output.Type = SectionTypeToOutputType(this.Section.Type);
31
32 this.AddSectionToOutput(this.Section, output);
33
34 this.Output = output;
35 }
36
37 private void AddSectionToOutput(IntermediateSection section, Output output)
38 {
39 foreach (var tuple in section.Tuples)
40 {
41 switch (tuple.Definition.Type)
42 {
43 case TupleDefinitionType.File:
44 this.AddFileTuple((FileTuple)tuple, output);
45 break;
46
47 case TupleDefinitionType.WixAction:
48 this.AddWixActionTuple((WixActionTuple)tuple, output);
49 break;
50
51 default:
52 this.AddTupleDefaultly(tuple, output);
53 break;
54 }
55 }
56 }
57
58 private void AddFileTuple(FileTuple tuple, Output output)
59 {
60 var table = output.EnsureTable(this.TableDefinitions["File"]);
61 var row = (FileRow)table.CreateRow(tuple.SourceLineNumbers);
62 row.File = tuple.File;
63 row.Component = tuple.Component_;
64 row.FileName = GetMsiFilenameValue(tuple.ShortFileName, tuple.LongFileName);
65 row.FileSize = tuple.FileSize;
66 row.Version = tuple.Version;
67 row.Language = tuple.Language;
68
69 var attributes = tuple.Checksum ? MsiInterop.MsidbFileAttributesChecksum : 0;
70 attributes |= (tuple.Compressed.HasValue && tuple.Compressed.Value) ? MsiInterop.MsidbFileAttributesCompressed : 0;
71 attributes |= (tuple.Compressed.HasValue && !tuple.Compressed.Value) ? MsiInterop.MsidbFileAttributesNoncompressed : 0;
72 attributes |= tuple.Hidden ? MsiInterop.MsidbFileAttributesHidden : 0;
73 attributes |= tuple.ReadOnly ? MsiInterop.MsidbFileAttributesReadOnly : 0;
74 attributes |= tuple.System ? MsiInterop.MsidbFileAttributesSystem : 0;
75 attributes |= tuple.Vital ? MsiInterop.MsidbFileAttributesVital : 0;
76 row.Attributes = attributes;
77 }
78
79 private void AddWixActionTuple(WixActionTuple actionRow, Output output)
80 {
81 // Get the table definition for the action (and ensure the proper table exists for a module).
82 TableDefinition sequenceTableDefinition = null;
83 switch (actionRow.SequenceTable)
84 {
85 case SequenceTable.AdminExecuteSequence:
86 if (OutputType.Module == output.Type)
87 {
88 output.EnsureTable(this.TableDefinitions["AdminExecuteSequence"]);
89 sequenceTableDefinition = this.TableDefinitions["ModuleAdminExecuteSequence"];
90 }
91 else
92 {
93 sequenceTableDefinition = this.TableDefinitions["AdminExecuteSequence"];
94 }
95 break;
96 case SequenceTable.AdminUISequence:
97 if (OutputType.Module == output.Type)
98 {
99 output.EnsureTable(this.TableDefinitions["AdminUISequence"]);
100 sequenceTableDefinition = this.TableDefinitions["ModuleAdminUISequence"];
101 }
102 else
103 {
104 sequenceTableDefinition = this.TableDefinitions["AdminUISequence"];
105 }
106 break;
107 case SequenceTable.AdvtExecuteSequence:
108 if (OutputType.Module == output.Type)
109 {
110 output.EnsureTable(this.TableDefinitions["AdvtExecuteSequence"]);
111 sequenceTableDefinition = this.TableDefinitions["ModuleAdvtExecuteSequence"];
112 }
113 else
114 {
115 sequenceTableDefinition = this.TableDefinitions["AdvtExecuteSequence"];
116 }
117 break;
118 case SequenceTable.InstallExecuteSequence:
119 if (OutputType.Module == output.Type)
120 {
121 output.EnsureTable(this.TableDefinitions["InstallExecuteSequence"]);
122 sequenceTableDefinition = this.TableDefinitions["ModuleInstallExecuteSequence"];
123 }
124 else
125 {
126 sequenceTableDefinition = this.TableDefinitions["InstallExecuteSequence"];
127 }
128 break;
129 case SequenceTable.InstallUISequence:
130 if (OutputType.Module == output.Type)
131 {
132 output.EnsureTable(this.TableDefinitions["InstallUISequence"]);
133 sequenceTableDefinition = this.TableDefinitions["ModuleInstallUISequence"];
134 }
135 else
136 {
137 sequenceTableDefinition = this.TableDefinitions["InstallUISequence"];
138 }
139 break;
140 }
141
142 // create the action sequence row in the output
143 var sequenceTable = output.EnsureTable(sequenceTableDefinition);
144 var row = sequenceTable.CreateRow(actionRow.SourceLineNumbers);
145
146 if (SectionType.Module == this.Section.Type)
147 {
148 row[0] = actionRow.Action;
149 if (0 != actionRow.Sequence)
150 {
151 row[1] = actionRow.Sequence;
152 }
153 else
154 {
155 bool after = (null == actionRow.Before);
156 row[2] = after ? actionRow.After : actionRow.Before;
157 row[3] = after ? 1 : 0;
158 }
159 row[4] = actionRow.Condition;
160 }
161 else
162 {
163 row[0] = actionRow.Action;
164 row[1] = actionRow.Condition;
165 row[2] = actionRow.Sequence;
166 }
167 }
168
169 private void AddTupleDefaultly(IntermediateTuple tuple, Output output)
170 {
171 if (!this.TableDefinitions.TryGet(tuple.Definition.Name, out var tableDefinition))
172 {
173 return;
174 }
175
176 var table = output.EnsureTable(tableDefinition);
177 var row = table.CreateRow(tuple.SourceLineNumbers);
178 for (var i = 0; i < tuple.Fields.Length; ++i)
179 {
180 if (i < tableDefinition.Columns.Count)
181 {
182 var column = tableDefinition.Columns[i];
183
184 switch (column.Type)
185 {
186 case ColumnType.Number:
187 row[i] = tuple.AsNumber(i);
188 break;
189
190 default:
191 row[i] = tuple.AsString(i);
192 break;
193 }
194 }
195 }
196 }
197
198 private static OutputType SectionTypeToOutputType(SectionType type)
199 {
200 switch (type)
201 {
202 case SectionType.Bundle:
203 return OutputType.Bundle;
204 case SectionType.Module:
205 return OutputType.Module;
206 case SectionType.Product:
207 return OutputType.Product;
208 case SectionType.PatchCreation:
209 return OutputType.PatchCreation;
210 case SectionType.Patch:
211 return OutputType.Patch;
212
213 default:
214 throw new ArgumentOutOfRangeException(nameof(type));
215 }
216 }
217
218 private static string GetMsiFilenameValue(string shortName, string longName)
219 {
220 if (String.IsNullOrEmpty(shortName))
221 {
222 return longName;
223 }
224 else
225 {
226 return shortName + "|" + longName;
227 }
228 }
229 }
230}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs
index aef130b0..ab2e8201 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateSpecialPropertiesCommand.cs
@@ -1,67 +1,72 @@
1// Copyright (c) .NET 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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Linq;
7 using WixToolset.Data; 8 using WixToolset.Data;
8 using WixToolset.Data.Rows; 9 using WixToolset.Data.Tuples;
9 10
10 internal class CreateSpecialPropertiesCommand 11 internal class CreateSpecialPropertiesCommand
11 { 12 {
12 public Table PropertyTable { private get; set; } 13 public CreateSpecialPropertiesCommand(IntermediateSection section)
14 {
15 this.Section = section;
16 }
13 17
14 public Table WixPropertyTable { private get; set; } 18 private IntermediateSection Section { get; }
15 19
16 public void Execute() 20 public void Execute()
17 { 21 {
18 // Create the special properties. 22 // Create lists of the properties that contribute to the special lists of properties.
19 if (null != this.WixPropertyTable) 23 SortedSet<string> adminProperties = new SortedSet<string>();
20 { 24 SortedSet<string> secureProperties = new SortedSet<string>();
21 // Create lists of the properties that contribute to the special lists of properties. 25 SortedSet<string> hiddenProperties = new SortedSet<string>();
22 SortedSet<string> adminProperties = new SortedSet<string>();
23 SortedSet<string> secureProperties = new SortedSet<string>();
24 SortedSet<string> hiddenProperties = new SortedSet<string>();
25 26
26 foreach (WixPropertyRow wixPropertyRow in this.WixPropertyTable.Rows) 27 foreach (var wixPropertyRow in this.Section.Tuples.OfType<WixPropertyTuple>())
28 {
29 if (wixPropertyRow.Admin)
27 { 30 {
28 if (wixPropertyRow.Admin) 31 adminProperties.Add(wixPropertyRow.Property_);
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 } 32 }
43 33
44 Table propertyTable = this.PropertyTable; 34 if (wixPropertyRow.Hidden)
45 if (0 < adminProperties.Count)
46 { 35 {
47 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); 36 hiddenProperties.Add(wixPropertyRow.Property_);
48 row.Property = "AdminProperties";
49 row.Value = String.Join(";", adminProperties);
50 } 37 }
51 38
52 if (0 < secureProperties.Count) 39 if (wixPropertyRow.Secure)
53 { 40 {
54 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); 41 secureProperties.Add(wixPropertyRow.Property_);
55 row.Property = "SecureCustomProperties";
56 row.Value = String.Join(";", secureProperties);
57 } 42 }
43 }
58 44
59 if (0 < hiddenProperties.Count) 45 if (0 < adminProperties.Count)
60 { 46 {
61 PropertyRow row = (PropertyRow)propertyTable.CreateRow(null); 47 var tuple = new PropertyTuple(null, new Identifier("AdminProperties", AccessModifier.Private));
62 row.Property = "MsiHiddenProperties"; 48 tuple.Property = "AdminProperties";
63 row.Value = String.Join(";", hiddenProperties); 49 tuple.Value = String.Join(";", adminProperties);
64 } 50
51 this.Section.Tuples.Add(tuple);
52 }
53
54 if (0 < secureProperties.Count)
55 {
56 var tuple = new PropertyTuple(null, new Identifier("SecureCustomProperties", AccessModifier.Private));
57 tuple.Property = "SecureCustomProperties";
58 tuple.Value = String.Join(";", secureProperties);
59
60 this.Section.Tuples.Add(tuple);
61 }
62
63 if (0 < hiddenProperties.Count)
64 {
65 var tuple = new PropertyTuple(null, new Identifier("MsiHiddenProperties", AccessModifier.Private));
66 tuple.Property = "MsiHiddenProperties";
67 tuple.Value = String.Join(";", hiddenProperties);
68
69 this.Section.Tuples.Add(tuple);
65 } 70 }
66 } 71 }
67 } 72 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
index 0d3e7bd1..32d1cfda 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ExtractMergeModuleFilesCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -10,7 +10,6 @@ namespace WixToolset.Core.WindowsInstaller.Databases
10 using System.Linq; 10 using System.Linq;
11 using System.Runtime.InteropServices; 11 using System.Runtime.InteropServices;
12 using WixToolset.Data; 12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.MergeMod; 13 using WixToolset.MergeMod;
15 using WixToolset.Msi; 14 using WixToolset.Msi;
16 using WixToolset.Core.Native; 15 using WixToolset.Core.Native;
@@ -23,13 +22,17 @@ namespace WixToolset.Core.WindowsInstaller.Databases
23 /// </summary> 22 /// </summary>
24 internal class ExtractMergeModuleFilesCommand 23 internal class ExtractMergeModuleFilesCommand
25 { 24 {
26 public IEnumerable<FileFacade> FileFacades { private get; set; } 25 public ExtractMergeModuleFilesCommand(IntermediateSection section, List<WixMergeTuple> wixMergeTuples)
26 {
27 this.Section = section;
28 this.WixMergeTuples = wixMergeTuples;
29 }
27 30
28 public Table FileTable { private get; set; } 31 private IntermediateSection Section { get; }
29 32
30 public Table WixFileTable { private get; set; } 33 private List<WixMergeTuple> WixMergeTuples { get; }
31 34
32 public Table WixMergeTable { private get; set; } 35 public IEnumerable<FileFacade> FileFacades { private get; set; }
33 36
34 public int OutputInstallerVersion { private get; set; } 37 public int OutputInstallerVersion { private get; set; }
35 38
@@ -41,7 +44,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
41 44
42 public void Execute() 45 public void Execute()
43 { 46 {
44 List<FileFacade> mergeModulesFileFacades = new List<FileFacade>(); 47 var mergeModulesFileFacades = new List<FileFacade>();
45 48
46 IMsmMerge2 merge = MsmInterop.GetMsmMerge(); 49 IMsmMerge2 merge = MsmInterop.GetMsmMerge();
47 50
@@ -52,9 +55,9 @@ namespace WixToolset.Core.WindowsInstaller.Databases
52 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let 55 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
53 // this case be slightly more expensive because the cost of maintaining an indexed file row collection 56 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
54 // is a lot more costly for the common cases. 57 // is a lot more costly for the common cases.
55 Dictionary<string, FileFacade> indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal); 58 var indexedFileFacades = this.FileFacades.ToDictionary(f => f.File.File, StringComparer.Ordinal);
56 59
57 foreach (WixMergeRow wixMergeRow in this.WixMergeTable.Rows) 60 foreach (var wixMergeRow in this.WixMergeTuples)
58 { 61 {
59 bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades); 62 bool containsFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
60 63
@@ -68,7 +71,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
68 this.MergeModulesFileFacades = mergeModulesFileFacades; 71 this.MergeModulesFileFacades = mergeModulesFileFacades;
69 } 72 }
70 73
71 private bool CreateFacadesForMergeModuleFiles(WixMergeRow wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades) 74 private bool CreateFacadesForMergeModuleFiles(WixMergeTuple wixMergeRow, List<FileFacade> mergeModulesFileFacades, Dictionary<string, FileFacade> indexedFileFacades)
72 { 75 {
73 bool containsFiles = false; 76 bool containsFiles = false;
74 77
@@ -98,7 +101,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
98 // rows are created by merging in the actual modules. 101 // rows are created by merging in the actual modules.
99 var fileRow = new FileTuple(wixMergeRow.SourceLineNumbers, new Identifier(record[1], AccessModifier.Private)); 102 var fileRow = new FileTuple(wixMergeRow.SourceLineNumbers, new Identifier(record[1], AccessModifier.Private));
100 fileRow.File = record[1]; 103 fileRow.File = record[1];
101 fileRow.Compressed = (wixMergeRow.FileCompression == YesNoType.Yes) ? true : (wixMergeRow.FileCompression == YesNoType.No) ? (bool?)false : null; 104 fileRow.Compressed = wixMergeRow.FileCompression;
102 //FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); 105 //FileRow fileRow = (FileRow)this.FileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
103 //fileRow.File = record[1]; 106 //fileRow.File = record[1];
104 //fileRow.Compressed = wixMergeRow.FileCompression; 107 //fileRow.Compressed = wixMergeRow.FileCompression;
@@ -107,7 +110,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
107 wixFileRow.Directory_ = record[2]; 110 wixFileRow.Directory_ = record[2];
108 wixFileRow.DiskId = wixMergeRow.DiskId; 111 wixFileRow.DiskId = wixMergeRow.DiskId;
109 wixFileRow.PatchGroup = -1; 112 wixFileRow.PatchGroup = -1;
110 wixFileRow.Source = Path.Combine(this.IntermediateFolder, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture), record[1]); 113 wixFileRow.Source = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]);
111 //WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false); 114 //WixFileRow wixFileRow = (WixFileRow)this.WixFileTable.CreateRow(wixMergeRow.SourceLineNumbers, false);
112 //wixFileRow.Directory = record[2]; 115 //wixFileRow.Directory = record[2];
113 //wixFileRow.DiskId = wixMergeRow.DiskId; 116 //wixFileRow.DiskId = wixMergeRow.DiskId;
@@ -119,11 +122,11 @@ namespace WixToolset.Core.WindowsInstaller.Databases
119 // If case-sensitive collision with another merge module or a user-authored file identifier. 122 // If case-sensitive collision with another merge module or a user-authored file identifier.
120 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out var collidingFacade)) 123 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.File.File, out var collidingFacade))
121 { 124 {
122 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFacade.File.File)); 125 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.File.File));
123 } 126 }
124 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module 127 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.File.File, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
125 { 128 {
126 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File)); 129 Messaging.Instance.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.File.File, collidingFacade.File.File));
127 } 130 }
128 else // no collision 131 else // no collision
129 { 132 {
@@ -150,12 +153,12 @@ namespace WixToolset.Core.WindowsInstaller.Databases
150 int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); 153 int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
151 if (moduleInstallerVersion > this.OutputInstallerVersion) 154 if (moduleInstallerVersion > this.OutputInstallerVersion)
152 { 155 {
153 Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, this.OutputInstallerVersion)); 156 Messaging.Instance.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion));
154 } 157 }
155 } 158 }
156 catch (FormatException) 159 catch (FormatException)
157 { 160 {
158 throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); 161 throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString));
159 } 162 }
160 } 163 }
161 } 164 }
@@ -166,24 +169,26 @@ namespace WixToolset.Core.WindowsInstaller.Databases
166 } 169 }
167 catch (Win32Exception) 170 catch (Win32Exception)
168 { 171 {
169 throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); 172 throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile));
170 } 173 }
171 174
172 return containsFiles; 175 return containsFiles;
173 } 176 }
174 177
175 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeRow wixMergeRow) 178 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeTuple wixMergeRow)
176 { 179 {
177 bool moduleOpen = false; 180 bool moduleOpen = false;
178 short mergeLanguage; 181 short mergeLanguage;
179 182
183 var mergeId = wixMergeRow.Id.Id;
184
180 try 185 try
181 { 186 {
182 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); 187 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
183 } 188 }
184 catch (System.FormatException) 189 catch (FormatException)
185 { 190 {
186 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); 191 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString()));
187 return; 192 return;
188 } 193 }
189 194
@@ -192,13 +197,11 @@ namespace WixToolset.Core.WindowsInstaller.Databases
192 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); 197 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
193 moduleOpen = true; 198 moduleOpen = true;
194 199
195 string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat);
196
197 // extract the module cabinet, then explode all of the files to a temp directory 200 // extract the module cabinet, then explode all of the files to a temp directory
198 string moduleCabPath = String.Concat(this.IntermediateFolder, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); 201 string moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab");
199 merge.ExtractCAB(moduleCabPath); 202 merge.ExtractCAB(moduleCabPath);
200 203
201 string mergeIdPath = String.Concat(this.IntermediateFolder, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); 204 string mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId);
202 Directory.CreateDirectory(mergeIdPath); 205 Directory.CreateDirectory(mergeIdPath);
203 206
204 using (var extractCab = new WixExtractCab()) 207 using (var extractCab = new WixExtractCab())
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
index 47b58058..a3d3ecf7 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
index 9bbb4763..70ba971f 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GetFileFacadesCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
@@ -8,57 +8,50 @@ namespace WixToolset.Core.WindowsInstaller.Databases
8 using System.Linq; 8 using System.Linq;
9 using WixToolset.Core.Bind; 9 using WixToolset.Core.Bind;
10 using WixToolset.Data; 10 using WixToolset.Data;
11 using WixToolset.Data.Rows; 11 using WixToolset.Data.Tuples;
12 12
13 internal class GetFileFacadesCommand 13 internal class GetFileFacadesCommand
14 { 14 {
15 public Table FileTable { private get; set; } 15 public GetFileFacadesCommand(IntermediateSection section)
16 16 {
17 public Table WixFileTable { private get; set; } 17 this.Section = section;
18 18 }
19 public Table WixDeltaPatchFileTable { private get; set; }
20 19
21 public Table WixDeltaPatchSymbolPathsTable { private get; set; } 20 private IntermediateSection Section { get; }
22 21
23 public List<FileFacade> FileFacades { get; private set; } 22 public List<FileFacade> FileFacades { get; private set; }
24 23
25 public void Execute() 24 public void Execute()
26 { 25 {
27 throw new NotImplementedException(); 26 var facades = new List<FileFacade>();
28#if TODO
29 List<FileFacade> facades = new List<FileFacade>(this.FileTable.Rows.Count);
30 27
31 RowDictionary<WixFileRow> wixFiles = new RowDictionary<WixFileRow>(this.WixFileTable); 28 var wixFiles = this.Section.Tuples.OfType<WixFileTuple>().ToDictionary(t => t.File_);
32 RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles = new RowDictionary<WixDeltaPatchFileRow>(this.WixDeltaPatchFileTable); 29 var deltaPatchFiles = this.Section.Tuples.OfType<WixDeltaPatchFileTuple>().ToDictionary(t => t.File_);
33 30
34 foreach (FileRow file in this.FileTable.Rows) 31 foreach (var file in this.Section.Tuples.OfType<FileTuple>())
35 { 32 {
36 WixDeltaPatchFileRow deltaPatchFile = null; 33 var wixFile = wixFiles[file.File];
37 34
38 deltaPatchFiles.TryGetValue(file.File, out deltaPatchFile); 35 deltaPatchFiles.TryGetValue(file.File, out var deltaPatchFile);
39 36
40 facades.Add(new FileFacade(file, wixFiles[file.File], deltaPatchFile)); 37 facades.Add(new FileFacade(file, wixFile, deltaPatchFile));
41 } 38 }
42 39
43 if (null != this.WixDeltaPatchSymbolPathsTable) 40 this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
44 {
45 this.ResolveDeltaPatchSymbolPaths(deltaPatchFiles, facades);
46 }
47 41
48 this.FileFacades = facades; 42 this.FileFacades = facades;
49#endif
50 } 43 }
51 44
52 /// <summary> 45 /// <summary>
53 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows. 46 /// Merge data from the WixPatchSymbolPaths rows into the WixDeltaPatchFile rows.
54 /// </summary> 47 /// </summary>
55 public RowDictionary<WixDeltaPatchFileRow> ResolveDeltaPatchSymbolPaths(RowDictionary<WixDeltaPatchFileRow> deltaPatchFiles, IEnumerable<FileFacade> facades) 48 public void ResolveDeltaPatchSymbolPaths(Dictionary<string, WixDeltaPatchFileTuple> deltaPatchFiles, IEnumerable<FileFacade> facades)
56 { 49 {
57 ILookup<string, FileFacade> filesByComponent = null; 50 ILookup<string, FileFacade> filesByComponent = null;
58 ILookup<string, FileFacade> filesByDirectory = null; 51 ILookup<string, FileFacade> filesByDirectory = null;
59 ILookup<string, FileFacade> filesByDiskId = null; 52 ILookup<string, FileFacade> filesByDiskId = null;
60 53
61 foreach (WixDeltaPatchSymbolPathsRow row in this.WixDeltaPatchSymbolPathsTable.RowsAs<WixDeltaPatchSymbolPathsRow>().OrderBy(r => r.Type)) 54 foreach (var row in this.Section.Tuples.OfType<WixDeltaPatchSymbolPathsTuple>().OrderBy(r => r.Type))
62 { 55 {
63 switch (row.Type) 56 switch (row.Type)
64 { 57 {
@@ -72,7 +65,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
72 filesByComponent = facades.ToLookup(f => f.File.Component_); 65 filesByComponent = facades.ToLookup(f => f.File.Component_);
73 } 66 }
74 67
75 foreach (FileFacade facade in filesByComponent[row.Id]) 68 foreach (var facade in filesByComponent[row.Id])
76 { 69 {
77 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); 70 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
78 } 71 }
@@ -84,7 +77,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
84 filesByDirectory = facades.ToLookup(f => f.WixFile.Directory_); 77 filesByDirectory = facades.ToLookup(f => f.WixFile.Directory_);
85 } 78 }
86 79
87 foreach (FileFacade facade in filesByDirectory[row.Id]) 80 foreach (var facade in filesByDirectory[row.Id])
88 { 81 {
89 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); 82 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
90 } 83 }
@@ -96,14 +89,14 @@ namespace WixToolset.Core.WindowsInstaller.Databases
96 filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture)); 89 filesByDiskId = facades.ToLookup(f => f.WixFile.DiskId.ToString(CultureInfo.InvariantCulture));
97 } 90 }
98 91
99 foreach (FileFacade facade in filesByDiskId[row.Id]) 92 foreach (var facade in filesByDiskId[row.Id])
100 { 93 {
101 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]); 94 this.MergeSymbolPaths(row, deltaPatchFiles[facade.File.File]);
102 } 95 }
103 break; 96 break;
104 97
105 case SymbolPathType.Product: 98 case SymbolPathType.Product:
106 foreach (WixDeltaPatchFileRow fileRow in deltaPatchFiles.Values) 99 foreach (var fileRow in deltaPatchFiles.Values)
107 { 100 {
108 this.MergeSymbolPaths(row, fileRow); 101 this.MergeSymbolPaths(row, fileRow);
109 } 102 }
@@ -114,8 +107,6 @@ namespace WixToolset.Core.WindowsInstaller.Databases
114 break; 107 break;
115 } 108 }
116 } 109 }
117
118 return deltaPatchFiles;
119 } 110 }
120 111
121 /// <summary> 112 /// <summary>
@@ -124,17 +115,18 @@ namespace WixToolset.Core.WindowsInstaller.Databases
124 /// <param name="row">Row from the WixPatchSymbolsPaths table.</param> 115 /// <param name="row">Row from the WixPatchSymbolsPaths table.</param>
125 /// <param name="file">FileRow into which to set symbol information.</param> 116 /// <param name="file">FileRow into which to set symbol information.</param>
126 /// <comment>This includes PreviousData as well.</comment> 117 /// <comment>This includes PreviousData as well.</comment>
127 private void MergeSymbolPaths(WixDeltaPatchSymbolPathsRow row, WixDeltaPatchFileRow file) 118 private void MergeSymbolPaths(WixDeltaPatchSymbolPathsTuple row, WixDeltaPatchFileTuple file)
128 { 119 {
129 if (null == file.Symbols) 120 if (file.SymbolPaths is null)
130 { 121 {
131 file.Symbols = row.SymbolPaths; 122 file.SymbolPaths = row.SymbolPaths;
132 } 123 }
133 else 124 else
134 { 125 {
135 file.Symbols = String.Concat(file.Symbols, ";", row.SymbolPaths); 126 file.SymbolPaths = String.Concat(file.SymbolPaths, ";", row.SymbolPaths);
136 } 127 }
137 128
129#if REVISIT_FOR_PATCHING
138 Field field = row.Fields[2]; 130 Field field = row.Fields[2];
139 if (null != field.PreviousData) 131 if (null != field.PreviousData)
140 { 132 {
@@ -147,6 +139,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
147 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData); 139 file.PreviousSymbols = String.Concat(file.PreviousSymbols, ";", field.PreviousData);
148 } 140 }
149 } 141 }
142#endif
150 } 143 }
151 } 144 }
152} 145}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
index f1605eca..dcf67c05 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
@@ -1,20 +1,13 @@
1// Copyright (c) .NET 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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel;
9 using System.Diagnostics;
10 using System.Globalization; 7 using System.Globalization;
11 using System.IO; 8 using System.IO;
12 using System.Linq;
13 using System.Runtime.InteropServices; 9 using System.Runtime.InteropServices;
14 using System.Text; 10 using System.Text;
15 using System.Xml;
16 using System.Xml.XPath;
17 using WixToolset.Clr.Interop;
18 using WixToolset.Data; 11 using WixToolset.Data;
19 using WixToolset.Data.Rows; 12 using WixToolset.Data.Rows;
20 using WixToolset.MergeMod; 13 using WixToolset.MergeMod;
@@ -35,12 +28,10 @@ namespace WixToolset.Core.WindowsInstaller.Databases
35 28
36 public IEnumerable<string> SuppressedTableNames { private get; set; } 29 public IEnumerable<string> SuppressedTableNames { private get; set; }
37 30
38 public string TempFilesLocation { private get; set; } 31 public string IntermediateFolder { private get; set; }
39 32
40 public void Execute() 33 public void Execute()
41 { 34 {
42 Debug.Assert(OutputType.Product == this.Output.Type);
43
44 Table wixMergeTable = this.Output.Tables["WixMerge"]; 35 Table wixMergeTable = this.Output.Tables["WixMerge"];
45 Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"]; 36 Table wixFeatureModulesTable = this.Output.Tables["WixFeatureModules"];
46 37
@@ -59,7 +50,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
59 { 50 {
60 merge = MsmInterop.GetMsmMerge(); 51 merge = MsmInterop.GetMsmMerge();
61 52
62 logPath = Path.Combine(this.TempFilesLocation, "merge.log"); 53 logPath = Path.Combine(this.IntermediateFolder, "merge.log");
63 merge.OpenLog(logPath); 54 merge.OpenLog(logPath);
64 logOpen = true; 55 logOpen = true;
65 56
@@ -79,7 +70,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
79 { 70 {
80 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); 71 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
81 } 72 }
82 catch (System.FormatException) 73 catch (FormatException)
83 { 74 {
84 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); 75 Messaging.Instance.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language));
85 continue; 76 continue;
@@ -284,7 +275,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
284 Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); 275 Messaging.Instance.OnMessage(WixVerboses.ResequencingMergeModuleFiles());
285 using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) 276 using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?"))
286 { 277 {
287 foreach (FileFacade file in this.FileFacades) 278 foreach (var file in this.FileFacades)
288 { 279 {
289 if (!file.FromModule) 280 if (!file.FromModule)
290 { 281 {
@@ -307,32 +298,29 @@ namespace WixToolset.Core.WindowsInstaller.Databases
307 //recordUpdate.SetInteger(1, file.File.Sequence); 298 //recordUpdate.SetInteger(1, file.File.Sequence);
308 throw new NotImplementedException(); 299 throw new NotImplementedException();
309 300
310 // update the file attributes to match the compression specified 301 // Update the file attributes to match the compression specified
311 // on the Merge element or on the Package element 302 // on the Merge element or on the Package element.
312 int attributes = 0; 303 var attributes = 0;
313 304
314 // get the current value if its not null 305 // Get the current value if its not null.
315 if (!recordUpdate.IsNull(2)) 306 if (!recordUpdate.IsNull(2))
316 { 307 {
317 attributes = recordUpdate.GetInteger(2); 308 attributes = recordUpdate.GetInteger(2);
318 } 309 }
319 310
320 // not specified
321 if (!file.File.Compressed.HasValue) 311 if (!file.File.Compressed.HasValue)
322 { 312 {
323 // clear any compression bits 313 // Clear all compression bits.
324 attributes &= ~MsiInterop.MsidbFileAttributesCompressed; 314 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
325 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; 315 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
326 } 316 }
327 else if (file.File.Compressed.Value) 317 else if (file.File.Compressed.Value)
328 { 318 {
329 // these are mutually exclusive
330 attributes |= MsiInterop.MsidbFileAttributesCompressed; 319 attributes |= MsiInterop.MsidbFileAttributesCompressed;
331 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; 320 attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed;
332 } 321 }
333 else if (!file.File.Compressed.Value) 322 else if (!file.File.Compressed.Value)
334 { 323 {
335 // these are mutually exclusive
336 attributes |= MsiInterop.MsidbFileAttributesNoncompressed; 324 attributes |= MsiInterop.MsidbFileAttributesNoncompressed;
337 attributes &= ~MsiInterop.MsidbFileAttributesCompressed; 325 attributes &= ~MsiInterop.MsidbFileAttributesCompressed;
338 } 326 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
index b3c09b9e..d71724d1 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ProcessUncompressedFilesCommand.cs
@@ -1,39 +1,42 @@
1// Copyright (c) .NET 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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections;
7 using System.Collections.Generic; 6 using System.Collections.Generic;
8 using System.IO; 7 using System.IO;
9 using WixToolset.Data; 8 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Msi; 9 using WixToolset.Msi;
12 using WixToolset.Core.Native; 10 using WixToolset.Core.Native;
13 using WixToolset.Bind; 11 using WixToolset.Bind;
14 using WixToolset.Core.Bind; 12 using WixToolset.Core.Bind;
15 using WixToolset.Data.Bind; 13 using WixToolset.Data.Bind;
14 using WixToolset.Data.Tuples;
15 using System.Linq;
16 16
17 /// <summary> 17 /// <summary>
18 /// Defines the file transfers necessary to layout the uncompressed files. 18 /// Defines the file transfers necessary to layout the uncompressed files.
19 /// </summary> 19 /// </summary>
20 internal class ProcessUncompressedFilesCommand 20 internal class ProcessUncompressedFilesCommand
21 { 21 {
22 public ProcessUncompressedFilesCommand(IntermediateSection section)
23 {
24 this.Section = section;
25 }
26
27 private IntermediateSection Section { get; }
28
22 public string DatabasePath { private get; set; } 29 public string DatabasePath { private get; set; }
23 30
24 public IEnumerable<FileFacade> FileFacades { private get; set; } 31 public IEnumerable<FileFacade> FileFacades { private get; set; }
25 32
26 public RowDictionary<MediaRow> MediaRows { private get; set; }
27
28 public string LayoutDirectory { private get; set; } 33 public string LayoutDirectory { private get; set; }
29 34
30 public bool Compressed { private get; set; } 35 public bool Compressed { private get; set; }
31 36
32 public bool LongNamesInImage { private get; set; } 37 public bool LongNamesInImage { private get; set; }
33 38
34 public Func<MediaRow, string, string, string> ResolveMedia { private get; set; } 39 public Func<MediaTuple, string, string, string> ResolveMedia { private get; set; }
35
36 public Table WixMediaTable { private get; set; }
37 40
38 public IEnumerable<FileTransfer> FileTransfers { get; private set; } 41 public IEnumerable<FileTransfer> FileTransfers { get; private set; }
39 42
@@ -41,9 +44,11 @@ namespace WixToolset.Core.WindowsInstaller.Databases
41 { 44 {
42 List<FileTransfer> fileTransfers = new List<FileTransfer>(); 45 List<FileTransfer> fileTransfers = new List<FileTransfer>();
43 46
44 Hashtable directories = new Hashtable(); 47 var directories = new Dictionary<string, ResolvedDirectory>();
45 48
46 RowDictionary<WixMediaRow> wixMediaRows = new RowDictionary<WixMediaRow>(this.WixMediaTable); 49 var mediaRows = this.Section.Tuples.OfType<MediaTuple>().ToDictionary(t => t.DiskId);
50
51 var wixMediaRows = this.Section.Tuples.OfType<WixMediaTuple>().ToDictionary(t => t.DiskId_);
47 52
48 using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly)) 53 using (Database db = new Database(this.DatabasePath, OpenDatabase.ReadOnly))
49 { 54 {
@@ -72,18 +77,16 @@ namespace WixToolset.Core.WindowsInstaller.Databases
72 // for each file in the array of uncompressed files 77 // for each file in the array of uncompressed files
73 foreach (FileFacade facade in this.FileFacades) 78 foreach (FileFacade facade in this.FileFacades)
74 { 79 {
75 MediaRow mediaRow = this.MediaRows.Get(facade.WixFile.DiskId); 80 var mediaTuple = mediaRows[facade.WixFile.DiskId];
76 string relativeFileLayoutPath = null; 81 string relativeFileLayoutPath = null;
77
78 WixMediaRow wixMediaRow = null;
79 string mediaLayoutFolder = null; 82 string mediaLayoutFolder = null;
80 83
81 if (wixMediaRows.TryGetValue(mediaRow.GetKey(), out wixMediaRow)) 84 if (wixMediaRows.TryGetValue(facade.WixFile.DiskId, out var wixMediaRow))
82 { 85 {
83 mediaLayoutFolder = wixMediaRow.Layout; 86 mediaLayoutFolder = wixMediaRow.Layout;
84 } 87 }
85 88
86 string mediaLayoutDirectory = this.ResolveMedia(mediaRow, mediaLayoutFolder, this.LayoutDirectory); 89 var mediaLayoutDirectory = this.ResolveMedia(mediaTuple, mediaLayoutFolder, this.LayoutDirectory);
87 90
88 // setup up the query record and find the appropriate file in the 91 // setup up the query record and find the appropriate file in the
89 // previously executed file view 92 // previously executed file view
@@ -102,8 +105,7 @@ namespace WixToolset.Core.WindowsInstaller.Databases
102 105
103 // finally put together the base media layout path and the relative file layout path 106 // finally put together the base media layout path and the relative file layout path
104 string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); 107 string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath);
105 FileTransfer transfer; 108 if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out var transfer))
106 if (FileTransfer.TryCreate(facade.WixFile.Source, fileLayoutPath, false, "File", facade.File.SourceLineNumbers, out transfer))
107 { 109 {
108 fileTransfers.Add(transfer); 110 fileTransfers.Add(transfer);
109 } 111 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
new file mode 100644
index 00000000..cf9c0332
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
@@ -0,0 +1,671 @@
1// Copyright (c) .NET Foundation 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.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Core.Native;
10 using WixToolset.Data;
11 using WixToolset.Data.Tuples;
12
13 internal class SequenceActionsCommand
14 {
15 public SequenceActionsCommand(IntermediateSection section)
16 {
17 this.Section = section;
18
19 this.RelativeActionsForActions = new Dictionary<string, RelativeActions>();
20
21 this.StandardActionsById = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id);
22 }
23
24 private IntermediateSection Section { get; }
25
26 private Dictionary<string, RelativeActions> RelativeActionsForActions { get; }
27
28 private Dictionary<string, WixActionTuple> StandardActionsById { get; }
29
30 public Messaging Messaging { private get; set; }
31
32 public void Execute()
33 {
34 var actions = this.Section.Tuples.OfType<WixActionTuple>().ToList();
35 var suppressActions = this.Section.Tuples.OfType<WixSuppressActionTuple>().ToList();
36
37 this.SequenceActions(actions, suppressActions);
38 }
39
40 /// <summary>
41 /// Set sequence numbers for all the actions and create rows in the output object.
42 /// </summary>
43 /// <param name="actionRows">Collection of actions to schedule.</param>
44 /// <param name="suppressActionRows">Collection of actions to suppress.</param>
45 private void SequenceActions(List<WixActionTuple> actionRows, List<WixSuppressActionTuple> suppressActionRows)
46 {
47 var overridableActionRows = new Dictionary<string, WixActionTuple>();
48 var requiredActionRows = new Dictionary<string, WixActionTuple>();
49
50 // Get the standard actions required based on tuples in the section.
51 var requiredActionIds = this.GetRequiredActionIds();
52
53 foreach (var actionId in requiredActionIds)
54 {
55 var standardAction = this.StandardActionsById[actionId];
56
57 overridableActionRows.Add(standardAction.Id.Id, standardAction);
58 }
59
60 // Index all the action rows and look for collisions.
61 foreach (var actionRow in this.Section.Tuples.OfType<WixActionTuple>())
62 {
63 if (actionRow.Overridable) // overridable action
64 {
65 if (overridableActionRows.TryGetValue(actionRow.Id.Id, out var collidingActionRow))
66 {
67 this.Messaging.OnMessage(WixErrors.OverridableActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
68 if (null != collidingActionRow.SourceLineNumbers)
69 {
70 this.Messaging.OnMessage(WixErrors.OverridableActionCollision2(collidingActionRow.SourceLineNumbers));
71 }
72 }
73 else
74 {
75 overridableActionRows.Add(actionRow.Id.Id, actionRow);
76 }
77 }
78 else // unsequenced or sequenced action.
79 {
80 // Unsequenced action (allowed for certain standard actions).
81 if (null == actionRow.Before && null == actionRow.After && 0 == actionRow.Sequence)
82 {
83 if (this.StandardActionsById.TryGetValue(actionRow.Id.Id, out var standardAction))
84 {
85 // Populate the sequence from the standard action
86 actionRow.Sequence = standardAction.Sequence;
87 }
88 else // not a supported unscheduled action.
89 {
90 throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet);
91 }
92 }
93
94 if (overridableActionRows.TryGetValue(actionRow.Id.Id, out var collidingActionRow))
95 {
96 this.Messaging.OnMessage(WixErrors.ActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
97 if (null != collidingActionRow.SourceLineNumbers)
98 {
99 this.Messaging.OnMessage(WixErrors.ActionCollision2(collidingActionRow.SourceLineNumbers));
100 }
101 }
102 else
103 {
104 requiredActionRows.Add(actionRow.Id.Id, actionRow);
105 }
106 }
107 }
108
109 // Add the overridable action rows that are not overridden to the required action rows.
110 foreach (var actionRow in overridableActionRows.Values)
111 {
112 if (!requiredActionRows.ContainsKey(actionRow.Id.Id))
113 {
114 requiredActionRows.Add(actionRow.Id.Id, actionRow);
115 }
116 }
117
118 // Suppress the required actions that are overridable.
119 foreach (var suppressActionRow in suppressActionRows)
120 {
121 var key = suppressActionRow.Id.Id;
122
123 // If there is an overridable row to suppress; suppress it. There is no warning if there
124 // is no action to suppress because the action may be suppressed from a merge module in
125 // the binder.
126 if (requiredActionRows.TryGetValue(key, out var requiredActionRow))
127 {
128 if (requiredActionRow.Overridable)
129 {
130 this.Messaging.OnMessage(WixWarnings.SuppressAction(suppressActionRow.SourceLineNumbers, suppressActionRow.Action, suppressActionRow.SequenceTable.ToString()));
131 if (null != requiredActionRow.SourceLineNumbers)
132 {
133 this.Messaging.OnMessage(WixWarnings.SuppressAction2(requiredActionRow.SourceLineNumbers));
134 }
135
136 requiredActionRows.Remove(key);
137 }
138 else // suppressing a non-overridable action row
139 {
140 this.Messaging.OnMessage(WixErrors.SuppressNonoverridableAction(suppressActionRow.SourceLineNumbers, suppressActionRow.SequenceTable.ToString(), suppressActionRow.Action));
141 if (null != requiredActionRow.SourceLineNumbers)
142 {
143 this.Messaging.OnMessage(WixErrors.SuppressNonoverridableAction2(requiredActionRow.SourceLineNumbers));
144 }
145 }
146 }
147 }
148
149 // Build up dependency trees of the relatively scheduled actions.
150 // Use ToList() to create a copy of the required action rows so that new tuples can
151 // be added while enumerating.
152 foreach (var actionRow in requiredActionRows.Values.ToList())
153 {
154 if (0 == actionRow.Sequence)
155 {
156 // check for standard actions that don't have a sequence number in a merge module
157 if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionRow.Action))
158 {
159 this.Messaging.OnMessage(WixErrors.StandardActionRelativelyScheduledInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
160 }
161
162 this.SequenceActionRow(actionRow, requiredActionRows);
163 }
164 else if (SectionType.Module == this.Section.Type && 0 < actionRow.Sequence && !WindowsInstallerStandard.IsStandardAction(actionRow.Action)) // check for custom actions and dialogs that have a sequence number
165 {
166 this.Messaging.OnMessage(WixErrors.CustomActionSequencedInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
167 }
168 }
169
170 // Look for standard actions with sequence restrictions that aren't necessarily scheduled based
171 // on the presence of a particular table.
172 if (requiredActionRows.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionRows.ContainsKey("InstallExecuteSequence/InstallFiles"))
173 {
174 var standardAction = this.StandardActionsById["InstallExecuteSequence/InstallFiles"];
175 requiredActionRows.Add(standardAction.Id.Id, standardAction);
176 }
177
178 // Schedule actions.
179 List<WixActionTuple> scheduledActionRows;
180 if (SectionType.Module == this.Section.Type)
181 {
182 scheduledActionRows = requiredActionRows.Values.ToList();
183 }
184 else
185 {
186 scheduledActionRows = ScheduleActions(requiredActionRows);
187 }
188
189 // Remove all existing WixActionTuples from the section then add the
190 // scheduled actions back to the section. Note: we add the indices in
191 // reverse order to make it easy to remove them from the list later.
192 var removeIndices = new List<int>();
193 for (var i = this.Section.Tuples.Count - 1; i >= 0; --i)
194 {
195 var tuple = this.Section.Tuples[i];
196 if (tuple.Definition.Type == TupleDefinitionType.WixAction)
197 {
198 removeIndices.Add(i);
199 }
200 }
201
202 foreach (var removeIndex in removeIndices)
203 {
204 this.Section.Tuples.RemoveAt(removeIndex);
205 }
206
207 foreach (var action in scheduledActionRows)
208 {
209 this.Section.Tuples.Add(action);
210 }
211 }
212
213 private List<WixActionTuple> ScheduleActions(Dictionary<string, WixActionTuple> requiredActionRows)
214 {
215 var scheduledActionRows = new List<WixActionTuple>();
216
217 // Process each sequence table individually.
218 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
219 {
220 // Create a collection of just the action rows in this sequence
221 var sequenceActionRows = requiredActionRows.Values.Where(a => a.SequenceTable == sequenceTable).ToList();
222
223 // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers).
224 var absoluteActionRows = new List<WixActionTuple>();
225 foreach (var actionRow in sequenceActionRows)
226 {
227 if (0 != actionRow.Sequence)
228 {
229 // Look for sequence number collisions
230 foreach (var sequenceScheduledActionRow in absoluteActionRows)
231 {
232 if (sequenceScheduledActionRow.Sequence == actionRow.Sequence)
233 {
234 this.Messaging.OnMessage(WixWarnings.ActionSequenceCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, sequenceScheduledActionRow.Action, actionRow.Sequence));
235 if (null != sequenceScheduledActionRow.SourceLineNumbers)
236 {
237 this.Messaging.OnMessage(WixWarnings.ActionSequenceCollision2(sequenceScheduledActionRow.SourceLineNumbers));
238 }
239 }
240 }
241
242 absoluteActionRows.Add(actionRow);
243 }
244 }
245
246 absoluteActionRows.Sort((x, y) => x.Sequence.CompareTo(y.Sequence));
247
248 // Schedule the relatively scheduled actions (by resolving the dependency trees).
249 var previousUsedSequence = 0;
250 var relativeActionRows = new List<WixActionTuple>();
251 for (int j = 0; j < absoluteActionRows.Count; j++)
252 {
253 var absoluteActionRow = absoluteActionRows[j];
254
255 // Get all the relatively scheduled action rows occuring before and after this absolutely scheduled action row.
256 var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionRow);
257
258 // Check for relatively scheduled actions occuring before/after a special action
259 // (those actions with a negative sequence number).
260 if (absoluteActionRow.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any()))
261 {
262 // Create errors for all the before actions.
263 foreach (var actionRow in relativeActions.PreviousActions)
264 {
265 this.Messaging.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action));
266 }
267
268 // Create errors for all the after actions.
269 foreach (var actionRow in relativeActions.NextActions)
270 {
271 this.Messaging.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action));
272 }
273
274 // If there is source line information for the absolutely scheduled action display it
275 if (absoluteActionRow.SourceLineNumbers != null)
276 {
277 this.Messaging.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction2(absoluteActionRow.SourceLineNumbers));
278 }
279
280 continue;
281 }
282
283 // Schedule the action rows before this one.
284 var unusedSequence = absoluteActionRow.Sequence - 1;
285 for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--)
286 {
287 var relativeActionRow = relativeActions.PreviousActions[i];
288
289 // look for collisions
290 if (unusedSequence == previousUsedSequence)
291 {
292 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action));
293 if (absoluteActionRow.SourceLineNumbers != null)
294 {
295 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers));
296 }
297
298 unusedSequence++;
299 }
300
301 relativeActionRow.Sequence = unusedSequence;
302 relativeActionRows.Add(relativeActionRow);
303
304 unusedSequence--;
305 }
306
307 // Determine the next used action sequence number.
308 var nextUsedSequence = Int16.MaxValue + 1;
309 if (absoluteActionRows.Count > j + 1)
310 {
311 nextUsedSequence = absoluteActionRows[j + 1].Sequence;
312 }
313
314 // Schedule the action rows after this one.
315 unusedSequence = absoluteActionRow.Sequence + 1;
316 for (var i = 0; i < relativeActions.NextActions.Count; i++)
317 {
318 var relativeActionRow = relativeActions.NextActions[i];
319
320 if (unusedSequence == nextUsedSequence)
321 {
322 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action));
323 if (absoluteActionRow.SourceLineNumbers != null)
324 {
325 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers));
326 }
327
328 unusedSequence--;
329 }
330
331 relativeActionRow.Sequence = unusedSequence;
332 relativeActionRows.Add(relativeActionRow);
333
334 unusedSequence++;
335 }
336
337 // keep track of this sequence number as the previous used sequence number for the next iteration
338 previousUsedSequence = absoluteActionRow.Sequence;
339 }
340
341 // add the absolutely and relatively scheduled actions to the list of scheduled actions
342 scheduledActionRows.AddRange(absoluteActionRows);
343 scheduledActionRows.AddRange(relativeActionRows);
344 }
345
346 return scheduledActionRows;
347 }
348
349 private IEnumerable<string> GetRequiredActionIds()
350 {
351 var set = new HashSet<string>();
352
353 // gather the required actions for the output type
354 if (SectionType.Product == this.Section.Type)
355 {
356 // AdminExecuteSequence table
357 set.Add("AdminExecuteSequence/CostFinalize");
358 set.Add("AdminExecuteSequence/CostInitialize");
359 set.Add("AdminExecuteSequence/FileCost");
360 set.Add("AdminExecuteSequence/InstallAdminPackage");
361 set.Add("AdminExecuteSequence/InstallFiles");
362 set.Add("AdminExecuteSequence/InstallFinalize");
363 set.Add("AdminExecuteSequence/InstallInitialize");
364 set.Add("AdminExecuteSequence/InstallValidate");
365
366 // AdminUISequence table
367 set.Add("AdminUISequence/CostFinalize");
368 set.Add("AdminUISequence/CostInitialize");
369 set.Add("AdminUISequence/ExecuteAction");
370 set.Add("AdminUISequence/FileCost");
371
372 // AdvtExecuteSequence table
373 set.Add("AdvtExecuteSequence/CostFinalize");
374 set.Add("AdvtExecuteSequence/CostInitialize");
375 set.Add("AdvtExecuteSequence/InstallFinalize");
376 set.Add("AdvtExecuteSequence/InstallValidate");
377 set.Add("AdvtExecuteSequence/PublishFeatures");
378 set.Add("AdvtExecuteSequence/PublishProduct");
379
380 // InstallExecuteSequence table
381 set.Add("InstallExecuteSequence/CostFinalize");
382 set.Add("InstallExecuteSequence/CostInitialize");
383 set.Add("InstallExecuteSequence/FileCost");
384 set.Add("InstallExecuteSequence/InstallFinalize");
385 set.Add("InstallExecuteSequence/InstallInitialize");
386 set.Add("InstallExecuteSequence/InstallValidate");
387 set.Add("InstallExecuteSequence/ProcessComponents");
388 set.Add("InstallExecuteSequence/PublishFeatures");
389 set.Add("InstallExecuteSequence/PublishProduct");
390 set.Add("InstallExecuteSequence/RegisterProduct");
391 set.Add("InstallExecuteSequence/RegisterUser");
392 set.Add("InstallExecuteSequence/UnpublishFeatures");
393 set.Add("InstallExecuteSequence/ValidateProductID");
394
395 // InstallUISequence table
396 set.Add("InstallUISequence/CostFinalize");
397 set.Add("InstallUISequence/CostInitialize");
398 set.Add("InstallUISequence/ExecuteAction");
399 set.Add("InstallUISequence/FileCost");
400 set.Add("InstallUISequence/ValidateProductID");
401 }
402
403 // Gather the required actions for each tuple type.
404 foreach (var tupleType in this.Section.Tuples.Select(t => t.Definition.Type).Distinct())
405 {
406 switch (tupleType)
407 {
408 case TupleDefinitionType.AppSearch:
409 set.Add("InstallExecuteSequence/AppSearch");
410 set.Add("InstallUISequence/AppSearch");
411 break;
412 case TupleDefinitionType.BindImage:
413 set.Add("InstallExecuteSequence/BindImage");
414 break;
415 case TupleDefinitionType.CCPSearch:
416 set.Add("InstallExecuteSequence/AppSearch");
417 set.Add("InstallExecuteSequence/CCPSearch");
418 set.Add("InstallExecuteSequence/RMCCPSearch");
419 set.Add("InstallUISequence/AppSearch");
420 set.Add("InstallUISequence/CCPSearch");
421 set.Add("InstallUISequence/RMCCPSearch");
422 break;
423 case TupleDefinitionType.Class:
424 set.Add("AdvtExecuteSequence/RegisterClassInfo");
425 set.Add("InstallExecuteSequence/RegisterClassInfo");
426 set.Add("InstallExecuteSequence/UnregisterClassInfo");
427 break;
428 case TupleDefinitionType.Complus:
429 set.Add("InstallExecuteSequence/RegisterComPlus");
430 set.Add("InstallExecuteSequence/UnregisterComPlus");
431 break;
432 case TupleDefinitionType.CreateFolder:
433 set.Add("InstallExecuteSequence/CreateFolders");
434 set.Add("InstallExecuteSequence/RemoveFolders");
435 break;
436 case TupleDefinitionType.DuplicateFile:
437 set.Add("InstallExecuteSequence/DuplicateFiles");
438 set.Add("InstallExecuteSequence/RemoveDuplicateFiles");
439 break;
440 case TupleDefinitionType.Environment:
441 set.Add("InstallExecuteSequence/WriteEnvironmentStrings");
442 set.Add("InstallExecuteSequence/RemoveEnvironmentStrings");
443 break;
444 case TupleDefinitionType.Extension:
445 set.Add("AdvtExecuteSequence/RegisterExtensionInfo");
446 set.Add("InstallExecuteSequence/RegisterExtensionInfo");
447 set.Add("InstallExecuteSequence/UnregisterExtensionInfo");
448 break;
449 case TupleDefinitionType.File:
450 set.Add("InstallExecuteSequence/InstallFiles");
451 set.Add("InstallExecuteSequence/RemoveFiles");
452 break;
453 case TupleDefinitionType.Font:
454 set.Add("InstallExecuteSequence/RegisterFonts");
455 set.Add("InstallExecuteSequence/UnregisterFonts");
456 break;
457 case TupleDefinitionType.IniFile:
458 case TupleDefinitionType.RemoveIniFile:
459 set.Add("InstallExecuteSequence/WriteIniValues");
460 set.Add("InstallExecuteSequence/RemoveIniValues");
461 break;
462 case TupleDefinitionType.IsolatedComponent:
463 set.Add("InstallExecuteSequence/IsolateComponents");
464 break;
465 case TupleDefinitionType.LaunchCondition:
466 set.Add("InstallExecuteSequence/LaunchConditions");
467 set.Add("InstallUISequence/LaunchConditions");
468 break;
469 case TupleDefinitionType.MIME:
470 set.Add("AdvtExecuteSequence/RegisterMIMEInfo");
471 set.Add("InstallExecuteSequence/RegisterMIMEInfo");
472 set.Add("InstallExecuteSequence/UnregisterMIMEInfo");
473 break;
474 case TupleDefinitionType.MoveFile:
475 set.Add("InstallExecuteSequence/MoveFiles");
476 break;
477 case TupleDefinitionType.MsiAssembly:
478 set.Add("AdvtExecuteSequence/MsiPublishAssemblies");
479 set.Add("InstallExecuteSequence/MsiPublishAssemblies");
480 set.Add("InstallExecuteSequence/MsiUnpublishAssemblies");
481 break;
482 case TupleDefinitionType.MsiServiceConfig:
483 case TupleDefinitionType.MsiServiceConfigFailureActions:
484 set.Add("InstallExecuteSequence/MsiConfigureServices");
485 break;
486 case TupleDefinitionType.ODBCDataSource:
487 case TupleDefinitionType.ODBCTranslator:
488 case TupleDefinitionType.ODBCDriver:
489 set.Add("InstallExecuteSequence/SetODBCFolders");
490 set.Add("InstallExecuteSequence/InstallODBC");
491 set.Add("InstallExecuteSequence/RemoveODBC");
492 break;
493 case TupleDefinitionType.ProgId:
494 set.Add("AdvtExecuteSequence/RegisterProgIdInfo");
495 set.Add("InstallExecuteSequence/RegisterProgIdInfo");
496 set.Add("InstallExecuteSequence/UnregisterProgIdInfo");
497 break;
498 case TupleDefinitionType.PublishComponent:
499 set.Add("AdvtExecuteSequence/PublishComponents");
500 set.Add("InstallExecuteSequence/PublishComponents");
501 set.Add("InstallExecuteSequence/UnpublishComponents");
502 break;
503 case TupleDefinitionType.Registry:
504 case TupleDefinitionType.RemoveRegistry:
505 set.Add("InstallExecuteSequence/WriteRegistryValues");
506 set.Add("InstallExecuteSequence/RemoveRegistryValues");
507 break;
508 case TupleDefinitionType.RemoveFile:
509 set.Add("InstallExecuteSequence/RemoveFiles");
510 break;
511 case TupleDefinitionType.SelfReg:
512 set.Add("InstallExecuteSequence/SelfRegModules");
513 set.Add("InstallExecuteSequence/SelfUnregModules");
514 break;
515 case TupleDefinitionType.ServiceControl:
516 set.Add("InstallExecuteSequence/StartServices");
517 set.Add("InstallExecuteSequence/StopServices");
518 set.Add("InstallExecuteSequence/DeleteServices");
519 break;
520 case TupleDefinitionType.ServiceInstall:
521 set.Add("InstallExecuteSequence/InstallServices");
522 break;
523 case TupleDefinitionType.Shortcut:
524 set.Add("AdvtExecuteSequence/CreateShortcuts");
525 set.Add("InstallExecuteSequence/CreateShortcuts");
526 set.Add("InstallExecuteSequence/RemoveShortcuts");
527 break;
528 case TupleDefinitionType.TypeLib:
529 set.Add("InstallExecuteSequence/RegisterTypeLibraries");
530 set.Add("InstallExecuteSequence/UnregisterTypeLibraries");
531 break;
532 case TupleDefinitionType.Upgrade:
533 set.Add("InstallExecuteSequence/FindRelatedProducts");
534 set.Add("InstallUISequence/FindRelatedProducts");
535
536 // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on
537 // at least one UpgradeVersion element.
538 if (this.Section.Tuples.OfType<UpgradeTuple>().Any(t => (t.Attributes & MsiInterop.MsidbUpgradeAttributesMigrateFeatures) == MsiInterop.MsidbUpgradeAttributesMigrateFeatures))
539 {
540 set.Add("InstallExecuteSequence/MigrateFeatureStates");
541 set.Add("InstallUISequence/MigrateFeatureStates");
542 }
543 break;
544 }
545 }
546
547 return set;
548 }
549
550 private IEnumerable<WixActionTuple> GetActions(SequenceTable sequence, string[] actionNames)
551 {
552 foreach (var action in WindowsInstallerStandard.StandardActions())
553 {
554 if (action.SequenceTable == sequence && actionNames.Contains(action.Action))
555 {
556 yield return action;
557 }
558 }
559 }
560
561 /// <summary>
562 /// Sequence an action before or after a standard action.
563 /// </summary>
564 /// <param name="actionRow">The action row to be sequenced.</param>
565 /// <param name="requiredActionRows">Collection of actions which must be included.</param>
566 private void SequenceActionRow(WixActionTuple actionRow, Dictionary<string, WixActionTuple> requiredActionRows)
567 {
568 var after = false;
569
570 if (actionRow.After != null)
571 {
572 after = true;
573 }
574 else if (actionRow.Before == null)
575 {
576 throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet);
577 }
578
579 var parentActionName = (after ? actionRow.After : actionRow.Before);
580 var parentActionKey = actionRow.SequenceTable.ToString() + "/" + parentActionName;
581
582 if (!requiredActionRows.TryGetValue(parentActionKey, out var parentActionRow))
583 {
584 // If the missing parent action is a standard action (with a suggested sequence number), add it.
585 if (this.StandardActionsById.TryGetValue(parentActionKey, out parentActionRow))
586 {
587 // Create a clone to avoid modifying the static copy of the object.
588 // TODO: consider this: parentActionRow = parentActionRow.Clone();
589
590 requiredActionRows.Add(parentActionRow.Id.Id, parentActionRow);
591 }
592 else
593 {
594 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_FoundActionRowWinNonExistentAction, (after ? "After" : "Before"), parentActionName));
595 }
596 }
597 else if (actionRow == parentActionRow || this.ContainsChildActionRow(actionRow, parentActionRow)) // cycle detected
598 {
599 throw new WixException(WixErrors.ActionCircularDependency(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, parentActionRow.Action));
600 }
601
602 // Add this action to the appropriate list of dependent action rows.
603 var relativeActions = this.GetRelativeActions(parentActionRow);
604 var relatedRows = (after ? relativeActions.NextActions : relativeActions.PreviousActions);
605 relatedRows.Add(actionRow);
606 }
607
608 private bool ContainsChildActionRow(WixActionTuple childTuple, WixActionTuple parentTuple)
609 {
610 var result = false;
611
612 if (this.RelativeActionsForActions.TryGetValue(childTuple.Id.Id, out var relativeActions))
613 {
614 result = relativeActions.NextActions.Any(a => a.SequenceTable == parentTuple.SequenceTable && a.Id.Id == parentTuple.Id.Id) ||
615 relativeActions.PreviousActions.Any(a => a.SequenceTable == parentTuple.SequenceTable && a.Id.Id == parentTuple.Id.Id);
616 }
617
618 return result;
619 }
620
621 private RelativeActions GetRelativeActions(WixActionTuple action)
622 {
623 if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions))
624 {
625 relativeActions = new RelativeActions();
626 this.RelativeActionsForActions.Add(action.Id.Id, relativeActions);
627 }
628
629 return relativeActions;
630 }
631
632 private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionTuple action)
633 {
634 var relativeActions = new RelativeActions();
635
636 if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives))
637 {
638 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions);
639
640 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions);
641 }
642
643 return relativeActions;
644 }
645
646 private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List<WixActionTuple> actions, List<WixActionTuple> visitedActions)
647 {
648 foreach (var action in actions.Where(a => a.SequenceTable == sequenceType))
649 {
650 if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives))
651 {
652 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions);
653 }
654
655 visitedActions.Add(action);
656
657 if (actionRelatives != null)
658 {
659 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions);
660 }
661 }
662 }
663
664 private class RelativeActions
665 {
666 public List<WixActionTuple> PreviousActions { get; } = new List<WixActionTuple>();
667
668 public List<WixActionTuple> NextActions { get; } = new List<WixActionTuple>();
669 }
670 }
671}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs
index 7da32206..9579e0f8 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.IO; 6 using System.IO;
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
index 20058597..030bc4cc 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
@@ -1,10 +1,9 @@
1// Copyright (c) .NET 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.Core.WindowsInstaller.Databases 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Collections.Specialized;
8 using System.ComponentModel; 7 using System.ComponentModel;
9 using System.Globalization; 8 using System.Globalization;
10 using System.IO; 9 using System.IO;
@@ -14,7 +13,6 @@ namespace WixToolset.Core.WindowsInstaller.Databases
14 using WixToolset.Clr.Interop; 13 using WixToolset.Clr.Interop;
15 using WixToolset.Core.Bind; 14 using WixToolset.Core.Bind;
16 using WixToolset.Data; 15 using WixToolset.Data;
17 using WixToolset.Data.Rows;
18 using WixToolset.Data.Tuples; 16 using WixToolset.Data.Tuples;
19 using WixToolset.Msi; 17 using WixToolset.Msi;
20 18
@@ -23,13 +21,16 @@ namespace WixToolset.Core.WindowsInstaller.Databases
23 /// </summary> 21 /// </summary>
24 internal class UpdateFileFacadesCommand 22 internal class UpdateFileFacadesCommand
25 { 23 {
26 public IEnumerable<FileFacade> FileFacades { private get; set; } 24 public UpdateFileFacadesCommand(IntermediateSection section)
25 {
26 this.Section = section;
27 }
27 28
28 public IEnumerable<FileFacade> UpdateFileFacades { private get; set; } 29 private IntermediateSection Section { get; }
29 30
30 public string ModularizationGuid { private get; set; } 31 public IEnumerable<FileFacade> FileFacades { private get; set; }
31 32
32 public Output Output { private get; set; } 33 public IEnumerable<FileFacade> UpdateFileFacades { private get; set; }
33 34
34 public bool OverwriteHash { private get; set; } 35 public bool OverwriteHash { private get; set; }
35 36
@@ -47,6 +48,12 @@ namespace WixToolset.Core.WindowsInstaller.Databases
47 48
48 private void UpdateFileFacade(FileFacade file) 49 private void UpdateFileFacade(FileFacade file)
49 { 50 {
51 var assemblyNameTuples = new Dictionary<string, MsiAssemblyNameTuple>();
52 foreach (var assemblyTuple in this.Section.Tuples.OfType<MsiAssemblyNameTuple>())
53 {
54 assemblyNameTuples.Add(assemblyTuple.Component_ + "/" + assemblyTuple.Name, assemblyTuple);
55 }
56
50 FileInfo fileInfo = null; 57 FileInfo fileInfo = null;
51 try 58 try
52 { 59 {
@@ -149,9 +156,8 @@ namespace WixToolset.Core.WindowsInstaller.Databases
149 156
150 if (null == file.Hash) 157 if (null == file.Hash)
151 { 158 {
152 //Table msiFileHashTable = this.Output.EnsureTable(this.TableDefinitions["MsiFileHash"]); 159 file.Hash = new MsiFileHashTuple(file.File.SourceLineNumbers, file.File.Id);
153 //file.Hash = msiFileHashTable.CreateRow(file.File.SourceLineNumbers); 160 this.Section.Tuples.Add(file.Hash);
154 throw new NotImplementedException();
155 } 161 }
156 162
157 file.Hash.File_ = file.File.File; 163 file.Hash.File_ = file.File.File;
@@ -198,13 +204,13 @@ namespace WixToolset.Core.WindowsInstaller.Databases
198 { 204 {
199 if (!String.IsNullOrEmpty(file.File.Version)) 205 if (!String.IsNullOrEmpty(file.File.Version))
200 { 206 {
201 string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)); 207 var key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", file.File.File);
202 this.VariableCache[key] = file.File.Version; 208 this.VariableCache[key] = file.File.Version;
203 } 209 }
204 210
205 if (!String.IsNullOrEmpty(file.File.Language)) 211 if (!String.IsNullOrEmpty(file.File.Language))
206 { 212 {
207 string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", Common.Demodularize(this.Output.Type, ModularizationGuid, file.File.File)); 213 var key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", file.File.File);
208 this.VariableCache[key] = file.File.Language; 214 this.VariableCache[key] = file.File.Language;
209 } 215 }
210 } 216 }
@@ -214,14 +220,13 @@ namespace WixToolset.Core.WindowsInstaller.Databases
214 if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType) 220 if (FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType)
215 { 221 {
216 bool targetNetfx1 = false; 222 bool targetNetfx1 = false;
217 StringDictionary assemblyNameValues = new StringDictionary(); 223 var assemblyNameValues = new Dictionary<string, string>();
218 224
219 ClrInterop.IReferenceIdentity referenceIdentity = null;
220 Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid; 225 Guid referenceIdentityGuid = ClrInterop.ReferenceIdentityGuid;
221 uint result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity); 226 var result = ClrInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out var referenceIdentity);
222 if (0 == result && null != referenceIdentity) 227 if (0 == result && null != referenceIdentity)
223 { 228 {
224 string imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion"); 229 var imageRuntimeVersion = referenceIdentity.GetAttribute(null, "ImageRuntimeVersion");
225 if (null != imageRuntimeVersion) 230 if (null != imageRuntimeVersion)
226 { 231 {
227 targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase); 232 targetNetfx1 = imageRuntimeVersion.StartsWith("v1", StringComparison.OrdinalIgnoreCase);
@@ -269,15 +274,14 @@ namespace WixToolset.Core.WindowsInstaller.Databases
269 return; 274 return;
270 } 275 }
271 276
272 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]); 277 if (assemblyNameValues.TryGetValue("name", out var value))
273 if (assemblyNameValues.ContainsKey("name"))
274 { 278 {
275 this.SetMsiAssemblyName(assemblyNameTable, file, "name", assemblyNameValues["name"]); 279 this.SetMsiAssemblyName(assemblyNameTuples, file, "name", value);
276 } 280 }
277 281
278 if (!String.IsNullOrEmpty(version)) 282 if (!String.IsNullOrEmpty(version))
279 { 283 {
280 this.SetMsiAssemblyName(assemblyNameTable, file, "fileVersion", version); 284 this.SetMsiAssemblyName(assemblyNameTuples, file, "fileVersion", version);
281 } 285 }
282 286
283 if (assemblyNameValues.ContainsKey("version")) 287 if (assemblyNameValues.ContainsKey("version"))
@@ -303,33 +307,33 @@ namespace WixToolset.Core.WindowsInstaller.Databases
303 } 307 }
304 } 308 }
305 309
306 this.SetMsiAssemblyName(assemblyNameTable, file, "version", assemblyVersion); 310 this.SetMsiAssemblyName(assemblyNameTuples, file, "version", assemblyVersion);
307 } 311 }
308 312
309 if (assemblyNameValues.ContainsKey("culture")) 313 if (assemblyNameValues.ContainsKey("culture"))
310 { 314 {
311 this.SetMsiAssemblyName(assemblyNameTable, file, "culture", assemblyNameValues["culture"]); 315 this.SetMsiAssemblyName(assemblyNameTuples, file, "culture", assemblyNameValues["culture"]);
312 } 316 }
313 317
314 if (assemblyNameValues.ContainsKey("publicKeyToken")) 318 if (assemblyNameValues.ContainsKey("publicKeyToken"))
315 { 319 {
316 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]); 320 this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", assemblyNameValues["publicKeyToken"]);
317 } 321 }
318 322
319 if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture)) 323 if (!String.IsNullOrEmpty(file.WixFile.ProcessorArchitecture))
320 { 324 {
321 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", file.WixFile.ProcessorArchitecture); 325 this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", file.WixFile.ProcessorArchitecture);
322 } 326 }
323 327
324 if (assemblyNameValues.ContainsKey("processorArchitecture")) 328 if (assemblyNameValues.ContainsKey("processorArchitecture"))
325 { 329 {
326 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]); 330 this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", assemblyNameValues["processorArchitecture"]);
327 } 331 }
328 332
329 // add the assembly name to the information cache 333 // add the assembly name to the information cache
330 if (null != this.VariableCache) 334 if (null != this.VariableCache)
331 { 335 {
332 string fileId = Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File); 336 string fileId = file.File.File;
333 string key = String.Concat("assemblyfullname.", fileId); 337 string key = String.Concat("assemblyfullname.", fileId);
334 string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); 338 string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]);
335 if (assemblyNameValues.ContainsKey("processorArchitecture")) 339 if (assemblyNameValues.ContainsKey("processorArchitecture"))
@@ -437,30 +441,29 @@ namespace WixToolset.Core.WindowsInstaller.Databases
437 Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message)); 441 Messaging.Instance.OnMessage(WixErrors.InvalidXml(new SourceLineNumber(fileManifest.WixFile.Source), "manifest", xe.Message));
438 } 442 }
439 443
440 Table assemblyNameTable = this.Output.EnsureTable(this.TableDefinitions["MsiAssemblyName"]);
441 if (!String.IsNullOrEmpty(win32Name)) 444 if (!String.IsNullOrEmpty(win32Name))
442 { 445 {
443 this.SetMsiAssemblyName(assemblyNameTable, file, "name", win32Name); 446 this.SetMsiAssemblyName(assemblyNameTuples, file, "name", win32Name);
444 } 447 }
445 448
446 if (!String.IsNullOrEmpty(win32Version)) 449 if (!String.IsNullOrEmpty(win32Version))
447 { 450 {
448 this.SetMsiAssemblyName(assemblyNameTable, file, "version", win32Version); 451 this.SetMsiAssemblyName(assemblyNameTuples, file, "version", win32Version);
449 } 452 }
450 453
451 if (!String.IsNullOrEmpty(win32Type)) 454 if (!String.IsNullOrEmpty(win32Type))
452 { 455 {
453 this.SetMsiAssemblyName(assemblyNameTable, file, "type", win32Type); 456 this.SetMsiAssemblyName(assemblyNameTuples, file, "type", win32Type);
454 } 457 }
455 458
456 if (!String.IsNullOrEmpty(win32ProcessorArchitecture)) 459 if (!String.IsNullOrEmpty(win32ProcessorArchitecture))
457 { 460 {
458 this.SetMsiAssemblyName(assemblyNameTable, file, "processorArchitecture", win32ProcessorArchitecture); 461 this.SetMsiAssemblyName(assemblyNameTuples, file, "processorArchitecture", win32ProcessorArchitecture);
459 } 462 }
460 463
461 if (!String.IsNullOrEmpty(win32PublicKeyToken)) 464 if (!String.IsNullOrEmpty(win32PublicKeyToken))
462 { 465 {
463 this.SetMsiAssemblyName(assemblyNameTable, file, "publicKeyToken", win32PublicKeyToken); 466 this.SetMsiAssemblyName(assemblyNameTuples, file, "publicKeyToken", win32PublicKeyToken);
464 } 467 }
465 } 468 }
466 } 469 }
@@ -469,11 +472,11 @@ namespace WixToolset.Core.WindowsInstaller.Databases
469 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise 472 /// Set an MsiAssemblyName row. If it was directly authored, override the value, otherwise
470 /// create a new row. 473 /// create a new row.
471 /// </summary> 474 /// </summary>
472 /// <param name="assemblyNameTable">MsiAssemblyName table.</param> 475 /// <param name="assemblyNameTuples">MsiAssemblyName table.</param>
473 /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param> 476 /// <param name="file">FileFacade containing the assembly read for the MsiAssemblyName row.</param>
474 /// <param name="name">MsiAssemblyName name.</param> 477 /// <param name="name">MsiAssemblyName name.</param>
475 /// <param name="value">MsiAssemblyName value.</param> 478 /// <param name="value">MsiAssemblyName value.</param>
476 private void SetMsiAssemblyName(Table assemblyNameTable, FileFacade file, string name, string value) 479 private void SetMsiAssemblyName(Dictionary<string, MsiAssemblyNameTuple> assemblyNameTuples, FileFacade file, string name, string value)
477 { 480 {
478 // check for null value (this can occur when grabbing the file version from an assembly without one) 481 // check for null value (this can occur when grabbing the file version from an assembly without one)
479 if (String.IsNullOrEmpty(value)) 482 if (String.IsNullOrEmpty(value))
@@ -482,18 +485,6 @@ namespace WixToolset.Core.WindowsInstaller.Databases
482 } 485 }
483 else 486 else
484 { 487 {
485 Row assemblyNameRow = null;
486
487 // override directly authored value
488 foreach (Row row in assemblyNameTable.Rows)
489 {
490 if ((string)row[0] == file.File.Component_ && (string)row[1] == name)
491 {
492 assemblyNameRow = row;
493 break;
494 }
495 }
496
497 // 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. 488 // 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.
498 if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType && 489 if ("name" == name && FileAssemblyType.DotNetAssembly == file.WixFile.AssemblyType &&
499 String.IsNullOrEmpty(file.WixFile.File_AssemblyApplication) && 490 String.IsNullOrEmpty(file.WixFile.File_AssemblyApplication) &&
@@ -502,17 +493,14 @@ namespace WixToolset.Core.WindowsInstaller.Databases
502 Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value)); 493 Messaging.Instance.OnMessage(WixErrors.GACAssemblyIdentityWarning(file.File.SourceLineNumbers, Path.GetFileNameWithoutExtension(file.File.LongFileName), value));
503 } 494 }
504 495
505 if (null == assemblyNameRow) 496 // override directly authored value
497 var lookup = String.Concat(file.File.Component_, "/", name);
498 if (assemblyNameTuples.TryGetValue(lookup, out var assemblyNameRow))
506 { 499 {
507 throw new NotImplementedException(); 500 assemblyNameRow = new MsiAssemblyNameTuple(file.File.SourceLineNumbers);
508#if TODO 501 assemblyNameRow.Component_ = file.File.Component_;
509 assemblyNameRow = assemblyNameTable.CreateRow(file.File.SourceLineNumbers); 502 assemblyNameRow.Name = name;
510 assemblyNameRow[0] = file.File.Component_; 503 assemblyNameRow.Value = value;
511 assemblyNameRow[1] = name;
512 assemblyNameRow[2] = value;
513
514 // put the MsiAssemblyName row in the same section as the related File row
515 assemblyNameRow.SectionId = file.File.SectionId;
516 504
517 if (null == file.AssemblyNames) 505 if (null == file.AssemblyNames)
518 { 506 {
@@ -520,17 +508,15 @@ namespace WixToolset.Core.WindowsInstaller.Databases
520 } 508 }
521 509
522 file.AssemblyNames.Add(assemblyNameRow); 510 file.AssemblyNames.Add(assemblyNameRow);
523#endif 511 this.Section.Tuples.Add(assemblyNameRow);
524 }
525 else
526 {
527 assemblyNameRow[2] = value;
528 } 512 }
529 513
514 assemblyNameRow.Value = value;
515
530 if (this.VariableCache != null) 516 if (this.VariableCache != null)
531 { 517 {
532 string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, Common.Demodularize(this.Output.Type, this.ModularizationGuid, file.File.File)).ToLowerInvariant(); 518 var key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, file.File.File).ToLowerInvariant();
533 this.VariableCache[key] = (string)assemblyNameRow[2]; 519 this.VariableCache[key] = value;
534 } 520 }
535 } 521 }
536 } 522 }
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
new file mode 100644
index 00000000..db74eda5
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
@@ -0,0 +1,126 @@
1// Copyright (c) .NET Foundation 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.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 using WixToolset.Core.Bind;
9 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Data.Tuples;
12
13 internal class UpdateMediaSequencesCommand
14 {
15 public UpdateMediaSequencesCommand(Output output, List<FileFacade> fileFacades, Dictionary<int, MediaTuple> assignedMediaRows)
16 {
17 this.Output = output;
18 this.FileFacades = fileFacades;
19 }
20
21 private Output Output { get; }
22
23 private List<FileFacade> FileFacades { get; }
24
25 public void Execute()
26 {
27 var fileRows = new RowDictionary<FileRow>(this.Output.Tables["File"]);
28 var mediaRows = new RowDictionary<MediaRow>(this.Output.Tables["Media"]);
29
30 // Calculate sequence numbers and media disk id layout for all file media information objects.
31 if (OutputType.Module == this.Output.Type)
32 {
33 var lastSequence = 0;
34
35 // Order by Component to group the files by directory.
36 var optimized = this.OptimizedFileFacades();
37 foreach (var fileId in optimized.Select(f => f.File.File))
38 {
39 var fileRow = fileRows.Get(fileId);
40 fileRow.Sequence = ++lastSequence;
41 }
42 }
43 else
44 {
45 int lastSequence = 0;
46 MediaRow mediaRow = null;
47 Dictionary<int, List<FileFacade>> patchGroups = new Dictionary<int, List<FileFacade>>();
48
49 // sequence the non-patch-added files
50 var optimized = this.OptimizedFileFacades();
51 foreach (FileFacade facade in optimized)
52 {
53 if (null == mediaRow)
54 {
55 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
56 if (OutputType.Patch == this.Output.Type)
57 {
58 // patch Media cannot start at zero
59 lastSequence = mediaRow.LastSequence;
60 }
61 }
62 else if (mediaRow.DiskId != facade.WixFile.DiskId)
63 {
64 mediaRow.LastSequence = lastSequence;
65 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
66 }
67
68 if (0 < facade.WixFile.PatchGroup)
69 {
70 if (patchGroups.TryGetValue(facade.WixFile.PatchGroup, out var patchGroup))
71 {
72 patchGroup = new List<FileFacade>();
73 patchGroups.Add(facade.WixFile.PatchGroup, patchGroup);
74 }
75
76 patchGroup.Add(facade);
77 }
78 else
79 {
80 var fileRow = fileRows.Get(facade.File.File);
81 fileRow.Sequence = ++lastSequence;
82 }
83 }
84
85 if (null != mediaRow)
86 {
87 mediaRow.LastSequence = lastSequence;
88 mediaRow = null;
89 }
90
91 // sequence the patch-added files
92 foreach (var patchGroup in patchGroups.Values)
93 {
94 foreach (var facade in patchGroup)
95 {
96 if (null == mediaRow)
97 {
98 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
99 }
100 else if (mediaRow.DiskId != facade.WixFile.DiskId)
101 {
102 mediaRow.LastSequence = lastSequence;
103 mediaRow = mediaRows.Get(facade.WixFile.DiskId);
104 }
105
106 var fileRow = fileRows.Get(facade.File.File);
107 fileRow.Sequence = ++lastSequence;
108 }
109 }
110
111 if (null != mediaRow)
112 {
113 mediaRow.LastSequence = lastSequence;
114 }
115 }
116 }
117
118 private IEnumerable<FileFacade> OptimizedFileFacades()
119 {
120 // TODO: Sort these facades even smarter by directory path and component id
121 // and maybe file size or file extension and other creative ideas to
122 // get optimal install speed out of MSI.
123 return this.FileFacades.OrderBy(f => f.File.Component_);
124 }
125 }
126}