diff options
author | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2017-10-14 16:12:07 -0700 |
commit | dbde9e7104b907bbbaea17e21247d8cafc8b3a4c (patch) | |
tree | 0f5fbbb6fe12c6b2e5e622a0e18ce4c5b4eb2b96 /src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs | |
parent | fbf986eb97f68396797a89fc7d40dec07b775440 (diff) | |
download | wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.gz wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.tar.bz2 wix-dbde9e7104b907bbbaea17e21247d8cafc8b3a4c.zip |
Massive refactoring to introduce the concept of IBackend
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs')
-rw-r--r-- | src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs new file mode 100644 index 00000000..208be874 --- /dev/null +++ b/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs | |||
@@ -0,0 +1,791 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Core.WindowsInstaller.Unbind | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections; | ||
7 | using System.Collections.Generic; | ||
8 | using System.Globalization; | ||
9 | using System.IO; | ||
10 | using System.Text.RegularExpressions; | ||
11 | using WixToolset.Core.Native; | ||
12 | using WixToolset.Data; | ||
13 | using WixToolset.Data.Rows; | ||
14 | using WixToolset.Msi; | ||
15 | |||
16 | internal class UnbindDatabaseCommand | ||
17 | { | ||
18 | public UnbindDatabaseCommand(Messaging messaging, Database database, string databasePath, OutputType outputType, string exportBasePath, string intermediateFolder, bool isAdminImage, bool suppressDemodularization, bool skipSummaryInfo) | ||
19 | { | ||
20 | this.Messaging = messaging; | ||
21 | this.Database = database; | ||
22 | this.DatabasePath = databasePath; | ||
23 | this.OutputType = outputType; | ||
24 | this.ExportBasePath = exportBasePath; | ||
25 | this.IntermediateFolder = intermediateFolder; | ||
26 | this.IsAdminImage = isAdminImage; | ||
27 | this.SuppressDemodularization = suppressDemodularization; | ||
28 | this.SkipSummaryInfo = skipSummaryInfo; | ||
29 | |||
30 | this.TableDefinitions = WindowsInstallerStandard.GetTableDefinitions(); | ||
31 | } | ||
32 | |||
33 | public Messaging Messaging { get; } | ||
34 | |||
35 | public Database Database { get; } | ||
36 | |||
37 | public string DatabasePath { get; } | ||
38 | |||
39 | public OutputType OutputType { get; } | ||
40 | |||
41 | public string ExportBasePath { get; } | ||
42 | |||
43 | public string IntermediateFolder { get; } | ||
44 | |||
45 | public bool IsAdminImage { get; } | ||
46 | |||
47 | public bool SuppressDemodularization { get; } | ||
48 | |||
49 | public bool SkipSummaryInfo { get; } | ||
50 | |||
51 | public TableDefinitionCollection TableDefinitions { get; } | ||
52 | |||
53 | private int SectionCount { get; set; } | ||
54 | |||
55 | public Output Execute() | ||
56 | { | ||
57 | string modularizationGuid = null; | ||
58 | Output output = new Output(new SourceLineNumber(this.DatabasePath)); | ||
59 | View validationView = null; | ||
60 | |||
61 | // set the output type | ||
62 | output.Type = this.OutputType; | ||
63 | |||
64 | // get the codepage | ||
65 | this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); | ||
66 | using (StreamReader sr = File.OpenText(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt"))) | ||
67 | { | ||
68 | string line; | ||
69 | |||
70 | while (null != (line = sr.ReadLine())) | ||
71 | { | ||
72 | string[] data = line.Split('\t'); | ||
73 | |||
74 | if (2 == data.Length) | ||
75 | { | ||
76 | output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
81 | // get the summary information table if it exists; it won't if unbinding a transform | ||
82 | if (!this.SkipSummaryInfo) | ||
83 | { | ||
84 | using (SummaryInformation summaryInformation = new SummaryInformation(this.Database)) | ||
85 | { | ||
86 | Table table = new Table(null, this.TableDefinitions["_SummaryInformation"]); | ||
87 | |||
88 | for (int i = 1; 19 >= i; i++) | ||
89 | { | ||
90 | string value = summaryInformation.GetProperty(i); | ||
91 | |||
92 | if (0 < value.Length) | ||
93 | { | ||
94 | Row row = table.CreateRow(output.SourceLineNumbers); | ||
95 | row[0] = i; | ||
96 | row[1] = value; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | output.Tables.Add(table); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | try | ||
105 | { | ||
106 | // open a view on the validation table if it exists | ||
107 | if (this.Database.TableExists("_Validation")) | ||
108 | { | ||
109 | validationView = this.Database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); | ||
110 | } | ||
111 | |||
112 | // get the normal tables | ||
113 | using (View tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) | ||
114 | { | ||
115 | while (true) | ||
116 | { | ||
117 | using (Record tableRecord = tablesView.Fetch()) | ||
118 | { | ||
119 | if (null == tableRecord) | ||
120 | { | ||
121 | break; | ||
122 | } | ||
123 | |||
124 | string tableName = tableRecord.GetString(1); | ||
125 | |||
126 | using (View tableView = this.Database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) | ||
127 | { | ||
128 | List<ColumnDefinition> columns; | ||
129 | using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES), | ||
130 | columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES)) | ||
131 | { | ||
132 | // index the primary keys | ||
133 | HashSet<string> tablePrimaryKeys = new HashSet<string>(); | ||
134 | using (Record primaryKeysRecord = this.Database.PrimaryKeys(tableName)) | ||
135 | { | ||
136 | int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); | ||
137 | |||
138 | for (int i = 1; i <= primaryKeysFieldCount; i++) | ||
139 | { | ||
140 | tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | int columnCount = columnNameRecord.GetFieldCount(); | ||
145 | columns = new List<ColumnDefinition>(columnCount); | ||
146 | for (int i = 1; i <= columnCount; i++) | ||
147 | { | ||
148 | string columnName = columnNameRecord.GetString(i); | ||
149 | string idtType = columnTypeRecord.GetString(i); | ||
150 | |||
151 | ColumnType columnType; | ||
152 | int length; | ||
153 | bool nullable; | ||
154 | |||
155 | ColumnCategory columnCategory = ColumnCategory.Unknown; | ||
156 | ColumnModularizeType columnModularizeType = ColumnModularizeType.None; | ||
157 | bool primary = tablePrimaryKeys.Contains(columnName); | ||
158 | bool minValueSet = false; | ||
159 | int minValue = -1; | ||
160 | bool maxValueSet = false; | ||
161 | int maxValue = -1; | ||
162 | string keyTable = null; | ||
163 | bool keyColumnSet = false; | ||
164 | int keyColumn = -1; | ||
165 | string category = null; | ||
166 | string set = null; | ||
167 | string description = null; | ||
168 | |||
169 | // get the column type, length, and whether its nullable | ||
170 | switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) | ||
171 | { | ||
172 | case 'i': | ||
173 | columnType = ColumnType.Number; | ||
174 | break; | ||
175 | case 'l': | ||
176 | columnType = ColumnType.Localized; | ||
177 | break; | ||
178 | case 's': | ||
179 | columnType = ColumnType.String; | ||
180 | break; | ||
181 | case 'v': | ||
182 | columnType = ColumnType.Object; | ||
183 | break; | ||
184 | default: | ||
185 | // TODO: error | ||
186 | columnType = ColumnType.Unknown; | ||
187 | break; | ||
188 | } | ||
189 | length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); | ||
190 | nullable = Char.IsUpper(idtType[0]); | ||
191 | |||
192 | // try to get validation information | ||
193 | if (null != validationView) | ||
194 | { | ||
195 | using (Record validationRecord = new Record(2)) | ||
196 | { | ||
197 | validationRecord.SetString(1, tableName); | ||
198 | validationRecord.SetString(2, columnName); | ||
199 | |||
200 | validationView.Execute(validationRecord); | ||
201 | } | ||
202 | |||
203 | using (Record validationRecord = validationView.Fetch()) | ||
204 | { | ||
205 | if (null != validationRecord) | ||
206 | { | ||
207 | string validationNullable = validationRecord.GetString(3); | ||
208 | minValueSet = !validationRecord.IsNull(4); | ||
209 | minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); | ||
210 | maxValueSet = !validationRecord.IsNull(5); | ||
211 | maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); | ||
212 | keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); | ||
213 | keyColumnSet = !validationRecord.IsNull(7); | ||
214 | keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); | ||
215 | category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); | ||
216 | set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); | ||
217 | description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); | ||
218 | |||
219 | // check the validation nullable value against the column definition | ||
220 | if (null == validationNullable) | ||
221 | { | ||
222 | // TODO: warn for illegal validation nullable column | ||
223 | } | ||
224 | else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) | ||
225 | { | ||
226 | // TODO: warn for mismatch between column definition and validation nullable | ||
227 | } | ||
228 | |||
229 | // convert category to ColumnCategory | ||
230 | if (null != category) | ||
231 | { | ||
232 | try | ||
233 | { | ||
234 | columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); | ||
235 | } | ||
236 | catch (ArgumentException) | ||
237 | { | ||
238 | columnCategory = ColumnCategory.Unknown; | ||
239 | } | ||
240 | } | ||
241 | } | ||
242 | else | ||
243 | { | ||
244 | // TODO: warn about no validation information | ||
245 | } | ||
246 | } | ||
247 | } | ||
248 | |||
249 | // guess the modularization type | ||
250 | if ("Icon" == keyTable && 1 == keyColumn) | ||
251 | { | ||
252 | columnModularizeType = ColumnModularizeType.Icon; | ||
253 | } | ||
254 | else if ("Condition" == columnName) | ||
255 | { | ||
256 | columnModularizeType = ColumnModularizeType.Condition; | ||
257 | } | ||
258 | else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) | ||
259 | { | ||
260 | columnModularizeType = ColumnModularizeType.Property; | ||
261 | } | ||
262 | else if (ColumnCategory.Identifier == columnCategory) | ||
263 | { | ||
264 | columnModularizeType = ColumnModularizeType.Column; | ||
265 | } | ||
266 | |||
267 | columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); | ||
268 | } | ||
269 | } | ||
270 | |||
271 | TableDefinition tableDefinition = new TableDefinition(tableName, columns, false, false); | ||
272 | |||
273 | // use our table definitions if core properties are the same; this allows us to take advantage | ||
274 | // of wix concepts like localizable columns which current code assumes | ||
275 | if (this.TableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.TableDefinitions[tableName])) | ||
276 | { | ||
277 | tableDefinition = this.TableDefinitions[tableName]; | ||
278 | } | ||
279 | |||
280 | Table table = new Table(null, tableDefinition); | ||
281 | |||
282 | while (true) | ||
283 | { | ||
284 | using (Record rowRecord = tableView.Fetch()) | ||
285 | { | ||
286 | if (null == rowRecord) | ||
287 | { | ||
288 | break; | ||
289 | } | ||
290 | |||
291 | int recordCount = rowRecord.GetFieldCount(); | ||
292 | Row row = table.CreateRow(output.SourceLineNumbers); | ||
293 | |||
294 | for (int i = 0; recordCount > i && row.Fields.Length > i; i++) | ||
295 | { | ||
296 | if (rowRecord.IsNull(i + 1)) | ||
297 | { | ||
298 | if (!row.Fields[i].Column.Nullable) | ||
299 | { | ||
300 | // TODO: display an error for a null value in a non-nullable field OR | ||
301 | // display a warning and put an empty string in the value to let the compiler handle it | ||
302 | // (the second option is risky because the later code may make certain assumptions about | ||
303 | // the contents of a row value) | ||
304 | } | ||
305 | } | ||
306 | else | ||
307 | { | ||
308 | switch (row.Fields[i].Column.Type) | ||
309 | { | ||
310 | case ColumnType.Number: | ||
311 | bool success = false; | ||
312 | int intValue = rowRecord.GetInteger(i + 1); | ||
313 | if (row.Fields[i].Column.IsLocalizable) | ||
314 | { | ||
315 | success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); | ||
316 | } | ||
317 | else | ||
318 | { | ||
319 | success = row.BestEffortSetField(i, intValue); | ||
320 | } | ||
321 | |||
322 | if (!success) | ||
323 | { | ||
324 | this.Messaging.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); | ||
325 | } | ||
326 | break; | ||
327 | case ColumnType.Object: | ||
328 | string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; | ||
329 | |||
330 | if (null != this.ExportBasePath) | ||
331 | { | ||
332 | string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); | ||
333 | sourceFile = Path.Combine(this.ExportBasePath, relativeSourceFile); | ||
334 | |||
335 | // ensure the parent directory exists | ||
336 | System.IO.Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); | ||
337 | |||
338 | using (FileStream fs = System.IO.File.Create(sourceFile)) | ||
339 | { | ||
340 | int bytesRead; | ||
341 | byte[] buffer = new byte[512]; | ||
342 | |||
343 | while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) | ||
344 | { | ||
345 | fs.Write(buffer, 0, bytesRead); | ||
346 | } | ||
347 | } | ||
348 | } | ||
349 | |||
350 | row[i] = sourceFile; | ||
351 | break; | ||
352 | default: | ||
353 | string value = rowRecord.GetString(i + 1); | ||
354 | |||
355 | switch (row.Fields[i].Column.Category) | ||
356 | { | ||
357 | case ColumnCategory.Guid: | ||
358 | value = value.ToUpper(CultureInfo.InvariantCulture); | ||
359 | break; | ||
360 | } | ||
361 | |||
362 | // de-modularize | ||
363 | if (!this.SuppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) | ||
364 | { | ||
365 | Regex modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); | ||
366 | |||
367 | if (null == modularizationGuid) | ||
368 | { | ||
369 | Match match = modularization.Match(value); | ||
370 | if (match.Success) | ||
371 | { | ||
372 | modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); | ||
373 | } | ||
374 | } | ||
375 | |||
376 | value = modularization.Replace(value, String.Empty); | ||
377 | } | ||
378 | |||
379 | // escape "$(" for the preprocessor | ||
380 | value = value.Replace("$(", "$$("); | ||
381 | |||
382 | // escape things that look like wix variables | ||
383 | MatchCollection matches = Common.WixVariableRegex.Matches(value); | ||
384 | for (int j = matches.Count - 1; 0 <= j; j--) | ||
385 | { | ||
386 | value = value.Insert(matches[j].Index, "!"); | ||
387 | } | ||
388 | |||
389 | row[i] = value; | ||
390 | break; | ||
391 | } | ||
392 | } | ||
393 | } | ||
394 | } | ||
395 | } | ||
396 | |||
397 | output.Tables.Add(table); | ||
398 | } | ||
399 | |||
400 | } | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | finally | ||
405 | { | ||
406 | if (null != validationView) | ||
407 | { | ||
408 | validationView.Close(); | ||
409 | } | ||
410 | } | ||
411 | |||
412 | // set the modularization guid as the PackageCode | ||
413 | if (null != modularizationGuid) | ||
414 | { | ||
415 | Table table = output.Tables["_SummaryInformation"]; | ||
416 | |||
417 | foreach (Row row in table.Rows) | ||
418 | { | ||
419 | if (9 == (int)row[0]) // PID_REVNUMBER | ||
420 | { | ||
421 | row[1] = modularizationGuid; | ||
422 | } | ||
423 | } | ||
424 | } | ||
425 | |||
426 | if (this.IsAdminImage) | ||
427 | { | ||
428 | GenerateWixFileTable(this.DatabasePath, output); | ||
429 | GenerateSectionIds(output); | ||
430 | } | ||
431 | |||
432 | return output; | ||
433 | } | ||
434 | |||
435 | /// <summary> | ||
436 | /// Generates the WixFile table based on a path to an admin image msi and an Output. | ||
437 | /// </summary> | ||
438 | /// <param name="databaseFile">The path to the msi database file in an admin image.</param> | ||
439 | /// <param name="output">The Output that represents the msi database.</param> | ||
440 | private void GenerateWixFileTable(string databaseFile, Output output) | ||
441 | { | ||
442 | string adminRootPath = Path.GetDirectoryName(databaseFile); | ||
443 | |||
444 | Hashtable componentDirectoryIndex = new Hashtable(); | ||
445 | Table componentTable = output.Tables["Component"]; | ||
446 | foreach (Row row in componentTable.Rows) | ||
447 | { | ||
448 | componentDirectoryIndex.Add(row[0], row[2]); | ||
449 | } | ||
450 | |||
451 | // Index full source paths for all directories | ||
452 | Hashtable directoryDirectoryParentIndex = new Hashtable(); | ||
453 | Hashtable directoryFullPathIndex = new Hashtable(); | ||
454 | Hashtable directorySourceNameIndex = new Hashtable(); | ||
455 | Table directoryTable = output.Tables["Directory"]; | ||
456 | foreach (Row row in directoryTable.Rows) | ||
457 | { | ||
458 | directoryDirectoryParentIndex.Add(row[0], row[1]); | ||
459 | if (null == row[1]) | ||
460 | { | ||
461 | directoryFullPathIndex.Add(row[0], adminRootPath); | ||
462 | } | ||
463 | else | ||
464 | { | ||
465 | directorySourceNameIndex.Add(row[0], GetAdminSourceName((string)row[2])); | ||
466 | } | ||
467 | } | ||
468 | |||
469 | foreach (DictionaryEntry directoryEntry in directoryDirectoryParentIndex) | ||
470 | { | ||
471 | if (!directoryFullPathIndex.ContainsKey(directoryEntry.Key)) | ||
472 | { | ||
473 | GetAdminFullPath((string)directoryEntry.Key, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); | ||
474 | } | ||
475 | } | ||
476 | |||
477 | Table fileTable = output.Tables["File"]; | ||
478 | Table wixFileTable = output.EnsureTable(this.TableDefinitions["WixFile"]); | ||
479 | foreach (Row row in fileTable.Rows) | ||
480 | { | ||
481 | WixFileRow wixFileRow = new WixFileRow(null, this.TableDefinitions["WixFile"]); | ||
482 | wixFileRow.File = (string)row[0]; | ||
483 | wixFileRow.Directory = (string)componentDirectoryIndex[(string)row[1]]; | ||
484 | wixFileRow.Source = Path.Combine((string)directoryFullPathIndex[wixFileRow.Directory], GetAdminSourceName((string)row[2])); | ||
485 | |||
486 | if (!File.Exists(wixFileRow.Source)) | ||
487 | { | ||
488 | throw new WixException(WixErrors.WixFileNotFound(wixFileRow.Source)); | ||
489 | } | ||
490 | |||
491 | wixFileTable.Rows.Add(wixFileRow); | ||
492 | } | ||
493 | } | ||
494 | |||
495 | /// <summary> | ||
496 | /// Gets the full path of a directory. Populates the full path index with the directory's full path and all of its parent directorie's full paths. | ||
497 | /// </summary> | ||
498 | /// <param name="directory">The directory identifier.</param> | ||
499 | /// <param name="directoryDirectoryParentIndex">The Hashtable containing all the directory to directory parent mapping.</param> | ||
500 | /// <param name="directorySourceNameIndex">The Hashtable containing all the directory to source name mapping.</param> | ||
501 | /// <param name="directoryFullPathIndex">The Hashtable containing a mapping between all of the directories and their previously calculated full paths.</param> | ||
502 | /// <returns>The full path to the directory.</returns> | ||
503 | private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) | ||
504 | { | ||
505 | string parent = (string)directoryDirectoryParentIndex[directory]; | ||
506 | string sourceName = (string)directorySourceNameIndex[directory]; | ||
507 | |||
508 | string parentFullPath; | ||
509 | if (directoryFullPathIndex.ContainsKey(parent)) | ||
510 | { | ||
511 | parentFullPath = (string)directoryFullPathIndex[parent]; | ||
512 | } | ||
513 | else | ||
514 | { | ||
515 | parentFullPath = GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); | ||
516 | } | ||
517 | |||
518 | if (null == sourceName) | ||
519 | { | ||
520 | sourceName = String.Empty; | ||
521 | } | ||
522 | |||
523 | string fullPath = Path.Combine(parentFullPath, sourceName); | ||
524 | directoryFullPathIndex.Add(directory, fullPath); | ||
525 | |||
526 | return fullPath; | ||
527 | } | ||
528 | |||
529 | /// <summary> | ||
530 | /// Get the source name in an admin image. | ||
531 | /// </summary> | ||
532 | /// <param name="value">The Filename value.</param> | ||
533 | /// <returns>The source name of the directory in an admin image.</returns> | ||
534 | private static string GetAdminSourceName(string value) | ||
535 | { | ||
536 | string name = null; | ||
537 | string[] names; | ||
538 | string shortname = null; | ||
539 | string shortsourcename = null; | ||
540 | string sourcename = null; | ||
541 | |||
542 | names = Common.GetNames(value); | ||
543 | |||
544 | if (null != names[0] && "." != names[0]) | ||
545 | { | ||
546 | if (null != names[1]) | ||
547 | { | ||
548 | shortname = names[0]; | ||
549 | } | ||
550 | else | ||
551 | { | ||
552 | name = names[0]; | ||
553 | } | ||
554 | } | ||
555 | |||
556 | if (null != names[1]) | ||
557 | { | ||
558 | name = names[1]; | ||
559 | } | ||
560 | |||
561 | if (null != names[2]) | ||
562 | { | ||
563 | if (null != names[3]) | ||
564 | { | ||
565 | shortsourcename = names[2]; | ||
566 | } | ||
567 | else | ||
568 | { | ||
569 | sourcename = names[2]; | ||
570 | } | ||
571 | } | ||
572 | |||
573 | if (null != names[3]) | ||
574 | { | ||
575 | sourcename = names[3]; | ||
576 | } | ||
577 | |||
578 | if (null != sourcename) | ||
579 | { | ||
580 | return sourcename; | ||
581 | } | ||
582 | else if (null != shortsourcename) | ||
583 | { | ||
584 | return shortsourcename; | ||
585 | } | ||
586 | else if (null != name) | ||
587 | { | ||
588 | return name; | ||
589 | } | ||
590 | else | ||
591 | { | ||
592 | return shortname; | ||
593 | } | ||
594 | } | ||
595 | |||
596 | /// <summary> | ||
597 | /// Creates section ids on rows which form logical groupings of resources. | ||
598 | /// </summary> | ||
599 | /// <param name="output">The Output that represents the msi database.</param> | ||
600 | private void GenerateSectionIds(Output output) | ||
601 | { | ||
602 | // First assign and index section ids for the tables that are in their own sections. | ||
603 | AssignSectionIdsToTable(output.Tables["Binary"], 0); | ||
604 | Hashtable componentSectionIdIndex = AssignSectionIdsToTable(output.Tables["Component"], 0); | ||
605 | Hashtable customActionSectionIdIndex = AssignSectionIdsToTable(output.Tables["CustomAction"], 0); | ||
606 | AssignSectionIdsToTable(output.Tables["Directory"], 0); | ||
607 | Hashtable featureSectionIdIndex = AssignSectionIdsToTable(output.Tables["Feature"], 0); | ||
608 | AssignSectionIdsToTable(output.Tables["Icon"], 0); | ||
609 | Hashtable digitalCertificateSectionIdIndex = AssignSectionIdsToTable(output.Tables["MsiDigitalCertificate"], 0); | ||
610 | AssignSectionIdsToTable(output.Tables["Property"], 0); | ||
611 | |||
612 | // Now handle all the tables that rely on the first set of indexes but also produce their own indexes. Order matters here. | ||
613 | Hashtable fileSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["File"], componentSectionIdIndex, 1, 0); | ||
614 | Hashtable appIdSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Class"], componentSectionIdIndex, 2, 5); | ||
615 | Hashtable odbcDataSourceSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDataSource"], componentSectionIdIndex, 1, 0); | ||
616 | Hashtable odbcDriverSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ODBCDriver"], componentSectionIdIndex, 1, 0); | ||
617 | Hashtable registrySectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["Registry"], componentSectionIdIndex, 5, 0); | ||
618 | Hashtable serviceInstallSectionIdIndex = ConnectTableToSectionAndIndex(output.Tables["ServiceInstall"], componentSectionIdIndex, 11, 0); | ||
619 | |||
620 | // Now handle all the tables which only rely on previous indexes and order does not matter. | ||
621 | foreach (Table table in output.Tables) | ||
622 | { | ||
623 | switch (table.Name) | ||
624 | { | ||
625 | case "WixFile": | ||
626 | case "MsiFileHash": | ||
627 | ConnectTableToSection(table, fileSectionIdIndex, 0); | ||
628 | break; | ||
629 | case "MsiAssembly": | ||
630 | case "MsiAssemblyName": | ||
631 | ConnectTableToSection(table, componentSectionIdIndex, 0); | ||
632 | break; | ||
633 | case "MsiPackageCertificate": | ||
634 | case "MsiPatchCertificate": | ||
635 | ConnectTableToSection(table, digitalCertificateSectionIdIndex, 1); | ||
636 | break; | ||
637 | case "CreateFolder": | ||
638 | case "FeatureComponents": | ||
639 | case "MoveFile": | ||
640 | case "ReserveCost": | ||
641 | case "ODBCTranslator": | ||
642 | ConnectTableToSection(table, componentSectionIdIndex, 1); | ||
643 | break; | ||
644 | case "TypeLib": | ||
645 | ConnectTableToSection(table, componentSectionIdIndex, 2); | ||
646 | break; | ||
647 | case "Shortcut": | ||
648 | case "Environment": | ||
649 | ConnectTableToSection(table, componentSectionIdIndex, 3); | ||
650 | break; | ||
651 | case "RemoveRegistry": | ||
652 | ConnectTableToSection(table, componentSectionIdIndex, 4); | ||
653 | break; | ||
654 | case "ServiceControl": | ||
655 | ConnectTableToSection(table, componentSectionIdIndex, 5); | ||
656 | break; | ||
657 | case "IniFile": | ||
658 | case "RemoveIniFile": | ||
659 | ConnectTableToSection(table, componentSectionIdIndex, 7); | ||
660 | break; | ||
661 | case "AppId": | ||
662 | ConnectTableToSection(table, appIdSectionIdIndex, 0); | ||
663 | break; | ||
664 | case "Condition": | ||
665 | ConnectTableToSection(table, featureSectionIdIndex, 0); | ||
666 | break; | ||
667 | case "ODBCSourceAttribute": | ||
668 | ConnectTableToSection(table, odbcDataSourceSectionIdIndex, 0); | ||
669 | break; | ||
670 | case "ODBCAttribute": | ||
671 | ConnectTableToSection(table, odbcDriverSectionIdIndex, 0); | ||
672 | break; | ||
673 | case "AdminExecuteSequence": | ||
674 | case "AdminUISequence": | ||
675 | case "AdvtExecuteSequence": | ||
676 | case "AdvtUISequence": | ||
677 | case "InstallExecuteSequence": | ||
678 | case "InstallUISequence": | ||
679 | ConnectTableToSection(table, customActionSectionIdIndex, 0); | ||
680 | break; | ||
681 | case "LockPermissions": | ||
682 | case "MsiLockPermissions": | ||
683 | foreach (Row row in table.Rows) | ||
684 | { | ||
685 | string lockObject = (string)row[0]; | ||
686 | string tableName = (string)row[1]; | ||
687 | switch (tableName) | ||
688 | { | ||
689 | case "File": | ||
690 | row.SectionId = (string)fileSectionIdIndex[lockObject]; | ||
691 | break; | ||
692 | case "Registry": | ||
693 | row.SectionId = (string)registrySectionIdIndex[lockObject]; | ||
694 | break; | ||
695 | case "ServiceInstall": | ||
696 | row.SectionId = (string)serviceInstallSectionIdIndex[lockObject]; | ||
697 | break; | ||
698 | } | ||
699 | } | ||
700 | break; | ||
701 | } | ||
702 | } | ||
703 | |||
704 | // Now pass the output to each unbinder extension to allow them to analyze the output and determine thier proper section ids. | ||
705 | //foreach (IUnbinderExtension extension in this.unbinderExtensions) | ||
706 | //{ | ||
707 | // extension.GenerateSectionIds(output); | ||
708 | //} | ||
709 | } | ||
710 | |||
711 | /// <summary> | ||
712 | /// Creates new section ids on all the rows in a table. | ||
713 | /// </summary> | ||
714 | /// <param name="table">The table to add sections to.</param> | ||
715 | /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> | ||
716 | /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> | ||
717 | private Hashtable AssignSectionIdsToTable(Table table, int rowPrimaryKeyIndex) | ||
718 | { | ||
719 | Hashtable hashtable = new Hashtable(); | ||
720 | if (null != table) | ||
721 | { | ||
722 | foreach (Row row in table.Rows) | ||
723 | { | ||
724 | row.SectionId = GetNewSectionId(); | ||
725 | hashtable.Add(row[rowPrimaryKeyIndex], row.SectionId); | ||
726 | } | ||
727 | } | ||
728 | return hashtable; | ||
729 | } | ||
730 | |||
731 | /// <summary> | ||
732 | /// Connects a table's rows to an already sectioned table. | ||
733 | /// </summary> | ||
734 | /// <param name="table">The table containing rows that need to be connected to sections.</param> | ||
735 | /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> | ||
736 | /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> | ||
737 | private static void ConnectTableToSection(Table table, Hashtable sectionIdIndex, int rowIndex) | ||
738 | { | ||
739 | if (null != table) | ||
740 | { | ||
741 | foreach (Row row in table.Rows) | ||
742 | { | ||
743 | if (sectionIdIndex.ContainsKey(row[rowIndex])) | ||
744 | { | ||
745 | row.SectionId = (string)sectionIdIndex[row[rowIndex]]; | ||
746 | } | ||
747 | } | ||
748 | } | ||
749 | } | ||
750 | |||
751 | /// <summary> | ||
752 | /// Connects a table's rows to an already sectioned table and produces an index for other tables to connect to it. | ||
753 | /// </summary> | ||
754 | /// <param name="table">The table containing rows that need to be connected to sections.</param> | ||
755 | /// <param name="sectionIdIndex">A hashtable containing keys to map table to its section.</param> | ||
756 | /// <param name="rowIndex">The index of the column which is used as the foreign key in to the sectionIdIndex.</param> | ||
757 | /// <param name="rowPrimaryKeyIndex">The index of the column which is used by other tables to reference this table.</param> | ||
758 | /// <returns>A Hashtable containing the tables key for each row paired with its assigned section id.</returns> | ||
759 | private static Hashtable ConnectTableToSectionAndIndex(Table table, Hashtable sectionIdIndex, int rowIndex, int rowPrimaryKeyIndex) | ||
760 | { | ||
761 | Hashtable newHashTable = new Hashtable(); | ||
762 | if (null != table) | ||
763 | { | ||
764 | foreach (Row row in table.Rows) | ||
765 | { | ||
766 | if (!sectionIdIndex.ContainsKey(row[rowIndex])) | ||
767 | { | ||
768 | continue; | ||
769 | } | ||
770 | |||
771 | row.SectionId = (string)sectionIdIndex[row[rowIndex]]; | ||
772 | if (null != row[rowPrimaryKeyIndex]) | ||
773 | { | ||
774 | newHashTable.Add(row[rowPrimaryKeyIndex], row.SectionId); | ||
775 | } | ||
776 | } | ||
777 | } | ||
778 | return newHashTable; | ||
779 | } | ||
780 | |||
781 | /// <summary> | ||
782 | /// Creates a new section identifier to be used when adding a section to an output. | ||
783 | /// </summary> | ||
784 | /// <returns>A string representing a new section id.</returns> | ||
785 | private string GetNewSectionId() | ||
786 | { | ||
787 | this.SectionCount++; | ||
788 | return "wix.section." + this.SectionCount.ToString(CultureInfo.InvariantCulture); | ||
789 | } | ||
790 | } | ||
791 | } | ||