aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Unbind/UnbindDatabaseCommand.cs791
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
3namespace 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}