| 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.ComponentModel; |
| 9 | using System.Globalization; |
| 10 | using System.IO; |
| 11 | using System.Linq; |
| 12 | using System.Text.RegularExpressions; |
| 13 | using WixToolset.Core.Native.Msi; |
| 14 | using WixToolset.Data; |
| 15 | using WixToolset.Data.WindowsInstaller; |
| 16 | using WixToolset.Data.WindowsInstaller.Rows; |
| 17 | using WixToolset.Extensibility.Data; |
| 18 | using WixToolset.Extensibility.Services; |
| 19 | |
| 20 | internal class UnbindDatabaseCommand |
| 21 | { |
| 22 | private static readonly 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}"); |
| 23 | |
| 24 | public UnbindDatabaseCommand(IMessaging messaging, IBackendHelper backendHelper, IFileSystem fileSystem, IPathResolver pathResolver, string databasePath, Database database, OutputType outputType, string exportBasePath, string extractFilesFolder, string intermediateFolder, bool enableDemodularization, bool skipSummaryInfo) |
| 25 | { |
| 26 | this.Messaging = messaging; |
| 27 | this.BackendHelper = backendHelper; |
| 28 | this.FileSystem = fileSystem; |
| 29 | this.PathResolver = pathResolver; |
| 30 | this.DatabasePath = databasePath; |
| 31 | this.Database = database; |
| 32 | this.OutputType = outputType; |
| 33 | this.ExportBasePath = exportBasePath; |
| 34 | this.ExtractFilesFolder = extractFilesFolder; |
| 35 | this.IntermediateFolder = intermediateFolder; |
| 36 | this.EnableDemodularization = enableDemodularization; |
| 37 | this.SkipSummaryInfo = skipSummaryInfo; |
| 38 | |
| 39 | this.TableDefinitions = new TableDefinitionCollection(WindowsInstallerTableDefinitions.All); |
| 40 | } |
| 41 | |
| 42 | private IMessaging Messaging { get; } |
| 43 | |
| 44 | private IBackendHelper BackendHelper { get; } |
| 45 | |
| 46 | private IFileSystem FileSystem { get; } |
| 47 | |
| 48 | private IPathResolver PathResolver { get; } |
| 49 | |
| 50 | private Database Database { get; set; } |
| 51 | |
| 52 | private string DatabasePath { get; } |
| 53 | |
| 54 | private OutputType OutputType { get; } |
| 55 | |
| 56 | private string ExportBasePath { get; } |
| 57 | |
| 58 | private string ExtractFilesFolder { get; } |
| 59 | |
| 60 | private string IntermediateFolder { get; } |
| 61 | |
| 62 | private bool EnableDemodularization { get; } |
| 63 | |
| 64 | private bool SkipSummaryInfo { get; } |
| 65 | |
| 66 | private TableDefinitionCollection TableDefinitions { get; } |
| 67 | |
| 68 | public bool AdminImage { get; private set; } |
| 69 | |
| 70 | public WindowsInstallerData Data { get; private set; } |
| 71 | |
| 72 | public IEnumerable<string> ExportedFiles { get; private set; } |
| 73 | |
| 74 | public WindowsInstallerData Execute() |
| 75 | { |
| 76 | var adminImage = false; |
| 77 | var exportedFiles = new List<string>(); |
| 78 | |
| 79 | var data = new WindowsInstallerData(new SourceLineNumber(this.DatabasePath)) |
| 80 | { |
| 81 | Type = this.OutputType |
| 82 | }; |
| 83 | |
| 84 | Database database = null; |
| 85 | try |
| 86 | { |
| 87 | if (this.Database == null) |
| 88 | { |
| 89 | database = new Database(this.DatabasePath, OpenDatabase.ReadOnly); |
| 90 | this.Database = database; |
| 91 | } |
| 92 | |
| 93 | Directory.CreateDirectory(this.IntermediateFolder); |
| 94 | |
| 95 | data.Codepage = this.GetCodePage(); |
| 96 | |
| 97 | var modularizationGuid = this.ProcessTables(data, exportedFiles); |
| 98 | |
| 99 | var summaryInfo = this.ProcessSummaryInfo(data, modularizationGuid); |
| 100 | |
| 101 | this.UpdateUnrealFileColumns(this.DatabasePath, data, summaryInfo, exportedFiles); |
| 102 | } |
| 103 | catch (Win32Exception e) |
| 104 | { |
| 105 | if (0x6E == e.NativeErrorCode) // ERROR_OPEN_FAILED |
| 106 | { |
| 107 | throw new WixException(ErrorMessages.OpenDatabaseFailed(this.DatabasePath)); |
| 108 | } |
| 109 | |
| 110 | throw; |
| 111 | } |
| 112 | finally |
| 113 | { |
| 114 | database?.Dispose(); |
| 115 | } |
| 116 | |
| 117 | this.AdminImage = adminImage; |
| 118 | this.Data = data; |
| 119 | this.ExportedFiles = exportedFiles; |
| 120 | |
| 121 | return data; |
| 122 | } |
| 123 | |
| 124 | private int GetCodePage() |
| 125 | { |
| 126 | var codepage = 0; |
| 127 | |
| 128 | this.Database.Export("_ForceCodepage", this.IntermediateFolder, "_ForceCodepage.idt"); |
| 129 | |
| 130 | var lines = File.ReadAllLines(Path.Combine(this.IntermediateFolder, "_ForceCodepage.idt")); |
| 131 | |
| 132 | if (lines.Length == 3) |
| 133 | { |
| 134 | var data = lines[2].Split('\t'); |
| 135 | |
| 136 | if (2 == data.Length) |
| 137 | { |
| 138 | codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | return codepage; |
| 143 | } |
| 144 | |
| 145 | private string ProcessTables(WindowsInstallerData output, List<string> exportedFiles) |
| 146 | { |
| 147 | View validationView = null; |
| 148 | string modularizationGuid = null; |
| 149 | string modularizationSuffix = null; |
| 150 | |
| 151 | try |
| 152 | { |
| 153 | // open a view on the validation table if it exists |
| 154 | if (this.Database.TableExists("_Validation")) |
| 155 | { |
| 156 | validationView = this.Database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); |
| 157 | } |
| 158 | |
| 159 | // get the normal tables |
| 160 | using (var tablesView = this.Database.OpenExecuteView("SELECT * FROM _Tables")) |
| 161 | { |
| 162 | foreach (var tableRecord in tablesView.Records) |
| 163 | { |
| 164 | var tableName = tableRecord.GetString(1); |
| 165 | |
| 166 | using (var tableView = this.Database.OpenExecuteView($"SELECT * FROM `{tableName}`")) |
| 167 | { |
| 168 | var tableDefinition = this.GetTableDefinition(tableName, tableView, validationView); |
| 169 | var table = new Table(tableDefinition); |
| 170 | |
| 171 | foreach (var rowRecord in tableView.Records) |
| 172 | { |
| 173 | var recordCount = rowRecord.GetFieldCount(); |
| 174 | var row = table.CreateRow(output.SourceLineNumbers); |
| 175 | |
| 176 | for (var i = 0; recordCount > i && row.Fields.Length > i; i++) |
| 177 | { |
| 178 | if (rowRecord.IsNull(i + 1)) |
| 179 | { |
| 180 | if (!row.Fields[i].Column.Nullable) |
| 181 | { |
| 182 | // TODO: display an error for a null value in a non-nullable field OR |
| 183 | // display a warning and put an empty string in the value to let the compiler handle it |
| 184 | // (the second option is risky because the later code may make certain assumptions about |
| 185 | // the contents of a row value) |
| 186 | } |
| 187 | } |
| 188 | else |
| 189 | { |
| 190 | switch (row.Fields[i].Column.Type) |
| 191 | { |
| 192 | case ColumnType.Number: |
| 193 | var intValue = rowRecord.GetInteger(i + 1); |
| 194 | var success = row.Fields[i].Column.IsLocalizable ? row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)) : row.BestEffortSetField(i, intValue); |
| 195 | |
| 196 | if (!success) |
| 197 | { |
| 198 | this.Messaging.Write(WarningMessages.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); |
| 199 | } |
| 200 | break; |
| 201 | case ColumnType.Object: |
| 202 | var source = "FILE NOT EXPORTED"; |
| 203 | |
| 204 | if (null != this.ExportBasePath) |
| 205 | { |
| 206 | source = Path.Combine(this.ExportBasePath, tableName, row.GetPrimaryKey('.')); |
| 207 | |
| 208 | if (!String.IsNullOrEmpty(modularizationSuffix)) |
| 209 | { |
| 210 | source += modularizationSuffix; |
| 211 | } |
| 212 | |
| 213 | Directory.CreateDirectory(Path.Combine(this.ExportBasePath, tableName)); |
| 214 | |
| 215 | using (var fs = this.FileSystem.OpenFile(null, source, FileMode.Create, FileAccess.Write, FileShare.None)) |
| 216 | { |
| 217 | int bytesRead; |
| 218 | var buffer = new byte[4096]; |
| 219 | |
| 220 | while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) |
| 221 | { |
| 222 | fs.Write(buffer, 0, bytesRead); |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | exportedFiles.Add(source); |
| 227 | } |
| 228 | |
| 229 | row[i] = source; |
| 230 | break; |
| 231 | default: |
| 232 | var value = rowRecord.GetString(i + 1); |
| 233 | |
| 234 | switch (row.Fields[i].Column.Category) |
| 235 | { |
| 236 | case ColumnCategory.Guid: |
| 237 | value = value.ToUpperInvariant(); |
| 238 | break; |
| 239 | } |
| 240 | |
| 241 | // De-modularize |
| 242 | if (this.EnableDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) |
| 243 | { |
| 244 | if (null == modularizationGuid) |
| 245 | { |
| 246 | var match = Modularization.Match(value); |
| 247 | modularizationSuffix = match.Value; |
| 248 | |
| 249 | if (match.Success) |
| 250 | { |
| 251 | modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | value = Modularization.Replace(value, String.Empty); |
| 256 | } |
| 257 | |
| 258 | #if TODO_MOVE_TO_DECOMPILER |
| 259 | // escape "$(" for the preprocessor |
| 260 | value = value.Replace("$(", "$$("); |
| 261 | |
| 262 | // escape things that look like wix variables |
| 263 | // TODO: Evaluate this requirement. |
| 264 | //var matches = Common.WixVariableRegex.Matches(value); |
| 265 | //for (var j = matches.Count - 1; 0 <= j; j--) |
| 266 | //{ |
| 267 | // value = value.Insert(matches[j].Index, "!"); |
| 268 | //} |
| 269 | #endif |
| 270 | |
| 271 | row[i] = value; |
| 272 | break; |
| 273 | } |
| 274 | } |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | output.Tables.Add(table); |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | } |
| 283 | finally |
| 284 | { |
| 285 | validationView?.Close(); |
| 286 | } |
| 287 | |
| 288 | return modularizationGuid; |
| 289 | } |
| 290 | |
| 291 | private SummaryInformationBits ProcessSummaryInfo(WindowsInstallerData output, string modularizationGuid) |
| 292 | { |
| 293 | var result = new SummaryInformationBits(); |
| 294 | |
| 295 | if (!this.SkipSummaryInfo) |
| 296 | { |
| 297 | using (var summaryInformation = new SummaryInformation(this.Database)) |
| 298 | { |
| 299 | var table = new Table(this.TableDefinitions["_SummaryInformation"]); |
| 300 | |
| 301 | for (var i = 1; 19 >= i; i++) |
| 302 | { |
| 303 | var value = summaryInformation.GetProperty(i); |
| 304 | |
| 305 | // Set the modularization guid as the PackageCode, for merge modules. |
| 306 | if (i == (int)SummaryInformation.Package.PackageCode && !String.IsNullOrEmpty(modularizationGuid)) |
| 307 | { |
| 308 | var row = table.CreateRow(output.SourceLineNumbers); |
| 309 | row[0] = i; |
| 310 | row[1] = modularizationGuid; |
| 311 | } |
| 312 | else if (0 < value.Length) |
| 313 | { |
| 314 | var row = table.CreateRow(output.SourceLineNumbers); |
| 315 | row[0] = i; |
| 316 | row[1] = value; |
| 317 | |
| 318 | if (i == (int)SummaryInformation.Package.FileAndElevatedFlags) |
| 319 | { |
| 320 | var wordcount = Convert.ToInt32(value, CultureInfo.InvariantCulture); |
| 321 | result.LongFilenames = (wordcount & 0x1) != 0x1; |
| 322 | result.Compressed = (wordcount & 0x2) == 0x2; |
| 323 | result.AdminImage = (wordcount & 0x4) == 0x4; |
| 324 | } |
| 325 | } |
| 326 | } |
| 327 | |
| 328 | output.Tables.Add(table); |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | return result; |
| 333 | } |
| 334 | |
| 335 | private TableDefinition GetTableDefinition(string tableName, View tableView, View validationView) |
| 336 | { |
| 337 | // Use our table definitions whenever possible since they will be used when compiling the source code anyway. |
| 338 | // This also allows us to take advantage of WiX concepts like localizable columns which current code assumes. |
| 339 | if (this.TableDefinitions.Contains(tableName)) |
| 340 | { |
| 341 | return this.TableDefinitions[tableName]; |
| 342 | } |
| 343 | |
| 344 | ColumnDefinition[] columns; |
| 345 | using (Record columnNameRecord = tableView.GetColumnNames(), |
| 346 | columnTypeRecord = tableView.GetColumnTypes()) |
| 347 | { |
| 348 | // index the primary keys |
| 349 | var tablePrimaryKeys = new HashSet<string>(); |
| 350 | using (var primaryKeysRecord = this.Database.PrimaryKeys(tableName)) |
| 351 | { |
| 352 | var primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); |
| 353 | |
| 354 | for (var i = 1; i <= primaryKeysFieldCount; i++) |
| 355 | { |
| 356 | tablePrimaryKeys.Add(primaryKeysRecord.GetString(i)); |
| 357 | } |
| 358 | } |
| 359 | |
| 360 | var columnCount = columnNameRecord.GetFieldCount(); |
| 361 | columns = new ColumnDefinition[columnCount]; |
| 362 | for (var i = 1; i <= columnCount; i++) |
| 363 | { |
| 364 | var columnName = columnNameRecord.GetString(i); |
| 365 | var idtType = columnTypeRecord.GetString(i); |
| 366 | |
| 367 | var columnCategory = ColumnCategory.Unknown; |
| 368 | var columnModularizeType = ColumnModularizeType.None; |
| 369 | var primary = tablePrimaryKeys.Contains(columnName); |
| 370 | int? minValue = null; |
| 371 | int? maxValue = null; |
| 372 | string keyTable = null; |
| 373 | int? keyColumn = null; |
| 374 | string category = null; |
| 375 | string set = null; |
| 376 | string description = null; |
| 377 | |
| 378 | // get the column type, length, and whether its nullable |
| 379 | ColumnType columnType; |
| 380 | switch (Char.ToLowerInvariant(idtType[0])) |
| 381 | { |
| 382 | case 'i': |
| 383 | columnType = ColumnType.Number; |
| 384 | break; |
| 385 | case 'l': |
| 386 | columnType = ColumnType.Localized; |
| 387 | break; |
| 388 | case 's': |
| 389 | columnType = ColumnType.String; |
| 390 | break; |
| 391 | case 'v': |
| 392 | columnType = ColumnType.Object; |
| 393 | break; |
| 394 | default: |
| 395 | // TODO: error |
| 396 | columnType = ColumnType.Unknown; |
| 397 | break; |
| 398 | } |
| 399 | var length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); |
| 400 | var nullable = Char.IsUpper(idtType[0]); |
| 401 | |
| 402 | // try to get validation information |
| 403 | if (null != validationView) |
| 404 | { |
| 405 | using (var validationRecord = new Record(2)) |
| 406 | { |
| 407 | validationRecord.SetString(1, tableName); |
| 408 | validationRecord.SetString(2, columnName); |
| 409 | |
| 410 | validationView.Execute(validationRecord); |
| 411 | } |
| 412 | |
| 413 | using (var validationRecord = validationView.Fetch()) |
| 414 | { |
| 415 | if (null != validationRecord) |
| 416 | { |
| 417 | var validationNullable = validationRecord.GetString(3); |
| 418 | minValue = validationRecord.IsNull(4) ? null : (int?)validationRecord.GetInteger(4); |
| 419 | maxValue = validationRecord.IsNull(5) ? null : (int?)validationRecord.GetInteger(5); |
| 420 | keyTable = validationRecord.IsNull(6) ? null : validationRecord.GetString(6); |
| 421 | keyColumn = validationRecord.IsNull(7) ? null : (int?)validationRecord.GetInteger(7); |
| 422 | category = validationRecord.IsNull(8) ? null : validationRecord.GetString(8); |
| 423 | set = validationRecord.IsNull(9) ? null : validationRecord.GetString(9); |
| 424 | description = validationRecord.IsNull(10) ? null : validationRecord.GetString(10); |
| 425 | |
| 426 | // check the validation nullable value against the column definition |
| 427 | if (null == validationNullable) |
| 428 | { |
| 429 | // TODO: warn for illegal validation nullable column |
| 430 | } |
| 431 | else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) |
| 432 | { |
| 433 | // TODO: warn for mismatch between column definition and validation nullable |
| 434 | } |
| 435 | |
| 436 | // convert category to ColumnCategory |
| 437 | if (null != category) |
| 438 | { |
| 439 | if (!Enum.TryParse(category, true, out columnCategory)) |
| 440 | { |
| 441 | columnCategory = ColumnCategory.Unknown; |
| 442 | } |
| 443 | } |
| 444 | } |
| 445 | else |
| 446 | { |
| 447 | // TODO: warn about no validation information |
| 448 | } |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | // guess the modularization type |
| 453 | if ("Icon" == keyTable && 1 == keyColumn) |
| 454 | { |
| 455 | columnModularizeType = ColumnModularizeType.Icon; |
| 456 | } |
| 457 | else if ("Condition" == columnName) |
| 458 | { |
| 459 | columnModularizeType = ColumnModularizeType.Condition; |
| 460 | } |
| 461 | else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) |
| 462 | { |
| 463 | columnModularizeType = ColumnModularizeType.Property; |
| 464 | } |
| 465 | else if (ColumnCategory.Identifier == columnCategory) |
| 466 | { |
| 467 | columnModularizeType = ColumnModularizeType.Column; |
| 468 | } |
| 469 | |
| 470 | columns[i - 1] = new ColumnDefinition(columnName, columnType, length, primary, nullable, columnCategory, minValue, maxValue, keyTable, keyColumn, set, description, columnModularizeType, (ColumnType.Localized == columnType), true); |
| 471 | } |
| 472 | } |
| 473 | |
| 474 | return new TableDefinition(tableName, null, columns, false); |
| 475 | } |
| 476 | |
| 477 | private void UpdateUnrealFileColumns(string databaseFile, WindowsInstallerData output, SummaryInformationBits summaryInformation, List<string> exportedFiles) |
| 478 | { |
| 479 | var fileRows = output.Tables["File"]?.Rows; |
| 480 | |
| 481 | if (fileRows == null || fileRows.Count == 0) |
| 482 | { |
| 483 | return; |
| 484 | } |
| 485 | |
| 486 | this.UpdateFileRowsDiskId(output, fileRows); |
| 487 | |
| 488 | this.UpdateFileRowsSource(databaseFile, output, fileRows, summaryInformation, exportedFiles); |
| 489 | } |
| 490 | |
| 491 | private void UpdateFileRowsDiskId(WindowsInstallerData output, IList<Row> fileRows) |
| 492 | { |
| 493 | var mediaRows = output.Tables["Media"]?.Rows?.Cast<MediaRow>()?.OrderBy(r => r.LastSequence)?.ToList(); |
| 494 | |
| 495 | var lastMediaRowIndex = 0; |
| 496 | var lastMediaRow = (mediaRows == null || mediaRows.Count == 0) ? null : mediaRows[lastMediaRowIndex]; |
| 497 | |
| 498 | foreach (var fileRow in fileRows.Cast<FileRow>()?.OrderBy(r => r.Sequence)) |
| 499 | { |
| 500 | while (lastMediaRow != null && fileRow.Sequence > lastMediaRow.LastSequence) |
| 501 | { |
| 502 | ++lastMediaRowIndex; |
| 503 | |
| 504 | lastMediaRow = lastMediaRowIndex < mediaRows.Count ? mediaRows[lastMediaRowIndex] : null; |
| 505 | } |
| 506 | |
| 507 | fileRow.DiskId = lastMediaRow?.DiskId ?? 1; |
| 508 | } |
| 509 | } |
| 510 | |
| 511 | private void UpdateFileRowsSource(string databasePath, WindowsInstallerData output, IList<Row> fileRows, SummaryInformationBits summaryInformation, List<string> exportedFiles) |
| 512 | { |
| 513 | var databaseFolder = Path.GetDirectoryName(databasePath); |
| 514 | |
| 515 | var componentDirectoryIndex = output.Tables["Component"].Rows.Cast<ComponentRow>().ToDictionary(r => r.Component, r => r.Directory); |
| 516 | |
| 517 | // Index full source paths for all directories |
| 518 | var directories = new Dictionary<string, IResolvedDirectory>(); |
| 519 | |
| 520 | var directoryTable = output.Tables["Directory"]; |
| 521 | foreach (var row in directoryTable.Rows) |
| 522 | { |
| 523 | var sourceName = this.BackendHelper.GetMsiFileName(row.FieldAsString(2), source: true, longName: summaryInformation.LongFilenames); |
| 524 | var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(row.FieldAsString(1), sourceName); |
| 525 | |
| 526 | directories.Add(row.FieldAsString(0), resolvedDirectory); |
| 527 | } |
| 528 | |
| 529 | if (summaryInformation.AdminImage) |
| 530 | { |
| 531 | foreach (var fileRow in fileRows.Cast<FileRow>()) |
| 532 | { |
| 533 | var directoryId = componentDirectoryIndex[fileRow.Component]; |
| 534 | var relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, directoryId, fileRow.FileName, compressed: false, useLongName: summaryInformation.LongFilenames); |
| 535 | |
| 536 | fileRow.Source = Path.Combine(databaseFolder, relativeFileLayoutPath); |
| 537 | } |
| 538 | } |
| 539 | else |
| 540 | { |
| 541 | var extractedFileIds = new HashSet<string>(); |
| 542 | |
| 543 | if (!String.IsNullOrEmpty(this.ExtractFilesFolder)) |
| 544 | { |
| 545 | var extractCommand = new ExtractCabinetsCommand(this.FileSystem, output, this.Database, this.DatabasePath, this.ExtractFilesFolder, this.IntermediateFolder); |
| 546 | extractCommand.Execute(); |
| 547 | |
| 548 | extractedFileIds = new HashSet<string>(extractCommand.ExtractedFileIdsWithMediaRow.Keys, StringComparer.OrdinalIgnoreCase); |
| 549 | exportedFiles.AddRange(extractedFileIds); |
| 550 | } |
| 551 | |
| 552 | foreach (var fileRow in fileRows.Cast<FileRow>()) |
| 553 | { |
| 554 | var source = "FILE NOT EXPORTED"; |
| 555 | |
| 556 | if (fileRow.Compressed == YesNoType.Yes || (fileRow.Compressed == YesNoType.NotSet && summaryInformation.Compressed)) |
| 557 | { |
| 558 | if (extractedFileIds.Contains(fileRow.File)) |
| 559 | { |
| 560 | source = Path.Combine(this.ExtractFilesFolder, fileRow.File); |
| 561 | } |
| 562 | } |
| 563 | else if (componentDirectoryIndex.TryGetValue(fileRow.Component, out var directoryId)) // this can happen when unbinding an invalid MSI file or MST file with select table modifications. |
| 564 | { |
| 565 | var relativeFileLayoutPath = this.PathResolver.GetFileSourcePath(directories, directoryId, fileRow.FileName, compressed: false, useLongName: summaryInformation.LongFilenames); |
| 566 | |
| 567 | source = Path.Combine(databaseFolder, relativeFileLayoutPath); |
| 568 | } |
| 569 | |
| 570 | fileRow.Source = source; |
| 571 | } |
| 572 | } |
| 573 | } |
| 574 | |
| 575 | /// <summary> |
| 576 | /// 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. |
| 577 | /// </summary> |
| 578 | /// <param name="directory">The directory identifier.</param> |
| 579 | /// <param name="directoryDirectoryParentIndex">The Hashtable containing all the directory to directory parent mapping.</param> |
| 580 | /// <param name="directorySourceNameIndex">The Hashtable containing all the directory to source name mapping.</param> |
| 581 | /// <param name="directoryFullPathIndex">The Hashtable containing a mapping between all of the directories and their previously calculated full paths.</param> |
| 582 | /// <returns>The full path to the directory.</returns> |
| 583 | private string GetAdminFullPath(string directory, Hashtable directoryDirectoryParentIndex, Hashtable directorySourceNameIndex, Hashtable directoryFullPathIndex) |
| 584 | { |
| 585 | var parent = (string)directoryDirectoryParentIndex[directory]; |
| 586 | var sourceName = (string)directorySourceNameIndex[directory]; |
| 587 | |
| 588 | string parentFullPath; |
| 589 | if (directoryFullPathIndex.ContainsKey(parent)) |
| 590 | { |
| 591 | parentFullPath = (string)directoryFullPathIndex[parent]; |
| 592 | } |
| 593 | else |
| 594 | { |
| 595 | parentFullPath = this.GetAdminFullPath(parent, directoryDirectoryParentIndex, directorySourceNameIndex, directoryFullPathIndex); |
| 596 | } |
| 597 | |
| 598 | if (null == sourceName) |
| 599 | { |
| 600 | sourceName = String.Empty; |
| 601 | } |
| 602 | |
| 603 | var fullPath = Path.Combine(parentFullPath, sourceName); |
| 604 | directoryFullPathIndex.Add(directory, fullPath); |
| 605 | |
| 606 | return fullPath; |
| 607 | } |
| 608 | |
| 609 | /// <summary> |
| 610 | /// Get the source name in an admin image. |
| 611 | /// </summary> |
| 612 | /// <param name="value">The Filename value.</param> |
| 613 | /// <returns>The source name of the directory in an admin image.</returns> |
| 614 | private string GetAdminSourceName(string value) |
| 615 | { |
| 616 | string name = null; |
| 617 | string[] names; |
| 618 | string shortname = null; |
| 619 | string shortsourcename = null; |
| 620 | string sourcename = null; |
| 621 | |
| 622 | names = this.BackendHelper.SplitMsiFileName(value); |
| 623 | |
| 624 | if (null != names[0] && "." != names[0]) |
| 625 | { |
| 626 | if (null != names[1]) |
| 627 | { |
| 628 | shortname = names[0]; |
| 629 | } |
| 630 | else |
| 631 | { |
| 632 | name = names[0]; |
| 633 | } |
| 634 | } |
| 635 | |
| 636 | if (null != names[1]) |
| 637 | { |
| 638 | name = names[1]; |
| 639 | } |
| 640 | |
| 641 | if (null != names[2]) |
| 642 | { |
| 643 | if (null != names[3]) |
| 644 | { |
| 645 | shortsourcename = names[2]; |
| 646 | } |
| 647 | else |
| 648 | { |
| 649 | sourcename = names[2]; |
| 650 | } |
| 651 | } |
| 652 | |
| 653 | if (null != names[3]) |
| 654 | { |
| 655 | sourcename = names[3]; |
| 656 | } |
| 657 | |
| 658 | if (null != sourcename) |
| 659 | { |
| 660 | return sourcename; |
| 661 | } |
| 662 | else if (null != shortsourcename) |
| 663 | { |
| 664 | return shortsourcename; |
| 665 | } |
| 666 | else if (null != name) |
| 667 | { |
| 668 | return name; |
| 669 | } |
| 670 | else |
| 671 | { |
| 672 | return shortname; |
| 673 | } |
| 674 | } |
| 675 | |
| 676 | private class SummaryInformationBits |
| 677 | { |
| 678 | public bool AdminImage { get; set; } |
| 679 | |
| 680 | public bool Compressed { get; set; } |
| 681 | |
| 682 | public bool LongFilenames { get; set; } |
| 683 | } |
| 684 | } |
| 685 | } |