aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2017-12-07 14:19:05 -0800
committerRob Mensching <rob@firegiant.com>2017-12-07 14:19:05 -0800
commit49f1209035aac1fcfad5dbbe25f7b2306d3be86c (patch)
tree6ce5921493eb751b6d89c3faf0ebdf64110cbb65 /src/WixToolset.Core.WindowsInstaller/Bind
parentb1e662bd480241ea914f0f3d6bd174d9ffd03f5f (diff)
downloadwix-49f1209035aac1fcfad5dbbe25f7b2306d3be86c.tar.gz
wix-49f1209035aac1fcfad5dbbe25f7b2306d3be86c.tar.bz2
wix-49f1209035aac1fcfad5dbbe25f7b2306d3be86c.zip
Support MSI backends creating custom tables and remove WixToolset.Data.WindowsInstaller
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs18
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs9
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs3
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs228
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs25
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs55
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs7
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs238
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs3
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs1
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs4
12 files changed, 564 insertions, 28 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
index 012998e6..9e30aed2 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindDatabaseCommand.cs
@@ -10,6 +10,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
10 using WixToolset.Data; 10 using WixToolset.Data;
11 using WixToolset.Data.Bind; 11 using WixToolset.Data.Bind;
12 using WixToolset.Data.Tuples; 12 using WixToolset.Data.Tuples;
13 using WixToolset.Data.WindowsInstaller;
13 using WixToolset.Extensibility; 14 using WixToolset.Extensibility;
14 using WixToolset.Extensibility.Services; 15 using WixToolset.Extensibility.Services;
15 16
@@ -21,7 +22,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
21 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs. 22 // As outlined in RFC 4122, this is our namespace for generating name-based (version 3) UUIDs.
22 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}"); 23 internal static readonly Guid WixComponentGuidNamespace = new Guid("{3064E5C6-FB63-4FE9-AC49-E446A792EFA5}");
23 24
24 public BindDatabaseCommand(IBindContext context, Validator validator) 25 public BindDatabaseCommand(IBindContext context, IEnumerable<IWindowsInstallerBackendExtension> backendExtension, Validator validator)
25 { 26 {
26 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions(); 27 this.TableDefinitions = WindowsInstallerStandardInternal.GetTableDefinitions();
27 28
@@ -40,7 +41,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
40 this.Validator = validator; 41 this.Validator = validator;
41 this.WixVariableResolver = context.WixVariableResolver; 42 this.WixVariableResolver = context.WixVariableResolver;
42 43
43 this.BackendExtensions = context.ExtensionManager.Create<IWindowsInstallerBackendExtension>(); 44 this.BackendExtensions = backendExtension;
44 } 45 }
45 46
46 private IEnumerable<BindPath> BindPaths { get; } 47 private IEnumerable<BindPath> BindPaths { get; }
@@ -298,7 +299,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
298 // Try to put as much above here as possible, updating the IR is better. 299 // Try to put as much above here as possible, updating the IR is better.
299 Output output; 300 Output output;
300 { 301 {
301 var command = new CreateOutputFromIRCommand(section, this.TableDefinitions); 302 var command = new CreateOutputFromIRCommand(section, this.TableDefinitions, this.BackendExtensions);
302 command.Execute(); 303 command.Execute();
303 304
304 output = command.Output; 305 output = command.Output;
@@ -313,13 +314,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
313 // Modularize identifiers and add tables with real streams to the import tables. 314 // Modularize identifiers and add tables with real streams to the import tables.
314 if (OutputType.Module == output.Type) 315 if (OutputType.Module == output.Type)
315 { 316 {
316 // Gather all the suppress modularization identifiers 317 var command = new ModularaizeCommand(output, modularizationGuid, section.Tuples.OfType<WixSuppressModularizationTuple>());
317 var suppressModularizationIdentifiers = new HashSet<string>(section.Tuples.OfType<WixSuppressModularizationTuple>().Select(s => s.WixSuppressModularization)); 318 command.Execute();
318
319 foreach (var table in output.Tables)
320 {
321 table.Modularize(modularizationGuid, suppressModularizationIdentifiers);
322 }
323 } 319 }
324 320
325#if TODO_FINISH_UPDATE 321#if TODO_FINISH_UPDATE
@@ -897,7 +893,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
897 { 893 {
898 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count); 894 Dictionary<string, bool> componentGuidConditions = new Dictionary<string, bool>(componentTable.Rows.Count);
899 895
900 foreach (Data.Rows.ComponentRow row in componentTable.Rows) 896 foreach (Data.WindowsInstaller.Rows.ComponentRow row in componentTable.Rows)
901 { 897 {
902 // we don't care about unmanaged components and if there's a * GUID remaining, 898 // we don't care about unmanaged components and if there's a * GUID remaining,
903 // there's already an error that prevented it from being replaced with a real GUID. 899 // there's already an error that prevented it from being replaced with a real GUID.
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
index b4027834..49440cea 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/BindTransformCommand.cs
@@ -10,6 +10,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
10 using WixToolset.Extensibility; 10 using WixToolset.Extensibility;
11 using WixToolset.Msi; 11 using WixToolset.Msi;
12 using WixToolset.Core.Native; 12 using WixToolset.Core.Native;
13 using WixToolset.Data.WindowsInstaller;
13 14
14 internal class BindTransformCommand 15 internal class BindTransformCommand
15 { 16 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
index 0dcddb99..559d440c 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CopyTransformDataCommand.cs
@@ -5,12 +5,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
5 using System; 5 using System;
6 using System.Collections.Generic; 6 using System.Collections.Generic;
7 using System.Diagnostics; 7 using System.Diagnostics;
8 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Extensibility;
11 using WixToolset.Core.Native;
12 using WixToolset.Core.Bind; 8 using WixToolset.Core.Bind;
9 using WixToolset.Core.Native;
10 using WixToolset.Data;
13 using WixToolset.Data.Tuples; 11 using WixToolset.Data.Tuples;
12 using WixToolset.Data.WindowsInstaller;
13 using WixToolset.Data.WindowsInstaller.Rows;
14 using WixToolset.Extensibility;
14 15
15 internal class CopyTransformDataCommand 16 internal class CopyTransformDataCommand
16 { 17 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
index a449397d..0aa50bd9 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateCabinetsCommand.cs
@@ -12,8 +12,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
12 using WixToolset.Core.Bind; 12 using WixToolset.Core.Bind;
13 using WixToolset.Data; 13 using WixToolset.Data;
14 using WixToolset.Data.Bind; 14 using WixToolset.Data.Bind;
15 using WixToolset.Data.Rows;
16 using WixToolset.Data.Tuples; 15 using WixToolset.Data.Tuples;
16 using WixToolset.Data.WindowsInstaller;
17 using WixToolset.Data.WindowsInstaller.Rows;
17 using WixToolset.Extensibility; 18 using WixToolset.Extensibility;
18 19
19 /// <summary> 20 /// <summary>
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs
new file mode 100644
index 00000000..1fc7d068
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateIdtFileCommand.cs
@@ -0,0 +1,228 @@
1// Copyright (c) .NET Foundation 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.Globalization;
7 using System.IO;
8 using System.Text;
9 using WixToolset.Data;
10 using WixToolset.Data.WindowsInstaller;
11
12 internal class CreateIdtFileCommand
13 {
14 public CreateIdtFileCommand(Table table, int codepage, string intermediateFolder, bool keepAddedColumns)
15 {
16 this.Table = table;
17 this.Codepage = codepage;
18 this.IntermediateFolder = intermediateFolder;
19 this.KeepAddedColumns = keepAddedColumns;
20 }
21
22 private Table Table { get; }
23
24 private int Codepage { get; set; }
25
26 private string IntermediateFolder { get; }
27
28 private bool KeepAddedColumns { get; }
29
30 public string IdtPath { get; private set; }
31
32 public void Execute()
33 {
34 // write out the table to an IDT file
35 Encoding encoding;
36
37 // If UTF8 encoding, use the UTF8-specific constructor to avoid writing
38 // the byte order mark at the beginning of the file
39 if (this.Codepage == Encoding.UTF8.CodePage)
40 {
41 encoding = new UTF8Encoding(false, true);
42 }
43 else
44 {
45 if (this.Codepage == 0)
46 {
47 this.Codepage = Encoding.ASCII.CodePage;
48 }
49
50 encoding = Encoding.GetEncoding(this.Codepage, new EncoderExceptionFallback(), new DecoderExceptionFallback());
51 }
52
53 this.IdtPath = Path.Combine(this.IntermediateFolder, String.Concat(this.Table.Name, ".idt"));
54
55 using (var idtWriter = new StreamWriter(this.IdtPath, false, encoding))
56 {
57 this.TableToIdtDefinition(this.Table, idtWriter, this.KeepAddedColumns);
58 }
59 }
60
61 private void TableToIdtDefinition(Table table, StreamWriter writer, bool keepAddedColumns)
62 {
63 if (table.Definition.Unreal)
64 {
65 return;
66 }
67
68 if (TableDefinition.MaxColumnsInRealTable < table.Definition.Columns.Count)
69 {
70 throw new WixException(WixDataErrors.TooManyColumnsInRealTable(table.Definition.Name, table.Definition.Columns.Count, TableDefinition.MaxColumnsInRealTable));
71 }
72
73 // Tack on the table header, and flush before we start writing bytes directly to the stream.
74 var header = this.TableDefinitionToIdtDefinition(table.Definition, keepAddedColumns);
75 writer.Write(header);
76 writer.Flush();
77
78 using (var binary = new BinaryWriter(writer.BaseStream, writer.Encoding, true))
79 {
80 // Create an encoding that replaces characters with question marks, and doesn't throw. We'll
81 // use this in case of errors
82 Encoding convertEncoding = Encoding.GetEncoding(writer.Encoding.CodePage);
83
84 foreach (Row row in table.Rows)
85 {
86 if (row.Redundant)
87 {
88 continue;
89 }
90
91 string rowString = this.RowToIdtDefinition(row, keepAddedColumns);
92 byte[] rowBytes;
93
94 try
95 {
96 // GetBytes will throw an exception if any character doesn't match our current encoding
97 rowBytes = writer.Encoding.GetBytes(rowString);
98 }
99 catch (EncoderFallbackException)
100 {
101 Messaging.Instance.OnMessage(WixDataErrors.InvalidStringForCodepage(row.SourceLineNumbers, Convert.ToString(writer.Encoding.WindowsCodePage, CultureInfo.InvariantCulture)));
102
103 rowBytes = convertEncoding.GetBytes(rowString);
104 }
105
106 binary.Write(rowBytes, 0, rowBytes.Length);
107 }
108 }
109 }
110
111 private string TableDefinitionToIdtDefinition(TableDefinition definition, bool keepAddedColumns)
112 {
113 var first = true;
114 var columnString = new StringBuilder();
115 var dataString = new StringBuilder();
116 var tableString = new StringBuilder();
117
118 tableString.Append(definition.Name);
119 foreach (var column in definition.Columns)
120 {
121 // conditionally keep columns added in a transform; otherwise,
122 // break because columns can only be added at the end
123 if (column.Added && !keepAddedColumns)
124 {
125 break;
126 }
127
128 if (!first)
129 {
130 columnString.Append('\t');
131 dataString.Append('\t');
132 }
133
134 columnString.Append(column.Name);
135 dataString.Append(ColumnIdtType(column));
136
137 if (column.PrimaryKey)
138 {
139 tableString.AppendFormat("\t{0}", column.Name);
140 }
141
142 first = false;
143 }
144 columnString.Append("\r\n");
145 columnString.Append(dataString);
146 columnString.Append("\r\n");
147 columnString.Append(tableString);
148 columnString.Append("\r\n");
149
150 return columnString.ToString();
151 }
152
153 private string RowToIdtDefinition(Row row, bool keepAddedColumns)
154 {
155 var first = true;
156 var sb = new StringBuilder();
157
158 foreach (var field in row.Fields)
159 {
160 // Conditionally keep columns added in a transform; otherwise,
161 // break because columns can only be added at the end.
162 if (field.Column.Added && !keepAddedColumns)
163 {
164 break;
165 }
166
167 if (first)
168 {
169 first = false;
170 }
171 else
172 {
173 sb.Append('\t');
174 }
175
176 sb.Append(this.FieldToIdtValue(field));
177 }
178 sb.Append("\r\n");
179
180 return sb.ToString();
181 }
182
183 private string FieldToIdtValue(Field field)
184 {
185 var data = field.AsString();
186
187 if (String.IsNullOrEmpty(data))
188 {
189 return data;
190 }
191
192 // Special field value idt-specific escaping.
193 return data.Replace('\t', '\x10')
194 .Replace('\r', '\x11')
195 .Replace('\n', '\x19');
196 }
197
198
199 /// <summary>
200 /// Gets the type of the column in IDT format.
201 /// </summary>
202 /// <value>IDT format for column type.</value>
203 private static string ColumnIdtType(ColumnDefinition column)
204 {
205 char typeCharacter;
206 switch (column.Type)
207 {
208 case ColumnType.Number:
209 typeCharacter = column.Nullable ? 'I' : 'i';
210 break;
211 case ColumnType.Preserved:
212 case ColumnType.String:
213 typeCharacter = column.Nullable ? 'S' : 's';
214 break;
215 case ColumnType.Localized:
216 typeCharacter = column.Nullable ? 'L' : 'l';
217 break;
218 case ColumnType.Object:
219 typeCharacter = column.Nullable ? 'V' : 'v';
220 break;
221 default:
222 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_UnknownColumnType, column.Type));
223 }
224
225 return String.Concat(typeCharacter, column.Length);
226 }
227 }
228}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
index a19a53f1..4e053c12 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/CreateOutputFromIRCommand.cs
@@ -3,20 +3,26 @@
3namespace WixToolset.Core.WindowsInstaller.Bind 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System; 5 using System;
6 using System.Collections.Generic;
6 using System.Linq; 7 using System.Linq;
7 using WixToolset.Core.Native; 8 using WixToolset.Core.Native;
8 using WixToolset.Data; 9 using WixToolset.Data;
9 using WixToolset.Data.Rows;
10 using WixToolset.Data.Tuples; 10 using WixToolset.Data.Tuples;
11 using WixToolset.Data.WindowsInstaller;
12 using WixToolset.Data.WindowsInstaller.Rows;
13 using WixToolset.Extensibility;
11 14
12 internal class CreateOutputFromIRCommand 15 internal class CreateOutputFromIRCommand
13 { 16 {
14 public CreateOutputFromIRCommand(IntermediateSection section, TableDefinitionCollection tableDefinitions) 17 public CreateOutputFromIRCommand(IntermediateSection section, TableDefinitionCollection tableDefinitions, IEnumerable<IWindowsInstallerBackendExtension> backendExtensions)
15 { 18 {
16 this.Section = section; 19 this.Section = section;
17 this.TableDefinitions = tableDefinitions; 20 this.TableDefinitions = tableDefinitions;
21 this.BackendExtensions = backendExtensions;
18 } 22 }
19 23
24 private IEnumerable<IWindowsInstallerBackendExtension> BackendExtensions { get; }
25
20 private TableDefinitionCollection TableDefinitions { get; } 26 private TableDefinitionCollection TableDefinitions { get; }
21 27
22 private IntermediateSection Section { get; } 28 private IntermediateSection Section { get; }
@@ -60,6 +66,10 @@ namespace WixToolset.Core.WindowsInstaller.Bind
60 // Ignored. 66 // Ignored.
61 break; 67 break;
62 68
69 case TupleDefinitionType.MustBeFromAnExtension:
70 this.AddTupleFromExtension(tuple, output);
71 break;
72
63 default: 73 default:
64 this.AddTupleDefaultly(tuple, output); 74 this.AddTupleDefaultly(tuple, output);
65 break; 75 break;
@@ -206,6 +216,17 @@ namespace WixToolset.Core.WindowsInstaller.Bind
206 } 216 }
207 } 217 }
208 218
219 private void AddTupleFromExtension(IntermediateTuple tuple, Output output)
220 {
221 foreach (var extension in this.BackendExtensions)
222 {
223 if (extension.TryAddTupleToOutput(tuple, output))
224 {
225 break;
226 }
227 }
228 }
229
209 private void AddTupleDefaultly(IntermediateTuple tuple, Output output) 230 private void AddTupleDefaultly(IntermediateTuple tuple, Output output)
210 { 231 {
211 if (!this.TableDefinitions.TryGet(tuple.Definition.Name, out var tableDefinition)) 232 if (!this.TableDefinitions.TryGet(tuple.Definition.Name, out var tableDefinition))
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
index a3d3ecf7..e4e66559 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/GenerateDatabaseCommand.cs
@@ -12,6 +12,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
12 using WixToolset.Extensibility; 12 using WixToolset.Extensibility;
13 using WixToolset.Msi; 13 using WixToolset.Msi;
14 using WixToolset.Core.Native; 14 using WixToolset.Core.Native;
15 using WixToolset.Data.WindowsInstaller;
15 16
16 internal class GenerateDatabaseCommand 17 internal class GenerateDatabaseCommand
17 { 18 {
@@ -44,14 +45,56 @@ namespace WixToolset.Core.WindowsInstaller.Bind
44 // Add the _Validation rows. 45 // Add the _Validation rows.
45 if (!this.SuppressAddingValidationRows) 46 if (!this.SuppressAddingValidationRows)
46 { 47 {
47 Table validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]); 48 var validationTable = this.Output.EnsureTable(this.TableDefinitions["_Validation"]);
48 49
49 foreach (Table table in this.Output.Tables) 50 foreach (var table in this.Output.Tables)
50 { 51 {
51 if (!table.Definition.Unreal) 52 if (!table.Definition.Unreal)
52 { 53 {
53 // Add the validation rows for this table. 54 // Add the validation rows for this table.
54 table.Definition.AddValidationRows(validationTable); 55 foreach (ColumnDefinition columnDef in table.Definition.Columns)
56 {
57 var row = validationTable.CreateRow(null);
58
59 row[0] = table.Name;
60
61 row[1] = columnDef.Name;
62
63 if (columnDef.Nullable)
64 {
65 row[2] = "Y";
66 }
67 else
68 {
69 row[2] = "N";
70 }
71
72 if (columnDef.MinValue.HasValue)
73 {
74 row[3] = columnDef.MinValue.Value;
75 }
76
77 if (columnDef.MaxValue.HasValue)
78 {
79 row[4] = columnDef.MaxValue.Value;
80 }
81
82 row[5] = columnDef.KeyTable;
83
84 if (columnDef.KeyColumn.HasValue)
85 {
86 row[6] = columnDef.KeyColumn.Value;
87 }
88
89 if (ColumnCategory.Unknown != columnDef.Category)
90 {
91 row[7] = columnDef.Category.ToString();
92 }
93
94 row[8] = columnDef.Possibilities;
95
96 row[9] = columnDef.Description;
97 }
55 } 98 }
56 } 99 }
57 } 100 }
@@ -133,7 +176,11 @@ namespace WixToolset.Core.WindowsInstaller.Bind
133 { 176 {
134 try 177 try
135 { 178 {
136 db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns); 179 //db.ImportTable(this.Output.Codepage, importTable, baseDirectory, this.KeepAddedColumns);
180 var command = new CreateIdtFileCommand(importTable, this.Output.Codepage, baseDirectory, this.KeepAddedColumns);
181 command.Execute();
182
183 db.Import(command.IdtPath);
137 } 184 }
138 catch (WixInvalidIdtException) 185 catch (WixInvalidIdtException)
139 { 186 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
index dcf67c05..32a05d93 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/MergeModulesCommand.cs
@@ -8,12 +8,13 @@ namespace WixToolset.Core.WindowsInstaller.Bind
8 using System.IO; 8 using System.IO;
9 using System.Runtime.InteropServices; 9 using System.Runtime.InteropServices;
10 using System.Text; 10 using System.Text;
11 using WixToolset.Core.Bind;
12 using WixToolset.Core.Native;
11 using WixToolset.Data; 13 using WixToolset.Data;
12 using WixToolset.Data.Rows; 14 using WixToolset.Data.WindowsInstaller;
15 using WixToolset.Data.WindowsInstaller.Rows;
13 using WixToolset.MergeMod; 16 using WixToolset.MergeMod;
14 using WixToolset.Msi; 17 using WixToolset.Msi;
15 using WixToolset.Core.Native;
16 using WixToolset.Core.Bind;
17 18
18 /// <summary> 19 /// <summary>
19 /// Update file information. 20 /// Update file information.
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs
new file mode 100644
index 00000000..03538fc3
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/ModularaizeCommand.cs
@@ -0,0 +1,238 @@
1// Copyright (c) .NET Foundation 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.Diagnostics;
8 using System.Globalization;
9 using System.Linq;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using WixToolset.Data;
13 using WixToolset.Data.Tuples;
14 using WixToolset.Data.WindowsInstaller;
15
16 internal class ModularaizeCommand
17 {
18 public ModularaizeCommand(Output output, string modularizationGuid, IEnumerable<WixSuppressModularizationTuple> suppressTuples)
19 {
20 this.Output = output;
21 this.ModularizationGuid = modularizationGuid;
22
23 // Gather all the unique suppress modularization identifiers.
24 this.SuppressModularizationIdentifiers = new HashSet<string>(suppressTuples.Select(s => s.WixSuppressModularization));
25 }
26
27 private Output Output { get; }
28
29 private string ModularizationGuid { get; }
30
31 private HashSet<string> SuppressModularizationIdentifiers { get; }
32
33 public void Execute()
34 {
35 foreach (var table in this.Output.Tables)
36 {
37 this.ModularizeTable(table);
38 }
39 }
40
41 /// <summary>
42 /// Modularize the table.
43 /// </summary>
44 /// <param name="modularizationGuid">String containing the GUID of the Merge Module, if appropriate.</param>
45 /// <param name="suppressModularizationIdentifiers">Optional collection of identifiers that should not be modularized.</param>
46 public void ModularizeTable(Table table)
47 {
48 var modularizedColumns = new List<int>();
49
50 // find the modularized columns
51 for (var i = 0; i < table.Definition.Columns.Count; ++i)
52 {
53 if (ColumnModularizeType.None != table.Definition.Columns[i].ModularizeType)
54 {
55 modularizedColumns.Add(i);
56 }
57 }
58
59 if (0 < modularizedColumns.Count)
60 {
61 foreach (var row in table.Rows)
62 {
63 foreach (var modularizedColumn in modularizedColumns)
64 {
65 var field = row.Fields[modularizedColumn];
66
67 if (field.Data != null)
68 {
69 field.Data = this.ModularizedRowFieldValue(row, field);
70 }
71 }
72 }
73 }
74 }
75
76 private string ModularizedRowFieldValue(Row row, Field field)
77 {
78 var fieldData = field.AsString();
79
80 if (!(WindowsInstallerStandard.IsStandardAction(fieldData) || WindowsInstallerStandard.IsStandardProperty(fieldData)))
81 {
82 ColumnModularizeType modularizeType = field.Column.ModularizeType;
83
84 // special logic for the ControlEvent table's Argument column
85 // this column requires different modularization methods depending upon the value of the Event column
86 if (ColumnModularizeType.ControlEventArgument == field.Column.ModularizeType)
87 {
88 switch (row[2].ToString())
89 {
90 case "CheckExistingTargetPath": // redirectable property name
91 case "CheckTargetPath":
92 case "DoAction": // custom action name
93 case "NewDialog": // dialog name
94 case "SelectionBrowse":
95 case "SetTargetPath":
96 case "SpawnDialog":
97 case "SpawnWaitDialog":
98 if (Common.IsIdentifier(fieldData))
99 {
100 modularizeType = ColumnModularizeType.Column;
101 }
102 else
103 {
104 modularizeType = ColumnModularizeType.Property;
105 }
106 break;
107 default: // formatted
108 modularizeType = ColumnModularizeType.Property;
109 break;
110 }
111 }
112 else if (ColumnModularizeType.ControlText == field.Column.ModularizeType)
113 {
114 // icons are stored in the Binary table, so they get column-type modularization
115 if (("Bitmap" == row[2].ToString() || "Icon" == row[2].ToString()) && Common.IsIdentifier(fieldData))
116 {
117 modularizeType = ColumnModularizeType.Column;
118 }
119 else
120 {
121 modularizeType = ColumnModularizeType.Property;
122 }
123 }
124
125 switch (modularizeType)
126 {
127 case ColumnModularizeType.Column:
128 // ensure the value is an identifier (otherwise it shouldn't be modularized this way)
129 if (!Common.IsIdentifier(fieldData))
130 {
131 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixDataStrings.EXP_CannotModularizeIllegalID, fieldData));
132 }
133
134 // if we're not supposed to suppress modularization of this identifier
135 if (!this.SuppressModularizationIdentifiers.Contains(fieldData))
136 {
137 fieldData = String.Concat(fieldData, ".", this.ModularizationGuid);
138 }
139 break;
140
141 case ColumnModularizeType.Property:
142 case ColumnModularizeType.Condition:
143 Regex regex;
144 if (ColumnModularizeType.Property == modularizeType)
145 {
146 regex = new Regex(@"\[(?<identifier>[#$!]?[a-zA-Z_][a-zA-Z0-9_\.]*)]", RegexOptions.Singleline | RegexOptions.ExplicitCapture);
147 }
148 else
149 {
150 Debug.Assert(ColumnModularizeType.Condition == modularizeType);
151
152 // This heinous looking regular expression is actually quite an elegant way
153 // to shred the entire condition into the identifiers that need to be
154 // modularized. Let's break it down piece by piece:
155 //
156 // 1. Look for the operators: NOT, EQV, XOR, OR, AND, IMP (plus a space). Note that the
157 // regular expression is case insensitive so we don't have to worry about
158 // all the permutations of these strings.
159 // 2. Look for quoted strings. Quoted strings are just text and are ignored
160 // outright.
161 // 3. Look for environment variables. These look like identifiers we might
162 // otherwise be interested in but start with a percent sign. Like quoted
163 // strings these enviroment variable references are ignored outright.
164 // 4. Match all identifiers that are things that need to be modularized. Note
165 // the special characters (!, $, ?, &) that denote Component and Feature states.
166 regex = new Regex(@"NOT\s|EQV\s|XOR\s|OR\s|AND\s|IMP\s|"".*?""|%[a-zA-Z_][a-zA-Z0-9_\.]*|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)", RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
167
168 // less performant version of the above with captures showing where everything lives
169 // regex = new Regex(@"(?<operator>NOT|EQV|XOR|OR|AND|IMP)|(?<string>"".*?"")|(?<environment>%[a-zA-Z_][a-zA-Z0-9_\.]*)|(?<identifier>[!$\?&]?[a-zA-Z_][a-zA-Z0-9_\.]*)",RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture);
170 }
171
172 var matches = regex.Matches(fieldData);
173
174 var sb = new StringBuilder(fieldData);
175
176 // Notice how this code walks backward through the list
177 // because it modifies the string as we through it.
178 for (var i = matches.Count - 1; 0 <= i; i--)
179 {
180 var group = matches[i].Groups["identifier"];
181 if (group.Success)
182 {
183 var identifier = group.Value;
184 if (!WindowsInstallerStandard.IsStandardProperty(identifier) && !this.SuppressModularizationIdentifiers.Contains(identifier))
185 {
186 sb.Insert(group.Index + group.Length, '.');
187 sb.Insert(group.Index + group.Length + 1, this.ModularizationGuid);
188 }
189 }
190 }
191
192 fieldData = sb.ToString();
193 break;
194
195 case ColumnModularizeType.CompanionFile:
196 // if we're not supposed to ignore this identifier and the value does not start with
197 // a digit, we must have a companion file so modularize it
198 if (!this.SuppressModularizationIdentifiers.Contains(fieldData) &&
199 0 < fieldData.Length && !Char.IsDigit(fieldData, 0))
200 {
201 fieldData = String.Concat(fieldData, ".", this.ModularizationGuid);
202 }
203 break;
204
205 case ColumnModularizeType.Icon:
206 if (!this.SuppressModularizationIdentifiers.Contains(fieldData))
207 {
208 var start = fieldData.LastIndexOf(".", StringComparison.Ordinal);
209 if (-1 == start)
210 {
211 fieldData = String.Concat(fieldData, ".", this.ModularizationGuid);
212 }
213 else
214 {
215 fieldData = String.Concat(fieldData.Substring(0, start), ".", this.ModularizationGuid, fieldData.Substring(start));
216 }
217 }
218 break;
219
220 case ColumnModularizeType.SemicolonDelimited:
221 var keys = fieldData.Split(';');
222 for (var i = 0; i < keys.Length; ++i)
223 {
224 if (!String.IsNullOrEmpty(keys[i]))
225 {
226 keys[i] = String.Concat(keys[i], ".", this.ModularizationGuid);
227 }
228 }
229
230 fieldData = String.Join(";", keys);
231 break;
232 }
233 }
234
235 return fieldData;
236 }
237 }
238}
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs
index 9579e0f8..dddc9380 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateControlTextCommand.cs
@@ -5,7 +5,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
5 using System; 5 using System;
6 using System.IO; 6 using System.IO;
7 using WixToolset.Data; 7 using WixToolset.Data;
8 using WixToolset.Data.Rows; 8 using WixToolset.Data.WindowsInstaller;
9 using WixToolset.Data.WindowsInstaller.Rows;
9 10
10 internal class UpdateControlTextCommand 11 internal class UpdateControlTextCommand
11 { 12 {
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
index a9eb2a8f..cf620e72 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateFileFacadesCommand.cs
@@ -14,6 +14,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
14 using WixToolset.Core.Bind; 14 using WixToolset.Core.Bind;
15 using WixToolset.Data; 15 using WixToolset.Data;
16 using WixToolset.Data.Tuples; 16 using WixToolset.Data.Tuples;
17 using WixToolset.Data.WindowsInstaller;
17 using WixToolset.Msi; 18 using WixToolset.Msi;
18 19
19 /// <summary> 20 /// <summary>
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
index db74eda5..0767adb0 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/UpdateMediaSequencesCommand.cs
@@ -2,13 +2,13 @@
2 2
3namespace WixToolset.Core.WindowsInstaller.Bind 3namespace WixToolset.Core.WindowsInstaller.Bind
4{ 4{
5 using System;
6 using System.Collections.Generic; 5 using System.Collections.Generic;
7 using System.Linq; 6 using System.Linq;
8 using WixToolset.Core.Bind; 7 using WixToolset.Core.Bind;
9 using WixToolset.Data; 8 using WixToolset.Data;
10 using WixToolset.Data.Rows;
11 using WixToolset.Data.Tuples; 9 using WixToolset.Data.Tuples;
10 using WixToolset.Data.WindowsInstaller;
11 using WixToolset.Data.WindowsInstaller.Rows;
12 12
13 internal class UpdateMediaSequencesCommand 13 internal class UpdateMediaSequencesCommand
14 { 14 {