diff options
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 | } | ||
