main
cs 7,610 lines 343 KB
Raw
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.Decompile
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using System.Text.RegularExpressions;
12 using System.Xml.Linq;
13 using WixToolset.Data;
14 using WixToolset.Data.Symbols;
15 using WixToolset.Data.WindowsInstaller;
16 using WixToolset.Data.WindowsInstaller.Rows;
17 using WixToolset.Extensibility;
18 using WixToolset.Extensibility.Services;
19
20 /// <summary>
21 /// Decompiles an msi database into WiX source.
22 /// </summary>
23 internal class Decompiler
24 {
25 private static readonly Regex NullSplitter = new Regex(@"\[~]");
26
27 // NameToBit arrays
28 private static readonly string[] TextControlAttributes = { "Transparent", "NoPrefix", "NoWrap", "FormatSize", "UserLanguage" };
29 private static readonly string[] HyperlinkControlAttributes = { "Transparent" };
30 private static readonly string[] EditControlAttributes = { "Multiline", null, null, null, null, "Password" };
31 private static readonly string[] ProgressControlAttributes = { "ProgressBlocks" };
32 private static readonly string[] VolumeControlAttributes = { "Removable", "Fixed", "Remote", "CDROM", "RAMDisk", "Floppy", "ShowRollbackCost" };
33 private static readonly string[] ListboxControlAttributes = { "Sorted", null, null, null, "UserLanguage" };
34 private static readonly string[] ListviewControlAttributes = { "Sorted", null, null, null, "FixedSize", "Icon16", "Icon32" };
35 private static readonly string[] ComboboxControlAttributes = { "Sorted", "ComboList", null, null, "UserLanguage" };
36 private static readonly string[] RadioControlAttributes = { "Image", "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", null, "HasBorder" };
37 private static readonly string[] ButtonControlAttributes = { "Image", null, "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32", "ElevationShield" };
38 private static readonly string[] IconControlAttributes = { "Image", null, null, null, "FixedSize", "Icon16", "Icon32" };
39 private static readonly string[] BitmapControlAttributes = { "Image", null, null, null, "FixedSize" };
40 private static readonly string[] CheckboxControlAttributes = { null, "PushLike", "Bitmap", "Icon", "FixedSize", "Icon16", "Icon32" };
41 private XElement uiElement;
42
43 /// <summary>
44 /// Creates a new decompiler object with a default set of table definitions.
45 /// </summary>
46 public Decompiler(IMessaging messaging, IBackendHelper backendHelper, IWindowsInstallerDecompilerHelper decompilerHelper, IEnumerable<IWindowsInstallerDecompilerExtension> extensions, IEnumerable<IExtensionData> extensionData, ISymbolDefinitionCreator creator, string baseSourcePath, bool suppressCustomTables, bool suppressDroppingEmptyTables, bool suppressRelativeActionSequencing, bool suppressUI, bool keepModularizationIds)
47 {
48 this.Messaging = messaging;
49 this.BackendHelper = backendHelper;
50 this.DecompilerHelper = decompilerHelper;
51 this.Extensions = extensions;
52 this.ExtensionData = extensionData;
53 this.SymbolDefinitionCreator = creator;
54 this.BaseSourcePath = baseSourcePath ?? "SourceDir";
55 this.SuppressCustomTables = suppressCustomTables;
56 this.SuppressDroppingEmptyTables = suppressDroppingEmptyTables;
57 this.SuppressRelativeActionSequencing = suppressRelativeActionSequencing;
58 this.SuppressUI = suppressUI;
59 this.KeepModularizationIds = keepModularizationIds;
60
61 this.ExtensionsByTableName = new Dictionary<string, IWindowsInstallerDecompilerExtension>();
62 this.StandardActions = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id);
63
64 this.TableDefinitions = new TableDefinitionCollection();
65 }
66
67 private IMessaging Messaging { get; }
68
69 private IBackendHelper BackendHelper { get; }
70
71 private IWindowsInstallerDecompilerHelper DecompilerHelper { get; }
72
73 private IEnumerable<IWindowsInstallerDecompilerExtension> Extensions { get; }
74
75 private IEnumerable<IExtensionData> ExtensionData { get; }
76
77 private ISymbolDefinitionCreator SymbolDefinitionCreator { get; }
78
79 private Dictionary<string, IWindowsInstallerDecompilerExtension> ExtensionsByTableName { get; }
80
81 private string BaseSourcePath { get; }
82
83 private bool SuppressCustomTables { get; }
84
85 private bool SuppressDroppingEmptyTables { get; }
86
87 private bool SuppressRelativeActionSequencing { get; }
88
89 private bool SuppressUI { get; }
90
91 private bool KeepModularizationIds { get; }
92
93 private OutputType OutputType { get; set; }
94
95 private Dictionary<string, WixActionSymbol> StandardActions { get; }
96
97 private bool Compressed { get; set; }
98
99 private TableDefinitionCollection TableDefinitions { get; }
100
101 private bool ShortNames { get; set; }
102
103 private string ModularizationGuid { get; set; }
104
105 private XElement UIElement
106 {
107 get
108 {
109 if (null == this.uiElement)
110 {
111 this.uiElement = this.DecompilerHelper.AddElementToRoot(new XElement(Names.UIElement));
112 }
113
114 return this.uiElement;
115 }
116 }
117
118 private Dictionary<string, XElement> Singletons { get; } = new Dictionary<string, XElement>();
119
120 private Dictionary<string, XElement> PatchTargetFiles { get; } = new Dictionary<string, XElement>();
121
122 /// <summary>
123 /// Decompile the database file.
124 /// </summary>
125 /// <param name="output">The output to decompile.</param>
126 /// <returns>The serialized WiX source code.</returns>
127 public XDocument Decompile(WindowsInstallerData output)
128 {
129 this.OutputType = output.Type;
130
131 switch (this.OutputType)
132 {
133 case OutputType.Module:
134 this.DecompilerHelper.RootElement = new XElement(Names.ModuleElement);
135 break;
136 case OutputType.PatchCreation:
137 this.DecompilerHelper.RootElement = new XElement(Names.PatchCreationElement);
138 break;
139 case OutputType.Package:
140 this.DecompilerHelper.RootElement = new XElement(Names.PackageElement);
141 break;
142 default:
143 throw new InvalidOperationException("Unknown output type.");
144 }
145
146 // collect the table definitions from the output
147 this.TableDefinitions.Clear();
148 foreach (var table in output.Tables)
149 {
150 this.TableDefinitions.Add(table.Definition);
151 }
152
153 // add any missing standard and wix-specific table definitions
154 foreach (var tableDefinition in WindowsInstallerTableDefinitions.All)
155 {
156 if (!this.TableDefinitions.Contains(tableDefinition.Name))
157 {
158 this.TableDefinitions.Add(tableDefinition);
159 }
160 }
161
162 // add any missing extension table definitions
163 foreach (var extension in this.Extensions)
164 {
165 this.AddExtensionTableDefinitions(extension);
166 }
167
168 // try to decompile the database file
169 // stop processing if an error previously occurred
170 if (this.Messaging.EncounteredError)
171 {
172 return null;
173 }
174
175 this.InitializeDecompile(output.Tables, output.Codepage);
176
177 // stop processing if an error previously occurred
178 if (this.Messaging.EncounteredError)
179 {
180 return null;
181 }
182
183 // decompile the tables
184 this.DecompileTables(output);
185
186 // finalize the decompiler and its extensions
187 this.FinalizeDecompile(output.Tables);
188
189 // return the XML document only if decompilation completed successfully
190 return this.Messaging.EncounteredError ? null : new XDocument(new XElement(Names.WixElement, this.DecompilerHelper.RootElement));
191 }
192
193 private void AddExtensionTableDefinitions(IWindowsInstallerDecompilerExtension extension)
194 {
195 if (null != extension.TableDefinitions)
196 {
197 foreach (var tableDefinition in extension.TableDefinitions)
198 {
199 if (!this.ExtensionsByTableName.ContainsKey(tableDefinition.Name))
200 {
201 this.ExtensionsByTableName.Add(tableDefinition.Name, extension);
202 }
203 else
204 {
205 this.Messaging.Write(ErrorMessages.DuplicateExtensionTable(extension.GetType().ToString(), tableDefinition.Name));
206 }
207 }
208 }
209 }
210
211 internal static Platform? GetPlatformFromTemplateSummaryInformation(string[] template)
212 {
213 if (null != template && 1 < template.Length && null != template[0] && 0 < template[0].Length)
214 {
215 switch (template[0])
216 {
217 case "Intel":
218 return Platform.X86;
219 case "x64":
220 return Platform.X64;
221 case "Arm64":
222 return Platform.ARM64;
223 }
224 }
225
226 return null;
227 }
228
229 private Dictionary<string, List<XElement>> IndexTableOneToMany(IEnumerable<Row> rows, int column = 0)
230 {
231 return rows
232 .ToLookup(row => row.FieldAsString(column), row => this.DecompilerHelper.GetIndexedElement(row))
233 .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
234 }
235
236 private Dictionary<string, List<XElement>> IndexTableOneToMany(TableIndexedCollection tables, string tableName, int column = 0)
237 {
238 return this.IndexTableOneToMany(tables[tableName]?.Rows ?? Enumerable.Empty<Row>(), column);
239 }
240
241 private Dictionary<string, List<XElement>> IndexTableOneToMany(Table table, int column = 0)
242 {
243 return this.IndexTableOneToMany(table?.Rows ?? Enumerable.Empty<Row>(), column);
244 }
245
246 private void AddChildToParent(string parentName, XElement xChild, Row row, int column)
247 {
248 var key = row.FieldAsString(column);
249 if (this.DecompilerHelper.TryGetIndexedElement(parentName, key, out var xParent))
250 {
251 xParent.Add(xChild);
252 }
253 else
254 {
255 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row.Fields[column].Column.Name, key, parentName));
256 }
257 }
258
259 private static XAttribute XAttributeIfNotNull(string attributeName, string value)
260 {
261 return value is null ? null : new XAttribute(attributeName, value);
262 }
263
264 private static XAttribute XAttributeIfNotNull(string attributeName, Row row, int column)
265 {
266 return row.IsColumnNull(column) ? null : new XAttribute(attributeName, row.FieldAsString(column));
267 }
268
269 private static void SetAttributeIfNotNull(XElement xElement, string attributeName, string value)
270 {
271 if (!String.IsNullOrEmpty(value))
272 {
273 xElement.SetAttributeValue(attributeName, value);
274 }
275 }
276
277 private static void SetAttributeIfNotNull(XElement xElement, string attributeName, int? value)
278 {
279 if (value.HasValue)
280 {
281 xElement.SetAttributeValue(attributeName, value);
282 }
283 }
284
285 /// <summary>
286 /// Convert an Int32 into a DateTime.
287 /// </summary>
288 /// <param name="value">The Int32 value.</param>
289 /// <returns>The DateTime.</returns>
290 private static DateTime ConvertIntegerToDateTime(int value)
291 {
292 var date = value / 65536;
293 var time = value % 65536;
294
295 return new DateTime(1980 + (date / 512), (date % 512) / 32, date % 32, time / 2048, (time % 2048) / 32, (time % 32) * 2);
296 }
297
298 /// <summary>
299 /// Set the common control attributes in a control element.
300 /// </summary>
301 /// <param name="attributes">The control attributes.</param>
302 /// <param name="xControl">The control element.</param>
303 private static void SetControlAttributes(int attributes, XElement xControl)
304 {
305 if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesEnabled))
306 {
307 xControl.SetAttributeValue("Disabled", "yes");
308 }
309
310 if (WindowsInstallerConstants.MsidbControlAttributesIndirect == (attributes & WindowsInstallerConstants.MsidbControlAttributesIndirect))
311 {
312 xControl.SetAttributeValue("Indirect", "yes");
313 }
314
315 if (WindowsInstallerConstants.MsidbControlAttributesInteger == (attributes & WindowsInstallerConstants.MsidbControlAttributesInteger))
316 {
317 xControl.SetAttributeValue("Integer", "yes");
318 }
319
320 if (WindowsInstallerConstants.MsidbControlAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbControlAttributesLeftScroll))
321 {
322 xControl.SetAttributeValue("LeftScroll", "yes");
323 }
324
325 if (WindowsInstallerConstants.MsidbControlAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbControlAttributesRightAligned))
326 {
327 xControl.SetAttributeValue("RightAligned", "yes");
328 }
329
330 if (WindowsInstallerConstants.MsidbControlAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbControlAttributesRTLRO))
331 {
332 xControl.SetAttributeValue("RightToLeft", "yes");
333 }
334
335 if (WindowsInstallerConstants.MsidbControlAttributesSunken == (attributes & WindowsInstallerConstants.MsidbControlAttributesSunken))
336 {
337 xControl.SetAttributeValue("Sunken", "yes");
338 }
339
340 if (0 == (attributes & WindowsInstallerConstants.MsidbControlAttributesVisible))
341 {
342 xControl.SetAttributeValue("Hidden", "yes");
343 }
344 }
345
346 /// <summary>
347 /// Creates an action element.
348 /// </summary>
349 /// <param name="actionSymbol">The action from which the element should be created.</param>
350 private void CreateActionElement(WixActionSymbol actionSymbol)
351 {
352 XElement xAction;
353
354 if (this.DecompilerHelper.TryGetIndexedElement("CustomAction", actionSymbol.Action, out var _)) // custom action
355 {
356 xAction = new XElement(Names.CustomElement,
357 new XAttribute("Action", actionSymbol.Action),
358 String.IsNullOrEmpty(actionSymbol.Condition) ? null : new XAttribute("Condition", actionSymbol.Condition));
359
360 AssignActionSequence(actionSymbol, xAction);
361 }
362 else if (this.DecompilerHelper.TryGetIndexedElement("Dialog", actionSymbol.Action, out var _)) // dialog
363 {
364 xAction = new XElement(Names.ShowElement,
365 new XAttribute("Dialog", actionSymbol.Action),
366 XAttributeIfNotNull("Condition", actionSymbol.Condition));
367
368 AssignActionSequence(actionSymbol, xAction);
369 }
370 else // possibly a standard action without suggested sequence information
371 {
372 xAction = this.CreateStandardActionElement(actionSymbol);
373 }
374
375 // add the action element to the appropriate sequence element
376 if (null != xAction)
377 {
378 var sequenceTable = actionSymbol.SequenceTable.ToString();
379 if (!this.Singletons.TryGetValue(sequenceTable, out var xSequence))
380 {
381 xSequence = new XElement(Names.WxsNamespace + sequenceTable);
382
383 this.DecompilerHelper.AddElementToRoot(xSequence);
384 this.Singletons.Add(sequenceTable, xSequence);
385 }
386
387 try
388 {
389 xSequence.Add(xAction);
390 }
391 catch (ArgumentException) // action/dialog is not valid for this sequence
392 {
393 this.Messaging.Write(WarningMessages.IllegalActionInSequence(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
394 }
395 }
396 }
397
398 /// <summary>
399 /// Creates a standard action element.
400 /// </summary>
401 /// <param name="actionSymbol">The action row from which the element should be created.</param>
402 /// <returns>The created element.</returns>
403 private XElement CreateStandardActionElement(WixActionSymbol actionSymbol)
404 {
405 XElement xStandardAction = null;
406
407 switch (actionSymbol.Action)
408 {
409 case "AllocateRegistrySpace":
410 case "BindImage":
411 case "CostFinalize":
412 case "CostInitialize":
413 case "CreateFolders":
414 case "CreateShortcuts":
415 case "DeleteServices":
416 case "DuplicateFiles":
417 case "ExecuteAction":
418 case "FileCost":
419 case "InstallAdminPackage":
420 case "InstallFiles":
421 case "InstallFinalize":
422 case "InstallInitialize":
423 case "InstallODBC":
424 case "InstallServices":
425 case "InstallValidate":
426 case "IsolateComponents":
427 case "MigrateFeatureStates":
428 case "MoveFiles":
429 case "MsiPublishAssemblies":
430 case "MsiUnpublishAssemblies":
431 case "PatchFiles":
432 case "ProcessComponents":
433 case "PublishComponents":
434 case "PublishFeatures":
435 case "PublishProduct":
436 case "RegisterClassInfo":
437 case "RegisterComPlus":
438 case "RegisterExtensionInfo":
439 case "RegisterFonts":
440 case "RegisterMIMEInfo":
441 case "RegisterProduct":
442 case "RegisterProgIdInfo":
443 case "RegisterTypeLibraries":
444 case "RegisterUser":
445 case "RemoveDuplicateFiles":
446 case "RemoveEnvironmentStrings":
447 case "RemoveFiles":
448 case "RemoveFolders":
449 case "RemoveIniValues":
450 case "RemoveODBC":
451 case "RemoveRegistryValues":
452 case "RemoveShortcuts":
453 case "SelfRegModules":
454 case "SelfUnregModules":
455 case "SetODBCFolders":
456 case "StartServices":
457 case "StopServices":
458 case "UnpublishComponents":
459 case "UnpublishFeatures":
460 case "UnregisterClassInfo":
461 case "UnregisterComPlus":
462 case "UnregisterExtensionInfo":
463 case "UnregisterFonts":
464 case "UnregisterMIMEInfo":
465 case "UnregisterProgIdInfo":
466 case "UnregisterTypeLibraries":
467 case "ValidateProductID":
468 case "WriteEnvironmentStrings":
469 case "WriteIniValues":
470 case "WriteRegistryValues":
471 xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action);
472 break;
473
474 case "AppSearch":
475 this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var appSearchActionRow);
476
477 if (null != actionSymbol.Before || null != actionSymbol.After || (null != appSearchActionRow && actionSymbol.Sequence != appSearchActionRow.Sequence))
478 {
479 xStandardAction = new XElement(Names.AppSearchElement);
480
481 SetAttributeIfNotNull(xStandardAction, "Condition", actionSymbol.Condition);
482 SetAttributeIfNotNull(xStandardAction, "Before", actionSymbol.Before);
483 SetAttributeIfNotNull(xStandardAction, "After", actionSymbol.After);
484 SetAttributeIfNotNull(xStandardAction, "Sequence", actionSymbol.Sequence);
485
486 return xStandardAction;
487 }
488 break;
489
490 case "CCPSearch":
491 case "DisableRollback":
492 case "FindRelatedProducts":
493 case "ForceReboot":
494 case "InstallExecute":
495 case "InstallExecuteAgain":
496 case "LaunchConditions":
497 case "RemoveExistingProducts":
498 case "ResolveSource":
499 case "RMCCPSearch":
500 case "ScheduleReboot":
501 xStandardAction = new XElement(Names.WxsNamespace + actionSymbol.Action);
502 Decompiler.SequenceRelativeAction(actionSymbol, xStandardAction);
503 return xStandardAction;
504
505 default:
506 this.Messaging.Write(WarningMessages.UnknownAction(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
507 return null;
508 }
509
510 if (xStandardAction != null)
511 {
512 this.SequenceStandardAction(actionSymbol, xStandardAction);
513 }
514
515 return xStandardAction;
516 }
517
518 /// <summary>
519 /// Applies the condition and sequence to a standard action element based on the action symbol data.
520 /// </summary>
521 /// <param name="actionSymbol">Action data from the database.</param>
522 /// <param name="xAction">Element to be sequenced.</param>
523 private void SequenceStandardAction(WixActionSymbol actionSymbol, XElement xAction)
524 {
525 xAction.SetAttributeValue("Condition", actionSymbol.Condition);
526
527 if ((null != actionSymbol.Before || null != actionSymbol.After) && 0 == actionSymbol.Sequence)
528 {
529 this.Messaging.Write(WarningMessages.DecompiledStandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
530 }
531 else if (actionSymbol.Sequence.HasValue)
532 {
533 xAction.SetAttributeValue("Sequence", actionSymbol.Sequence.Value);
534 }
535 }
536
537 /// <summary>
538 /// Applies the condition and relative sequence to an action element based on the action row data.
539 /// </summary>
540 /// <param name="actionSymbol">Action data from the database.</param>
541 /// <param name="xAction">Element to be sequenced.</param>
542 private static void SequenceRelativeAction(WixActionSymbol actionSymbol, XElement xAction)
543 {
544 SetAttributeIfNotNull(xAction, "Condition", actionSymbol.Condition);
545 SetAttributeIfNotNull(xAction, "Before", actionSymbol.Before);
546 SetAttributeIfNotNull(xAction, "After", actionSymbol.After);
547 SetAttributeIfNotNull(xAction, "Sequence", actionSymbol.Sequence);
548 }
549
550 /// <summary>
551 /// Ensure that a particular property exists in the decompiled output.
552 /// </summary>
553 /// <param name="id">The identifier of the property.</param>
554 /// <returns>The property element.</returns>
555 private XElement EnsureProperty(string id)
556 {
557 if (!this.DecompilerHelper.TryGetIndexedElement("Property", id, out var xProperty))
558 {
559 xProperty = new XElement(Names.PropertyElement, new XAttribute("Id", id));
560
561 this.DecompilerHelper.AddElementToRoot(xProperty);
562 this.DecompilerHelper.IndexElement("Property", id, xProperty);
563 }
564
565 return xProperty;
566 }
567
568 /// <summary>
569 /// Finalize decompilation.
570 /// </summary>
571 /// <param name="tables">The collection of all tables.</param>
572 private void FinalizeDecompile(TableIndexedCollection tables)
573 {
574 if (OutputType.PatchCreation == this.OutputType)
575 {
576 this.FinalizeFamilyFileRangesTable(tables);
577 }
578 else
579 {
580 this.FinalizeSummaryInformationStream(tables);
581 this.FinalizeCheckBoxTable(tables);
582 this.FinalizeComponentTable(tables);
583 this.FinalizeDialogTable(tables);
584 this.FinalizeDuplicateMoveFileTables(tables);
585 this.FinalizeFeatureComponentsTable(tables);
586 this.FinalizeFileTable(tables);
587 this.FinalizeMIMETable(tables);
588 this.FinalizeMsiLockPermissionsExTable(tables);
589 this.FinalizeLockPermissionsTable(tables);
590 this.FinalizeProgIdTable(tables);
591 this.FinalizePropertyTable(tables);
592 this.FinalizeRemoveFileTable(tables);
593 this.FinalizeSearchTables(tables);
594 this.FinalizeShortcutTable(tables);
595 this.FinalizeUpgradeTable(tables);
596 this.FinalizeSequenceTables(tables);
597 this.FinalizeVerbTable(tables);
598 }
599
600 foreach (var extension in this.Extensions)
601 {
602 extension.PostDecompileTables(tables);
603 }
604 }
605
606 /// <summary>
607 /// Finalize the CheckBox table.
608 /// </summary>
609 /// <param name="tables">The collection of all tables.</param>
610 /// <remarks>
611 /// Enumerates through all the Control rows, looking for controls of type "CheckBox" with
612 /// a value in the Property column. This is then possibly matched up with a CheckBox row
613 /// to retrieve a CheckBoxValue. There is no foreign key from the Control to CheckBox table.
614 /// </remarks>
615 private void FinalizeCheckBoxTable(TableIndexedCollection tables)
616 {
617 // if the user has requested to suppress the UI elements, we have nothing to do
618 if (this.SuppressUI)
619 {
620 return;
621 }
622
623 var checkBoxTable = tables["CheckBox"];
624 var controlTable = tables["Control"];
625
626 var checkBoxes = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
627 var checkBoxProperties = checkBoxTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), row => false);
628
629 // enumerate through the Control table, adding CheckBox values where appropriate
630 if (null != controlTable)
631 {
632 foreach (var row in controlTable.Rows)
633 {
634 var xControl = this.DecompilerHelper.GetIndexedElement(row);
635
636 if ("CheckBox" == row.FieldAsString(2))
637 {
638 var property = row.FieldAsString(8);
639 if (!String.IsNullOrEmpty(property) && checkBoxes.TryGetValue(property, out var checkBoxRow))
640 {
641 // if we've seen this property already, create a reference to it
642 if (checkBoxProperties.TryGetValue(property, out var seen) && seen)
643 {
644 xControl.SetAttributeValue("CheckBoxPropertyRef", property);
645 }
646 else
647 {
648 xControl.SetAttributeValue("Property", property);
649 checkBoxProperties[property] = true;
650 }
651
652 xControl.SetAttributeValue("CheckBoxValue", checkBoxRow.FieldAsString(1));
653 }
654 else
655 {
656 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Control", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Property", row.FieldAsString(8), "CheckBox"));
657 }
658 }
659 }
660 }
661 }
662
663 /// <summary>
664 /// Finalize the Component table.
665 /// </summary>
666 /// <param name="tables">The collection of all tables.</param>
667 /// <remarks>
668 /// Set the keypaths for each component.
669 /// </remarks>
670 private void FinalizeComponentTable(TableIndexedCollection tables)
671 {
672 var componentTable = tables["Component"];
673 var fileTable = tables["File"];
674 var odbcDataSourceTable = tables["ODBCDataSource"];
675 var registryTable = tables["Registry"];
676
677 // set the component keypaths
678 if (null != componentTable)
679 {
680 // Add the TARGETDIR StandardDirectory if a component is directly parented there.
681 if (componentTable.Rows.Any(row => row.FieldAsString(2) == "TARGETDIR")
682 && this.DecompilerHelper.TryGetIndexedElement("Directory", "TARGETDIR", out var xDirectory))
683 {
684 this.DecompilerHelper.AddElementToRoot(xDirectory);
685 }
686
687 foreach (var row in componentTable.Rows)
688 {
689 var attributes = row.FieldAsInteger(3);
690 var keyPath = row.FieldAsString(5);
691
692 if (String.IsNullOrEmpty(keyPath))
693 {
694 var xComponent = this.DecompilerHelper.GetIndexedElement("Component", row.FieldAsString(0));
695 xComponent.SetAttributeValue("KeyPath", "yes");
696 }
697 else if (WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath == (attributes & WindowsInstallerConstants.MsidbComponentAttributesRegistryKeyPath))
698 {
699 if (this.DecompilerHelper.TryGetIndexedElement("Registry", keyPath, out var xRegistry))
700 {
701 if (xRegistry.Name.LocalName == "RegistryValue")
702 {
703 xRegistry.SetAttributeValue("KeyPath", "yes");
704 }
705 else
706 {
707 this.Messaging.Write(WarningMessages.IllegalRegistryKeyPath(row.SourceLineNumbers, "Component", keyPath));
708 }
709 }
710 else
711 {
712 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "Registry"));
713 }
714 }
715 else if (WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource == (attributes & WindowsInstallerConstants.MsidbComponentAttributesODBCDataSource))
716 {
717 if (this.DecompilerHelper.TryGetIndexedElement("ODBCDataSource", keyPath, out var xOdbcDataSource))
718 {
719 xOdbcDataSource.SetAttributeValue("KeyPath", "yes");
720 }
721 else
722 {
723 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "ODBCDataSource"));
724 }
725 }
726 else
727 {
728 if (this.DecompilerHelper.TryGetIndexedElement("File", keyPath, out var xFile))
729 {
730 xFile.SetAttributeValue("KeyPath", "yes");
731 }
732 else
733 {
734 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Component", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "KeyPath", keyPath, "File"));
735 }
736 }
737 }
738 }
739
740 // add the File children elements
741 if (null != fileTable)
742 {
743 foreach (FileRow fileRow in fileTable.Rows)
744 {
745 if (this.DecompilerHelper.TryGetIndexedElement("Component", fileRow.Component, out var xComponent)
746 && this.DecompilerHelper.TryGetIndexedElement(fileRow, out var xFile))
747 {
748 xComponent.Add(xFile);
749 }
750 else
751 {
752 this.Messaging.Write(WarningMessages.ExpectedForeignRow(fileRow.SourceLineNumbers, "File", fileRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", fileRow.Component, "Component"));
753 }
754 }
755 }
756
757 // add the ODBCDataSource children elements
758 if (null != odbcDataSourceTable)
759 {
760 foreach (var row in odbcDataSourceTable.Rows)
761 {
762 if (this.DecompilerHelper.TryGetIndexedElement("Component", row.FieldAsString(1), out var xComponent)
763 && this.DecompilerHelper.TryGetIndexedElement(row, out var xOdbcDataSource))
764 {
765 xComponent.Add(xOdbcDataSource);
766 }
767 else
768 {
769 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ODBCDataSource", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component"));
770 }
771 }
772 }
773
774 // add the Registry children elements
775 if (null != registryTable)
776 {
777 foreach (var row in registryTable.Rows)
778 {
779 if (this.DecompilerHelper.TryGetIndexedElement("Component", row.FieldAsString(5), out var xComponent)
780 && this.DecompilerHelper.TryGetIndexedElement(row, out var xRegistry))
781 {
782 xComponent.Add(xRegistry);
783 }
784 else
785 {
786 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Registry", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(5), "Component"));
787 }
788 }
789 }
790 }
791
792 /// <summary>
793 /// Finalize the Dialog table.
794 /// </summary>
795 /// <param name="tables">The collection of all tables.</param>
796 /// <remarks>
797 /// Sets the first, default, and cancel control for each dialog and adds all child control
798 /// elements to the dialog.
799 /// </remarks>
800 private void FinalizeDialogTable(TableIndexedCollection tables)
801 {
802 // if the user has requested to suppress the UI elements, we have nothing to do
803 if (this.SuppressUI)
804 {
805 return;
806 }
807
808 var addedControls = new HashSet<XElement>();
809
810 var controlTable = tables["Control"];
811 var controlRows = controlTable?.Rows.ToDictionary(row => row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter));
812
813 var dialogTable = tables["Dialog"];
814 if (null != dialogTable)
815 {
816 foreach (var dialogRow in dialogTable.Rows)
817 {
818 var xDialog = this.DecompilerHelper.GetIndexedElement(dialogRow);
819 var dialogId = dialogRow.FieldAsString(0);
820
821 if (!this.DecompilerHelper.TryGetIndexedElement("Control", dialogId, dialogRow.FieldAsString(7), out var xControl))
822 {
823 this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_First", dialogRow.FieldAsString(7), "Control"));
824 }
825
826 // add tabbable controls
827 while (null != xControl)
828 {
829 var controlId = xControl.Attribute("Id").Value;
830 var controlRow = controlRows[String.Concat(dialogId, DecompilerConstants.PrimaryKeyDelimiter, controlId)];
831
832 xControl.SetAttributeValue("TabSkip", "no");
833
834 xDialog.Add(xControl);
835 addedControls.Add(xControl);
836
837 var controlNext = controlRow.FieldAsString(10);
838 if (!String.IsNullOrEmpty(controlNext))
839 {
840 if (this.DecompilerHelper.TryGetIndexedElement("Control", dialogId, controlNext, out xControl))
841 {
842 // looped back to the first control in the dialog
843 if (addedControls.Contains(xControl))
844 {
845 xControl = null;
846 }
847 }
848 else
849 {
850 this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Control_Next", controlNext, "Control"));
851 }
852 }
853 else
854 {
855 xControl = null;
856 }
857 }
858
859 // set default control
860 var controlDefault = dialogRow.FieldAsString(8);
861 if (!String.IsNullOrEmpty(controlDefault))
862 {
863 if (this.DecompilerHelper.TryGetIndexedElement("Control", dialogId, controlDefault, out var xDefaultControl))
864 {
865 xDefaultControl.SetAttributeValue("Default", "yes");
866 }
867 else
868 {
869 this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Default", Convert.ToString(dialogRow[8]), "Control"));
870 }
871 }
872
873 // set cancel control
874 var controlCancel = dialogRow.FieldAsString(9);
875 if (!String.IsNullOrEmpty(controlCancel))
876 {
877 if (this.DecompilerHelper.TryGetIndexedElement("Control", dialogId, controlCancel, out var xCancelControl))
878 {
879 xCancelControl.SetAttributeValue("Cancel", "yes");
880 }
881 else
882 {
883 this.Messaging.Write(WarningMessages.ExpectedForeignRow(dialogRow.SourceLineNumbers, "Dialog", dialogRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog", dialogId, "Control_Cancel", Convert.ToString(dialogRow[9]), "Control"));
884 }
885 }
886 }
887 }
888
889 // add the non-tabbable controls to the dialog
890 if (null != controlTable)
891 {
892 foreach (var controlRow in controlTable.Rows)
893 {
894 var dialogId = controlRow.FieldAsString(0);
895 if (!this.DecompilerHelper.TryGetIndexedElement("Dialog", dialogId, out var xDialog))
896 {
897 this.Messaging.Write(WarningMessages.ExpectedForeignRow(controlRow.SourceLineNumbers, "Control", controlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", dialogId, "Dialog"));
898 continue;
899 }
900
901 var xControl = this.DecompilerHelper.GetIndexedElement(controlRow);
902 if (!addedControls.Contains(xControl))
903 {
904 xControl.SetAttributeValue("TabSkip", "yes");
905 xDialog.Add(xControl);
906 }
907 }
908 }
909 }
910
911 /// <summary>
912 /// Finalize the DuplicateFile and MoveFile tables.
913 /// </summary>
914 /// <param name="tables">The collection of all tables.</param>
915 /// <remarks>
916 /// Sets the source/destination property/directory for each DuplicateFile or
917 /// MoveFile row.
918 /// </remarks>
919 private void FinalizeDuplicateMoveFileTables(TableIndexedCollection tables)
920 {
921 var duplicateFileTable = tables["DuplicateFile"];
922 if (null != duplicateFileTable)
923 {
924 foreach (var row in duplicateFileTable.Rows)
925 {
926 var xCopyFile = this.DecompilerHelper.GetIndexedElement(row);
927 var destination = row.FieldAsString(4);
928 if (!String.IsNullOrEmpty(destination))
929 {
930 if (this.DecompilerHelper.TryGetIndexedElement("Directory", destination, out var _))
931 {
932 xCopyFile.SetAttributeValue("DestinationDirectory", destination);
933 }
934 else
935 {
936 xCopyFile.SetAttributeValue("DestinationProperty", destination);
937 }
938 }
939 }
940 }
941
942 var moveFileTable = tables["MoveFile"];
943 if (null != moveFileTable)
944 {
945 foreach (var row in moveFileTable.Rows)
946 {
947 var xCopyFile = this.DecompilerHelper.GetIndexedElement(row);
948 var source = row.FieldAsString(4);
949 if (!String.IsNullOrEmpty(source))
950 {
951 if (this.DecompilerHelper.TryGetIndexedElement("Directory", source, out var _))
952 {
953 xCopyFile.SetAttributeValue("SourceDirectory", source);
954 }
955 else
956 {
957 xCopyFile.SetAttributeValue("SourceProperty", source);
958 }
959 }
960
961 var destination = row.FieldAsString(5);
962 if (this.DecompilerHelper.TryGetIndexedElement("Directory", destination, out var _))
963 {
964 xCopyFile.SetAttributeValue("DestinationDirectory", destination);
965 }
966 else
967 {
968 xCopyFile.SetAttributeValue("DestinationProperty", destination);
969 }
970 }
971 }
972 }
973
974 /// <summary>
975 /// Finalize the FamilyFileRanges table.
976 /// </summary>
977 /// <param name="tables">The collection of all tables.</param>
978 private void FinalizeFamilyFileRangesTable(TableIndexedCollection tables)
979 {
980 var familyFileRangesTable = tables["FamilyFileRanges"];
981 if (null != familyFileRangesTable)
982 {
983 foreach (var row in familyFileRangesTable.Rows)
984 {
985 var xProtectRange = new XElement(Names.ProtectRangeElement);
986
987 if (!row.IsColumnNull(2) && !row.IsColumnNull(3))
988 {
989 var retainOffsets = row.FieldAsString(2).Split(',');
990 var retainLengths = row.FieldAsString(3).Split(',');
991
992 if (retainOffsets.Length == retainLengths.Length)
993 {
994 for (var i = 0; i < retainOffsets.Length; i++)
995 {
996 if (retainOffsets[i].StartsWith("0x", StringComparison.Ordinal))
997 {
998 xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i].Substring(2), 16));
999 }
1000 else
1001 {
1002 xProtectRange.SetAttributeValue("Offset", Convert.ToInt32(retainOffsets[i], CultureInfo.InvariantCulture));
1003 }
1004
1005 if (retainLengths[i].StartsWith("0x", StringComparison.Ordinal))
1006 {
1007 xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i].Substring(2), 16));
1008 }
1009 else
1010 {
1011 xProtectRange.SetAttributeValue("Length", Convert.ToInt32(retainLengths[i], CultureInfo.InvariantCulture));
1012 }
1013 }
1014 }
1015 else
1016 {
1017 // TODO: warn
1018 }
1019 }
1020 else if (!row.IsColumnNull(2) || !row.IsColumnNull(3))
1021 {
1022 // TODO: warn about mismatch between columns
1023 }
1024
1025 this.DecompilerHelper.IndexElement(row, xProtectRange);
1026 }
1027 }
1028
1029 var usedProtectRanges = new HashSet<XElement>();
1030 var externalFilesTable = tables["ExternalFiles"];
1031 if (null != externalFilesTable)
1032 {
1033 foreach (var row in externalFilesTable.Rows)
1034 {
1035 if (this.DecompilerHelper.TryGetIndexedElement(row, out var xExternalFile)
1036 && this.DecompilerHelper.TryGetIndexedElement("FamilyFileRanges", row.FieldAsString(0), row.FieldAsString(0), out var xProtectRange))
1037 {
1038 xExternalFile.Add(xProtectRange);
1039 usedProtectRanges.Add(xProtectRange);
1040 }
1041 }
1042 }
1043
1044 var targetFiles_OptionalDataTable = tables["TargetFiles_OptionalData"];
1045 if (null != targetFiles_OptionalDataTable)
1046 {
1047 var targetImagesTable = tables["TargetImages"];
1048 var targetImageRows = targetImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0));
1049
1050 var upgradedImagesTable = tables["UpgradedImages"];
1051 var upgradedImagesRows = upgradedImagesTable?.Rows.ToDictionary(row => row.FieldAsString(0));
1052
1053 foreach (var row in targetFiles_OptionalDataTable.Rows)
1054 {
1055 var xTargetFile = this.PatchTargetFiles[row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter)];
1056
1057 if (!targetImageRows.TryGetValue(row.FieldAsString(0), out var targetImageRow))
1058 {
1059 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, targetFiles_OptionalDataTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Target", row.FieldAsString(0), "TargetImages"));
1060 continue;
1061 }
1062
1063 if (!upgradedImagesRows.TryGetValue(row.FieldAsString(3), out var upgradedImagesRow))
1064 {
1065 this.Messaging.Write(WarningMessages.ExpectedForeignRow(targetImageRow.SourceLineNumbers, targetImageRow.Table.Name, targetImageRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Upgraded", row.FieldAsString(3), "UpgradedImages"));
1066 continue;
1067 }
1068
1069 if (this.DecompilerHelper.TryGetIndexedElement("FamilyFileRanges", upgradedImagesRow.FieldAsString(4), row.FieldAsString(1), out var xProtectRange))
1070 {
1071 xTargetFile.Add(xProtectRange);
1072 usedProtectRanges.Add(xProtectRange);
1073 }
1074 }
1075 }
1076
1077 if (null != familyFileRangesTable)
1078 {
1079 foreach (var row in familyFileRangesTable.Rows)
1080 {
1081 var xProtectRange = this.DecompilerHelper.GetIndexedElement(row);
1082
1083 if (!usedProtectRanges.Contains(xProtectRange))
1084 {
1085 var xProtectFile = new XElement(Names.ProtectFileElement, new XAttribute("File", row.FieldAsString(1)));
1086 xProtectFile.Add(xProtectRange);
1087
1088 this.AddChildToParent("ImageFamilies", xProtectFile, row, 0);
1089 }
1090 }
1091 }
1092 }
1093
1094 /// <summary>
1095 /// Finalize the FeatureComponents table.
1096 /// </summary>
1097 /// <param name="tables">The collection of all tables.</param>
1098 /// <remarks>
1099 /// Since tables specifying references to the FeatureComponents table have references to
1100 /// the Feature and Component table separately, but not the FeatureComponents table specifically,
1101 /// the FeatureComponents table and primary features must be decompiled during finalization.
1102 /// </remarks>
1103 private void FinalizeFeatureComponentsTable(TableIndexedCollection tables)
1104 {
1105 var classTable = tables["Class"];
1106 if (null != classTable)
1107 {
1108 foreach (var row in classTable.Rows)
1109 {
1110 this.SetPrimaryFeature(row, 11, 2);
1111 }
1112 }
1113
1114 var extensionTable = tables["Extension"];
1115 if (null != extensionTable)
1116 {
1117 foreach (var row in extensionTable.Rows)
1118 {
1119 this.SetPrimaryFeature(row, 4, 1);
1120 }
1121 }
1122
1123 var msiAssemblyTable = tables["MsiAssembly"];
1124 if (null != msiAssemblyTable)
1125 {
1126 foreach (var row in msiAssemblyTable.Rows)
1127 {
1128 this.SetPrimaryFeature(row, 1, 0);
1129 }
1130 }
1131
1132 var publishComponentTable = tables["PublishComponent"];
1133 if (null != publishComponentTable)
1134 {
1135 foreach (var row in publishComponentTable.Rows)
1136 {
1137 this.SetPrimaryFeature(row, 4, 2);
1138 }
1139 }
1140
1141 var typeLibTable = tables["TypeLib"];
1142 if (null != typeLibTable)
1143 {
1144 foreach (var row in typeLibTable.Rows)
1145 {
1146 this.SetPrimaryFeature(row, 6, 2);
1147 }
1148 }
1149 }
1150
1151 /// <summary>
1152 /// Finalize the File table.
1153 /// </summary>
1154 /// <param name="tables">The collection of all tables.</param>
1155 /// <remarks>
1156 /// Sets the source, diskId, and assembly information for each file.
1157 /// </remarks>
1158 private void FinalizeFileTable(TableIndexedCollection tables)
1159 {
1160 // index the media table by media id
1161 var mediaTable = tables["Media"];
1162 var mediaRows = new RowDictionary<MediaRow>(mediaTable);
1163
1164 // set the disk identifiers and sources for files
1165 foreach (var fileRow in tables["File"]?.Rows.Cast<FileRow>() ?? Enumerable.Empty<FileRow>())
1166 {
1167 var xFile = this.DecompilerHelper.GetIndexedElement("File", fileRow.File);
1168
1169 // Don't bother processing files that are orphaned (and won't show up in the output anyway)
1170 if (null != xFile.Parent)
1171 {
1172 // set the diskid
1173 if (null != mediaTable)
1174 {
1175 foreach (MediaRow mediaRow in mediaTable.Rows)
1176 {
1177 if (fileRow.Sequence <= mediaRow.LastSequence && mediaRow.DiskId != 1)
1178 {
1179 xFile.SetAttributeValue("DiskId", mediaRow.DiskId);
1180 break;
1181 }
1182 }
1183 }
1184
1185 var fileId = xFile?.Attribute("Id")?.Value;
1186 var fileCompressed = xFile?.Attribute("Compressed")?.Value;
1187 var fileShortName = xFile?.Attribute("ShortName")?.Value;
1188 var fileName = xFile?.Attribute("Name")?.Value;
1189
1190 // set the source (done here because it requires information from the Directory table)
1191 if (OutputType.Module == this.OutputType && !this.KeepModularizationIds)
1192 {
1193 xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId, '.', this.ModularizationGuid.Substring(1, 36).Replace('-', '_')));
1194 }
1195 else if (fileCompressed == "yes" || (fileCompressed != "no" && this.Compressed) || OutputType.Module == this.OutputType)
1196 {
1197 xFile.SetAttributeValue("Source", String.Concat(this.BaseSourcePath, Path.DirectorySeparatorChar, "File", Path.DirectorySeparatorChar, fileId));
1198 }
1199 else // uncompressed
1200 {
1201 var name = (!this.ShortNames && !String.IsNullOrEmpty(fileName)) ? fileName : fileShortName ?? fileName;
1202
1203 if (this.Compressed) // uncompressed at the root of the source image
1204 {
1205 xFile.SetAttributeValue("Source", String.Concat("SourceDir", Path.DirectorySeparatorChar, name));
1206 }
1207 else
1208 {
1209 var sourcePath = this.GetSourcePath(xFile);
1210 xFile.SetAttributeValue("Source", Path.Combine(sourcePath, name));
1211 }
1212 }
1213 }
1214 }
1215
1216 // set the file assemblies and manifests
1217 foreach (var row in tables["MsiAssembly"]?.Rows ?? Enumerable.Empty<Row>())
1218 {
1219 if (this.DecompilerHelper.TryGetIndexedElement("Component", row.FieldAsString(0), out var xComponent))
1220 {
1221 foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes"))
1222 {
1223 xFile.SetAttributeValue("AssemblyManifest", row.FieldAsString(2));
1224 xFile.SetAttributeValue("AssemblyApplication", row.FieldAsString(3));
1225 xFile.SetAttributeValue("Assembly", row.FieldAsInteger(4) == 0 ? ".net" : "win32");
1226 }
1227 }
1228 else
1229 {
1230 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MsiAssembly", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component"));
1231 }
1232 }
1233
1234 // nest the TypeLib elements
1235 foreach (var row in tables["TypeLib"]?.Rows ?? Enumerable.Empty<Row>())
1236 {
1237 var xComponent = this.DecompilerHelper.GetIndexedElement("Component", row.FieldAsString(2));
1238 var xTypeLib = this.DecompilerHelper.GetIndexedElement(row);
1239
1240 foreach (var xFile in xComponent.Elements(Names.FileElement).Where(x => x.Attribute("KeyPath")?.Value == "yes"))
1241 {
1242 xFile.Add(xTypeLib);
1243 }
1244 }
1245 }
1246
1247 /// <summary>
1248 /// Finalize the MIME table.
1249 /// </summary>
1250 /// <param name="tables">The collection of all tables.</param>
1251 /// <remarks>
1252 /// There is a foreign key shared between the MIME and Extension
1253 /// tables so either one would be valid to be decompiled first, so
1254 /// the only safe way to nest the MIME elements is to do it during finalize.
1255 /// </remarks>
1256 private void FinalizeMIMETable(TableIndexedCollection tables)
1257 {
1258 var extensionRows = tables["Extension"]?.Rows ?? Enumerable.Empty<Row>();
1259 foreach (var row in extensionRows)
1260 {
1261 // set the default MIME element for this extension
1262 var mimeRef = row.FieldAsString(3);
1263 if (null != mimeRef)
1264 {
1265 if (this.DecompilerHelper.TryGetIndexedElement("MIME", mimeRef, out var xMime))
1266 {
1267 xMime.SetAttributeValue("Default", "yes");
1268 }
1269 else
1270 {
1271 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME"));
1272 }
1273 }
1274 }
1275
1276 var extensionsByExtensionId = this.IndexTableOneToMany(extensionRows);
1277
1278 foreach (var row in tables["MIME"]?.Rows ?? Enumerable.Empty<Row>())
1279 {
1280 var xMime = this.DecompilerHelper.GetIndexedElement(row);
1281
1282 if (extensionsByExtensionId.TryGetValue(row.FieldAsString(1), out var xExtensions))
1283 {
1284 foreach (var extension in xExtensions)
1285 {
1286 extension.Add(xMime);
1287 }
1288 }
1289 else
1290 {
1291 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "MIME", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(1), "Extension"));
1292 }
1293 }
1294 }
1295
1296 /// <summary>
1297 /// Finalize the ProgId table.
1298 /// </summary>
1299 /// <param name="tables">The collection of all tables.</param>
1300 /// <remarks>
1301 /// Enumerates through all the Class rows, looking for child ProgIds (these are the
1302 /// default ProgIds for a given Class). Then go through the ProgId table and add any
1303 /// remaining ProgIds for each Class. This happens during finalize because there is
1304 /// a circular dependency between the Class and ProgId tables.
1305 /// </remarks>
1306 private void FinalizeProgIdTable(TableIndexedCollection tables)
1307 {
1308 // add the default ProgIds for each class (and index the class table)
1309 var classRows = tables["Class"]?.Rows?.Where(row => row.FieldAsString(3) != null) ?? Enumerable.Empty<Row>();
1310
1311 var classesByCLSID = this.IndexTableOneToMany(classRows);
1312
1313 var addedProgIds = new Dictionary<XElement, string>();
1314
1315 foreach (var row in classRows)
1316 {
1317 var clsid = row.FieldAsString(0);
1318 var xClass = this.DecompilerHelper.GetIndexedElement(row);
1319
1320 if (this.DecompilerHelper.TryGetIndexedElement("ProgId", row.FieldAsString(3), out var xProgId))
1321 {
1322 if (addedProgIds.TryGetValue(xProgId, out var progid))
1323 {
1324 this.Messaging.Write(WarningMessages.TooManyProgIds(row.SourceLineNumbers, row.FieldAsString(0), row.FieldAsString(3), progid));
1325 }
1326 else
1327 {
1328 xClass.Add(xProgId);
1329 addedProgIds.Add(xProgId, clsid);
1330 }
1331 }
1332 else
1333 {
1334 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Class", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "ProgId_Default", row.FieldAsString(3), "ProgId"));
1335 }
1336 }
1337
1338 // add the remaining non-default ProgId entries for each class
1339 foreach (var row in tables["ProgId"]?.Rows ?? Enumerable.Empty<Row>())
1340 {
1341 var clsid = row.FieldAsString(2);
1342 var xProgId = this.DecompilerHelper.GetIndexedElement(row);
1343
1344 if (!addedProgIds.ContainsKey(xProgId) && null != clsid && null == xProgId.Parent)
1345 {
1346 if (classesByCLSID.TryGetValue(clsid, out var xClasses))
1347 {
1348 foreach (var xClass in xClasses)
1349 {
1350 xClass.Add(xProgId);
1351 addedProgIds.Add(xProgId, clsid);
1352 }
1353 }
1354 else
1355 {
1356 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "ProgId", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Class_", row.FieldAsString(2), "Class"));
1357 }
1358 }
1359 }
1360
1361 // Check for any progIds that are not hooked up to a class and hook them up to the component specified by the extension
1362 var componentsById = this.IndexTableOneToMany(tables, "Component");
1363
1364 foreach (var row in tables["Extension"]?.Rows?.Where(row => row.FieldAsString(2) != null) ?? Enumerable.Empty<Row>())
1365 {
1366 var xProgId = this.DecompilerHelper.GetIndexedElement("ProgId", row.FieldAsString(2));
1367
1368 // Haven't added the progId yet and it doesn't have a parent progId
1369 if (!addedProgIds.ContainsKey(xProgId) && null == xProgId.Parent)
1370 {
1371 if (componentsById.TryGetValue(row.FieldAsString(1), out var xComponents))
1372 {
1373 foreach (var xComponent in xComponents)
1374 {
1375 xComponent.Add(xProgId);
1376 }
1377 }
1378 else
1379 {
1380 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "Extension", row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(1), "Component"));
1381 }
1382 }
1383 }
1384 }
1385
1386 /// <summary>
1387 /// Finalize the Property table.
1388 /// </summary>
1389 /// <param name="tables">The collection of all tables.</param>
1390 /// <remarks>
1391 /// Removes properties that are generated from other entries.
1392 /// </remarks>
1393 private void FinalizePropertyTable(TableIndexedCollection tables)
1394 {
1395 foreach (var row in tables["CustomAction"]?.Rows ?? Enumerable.Empty<Row>())
1396 {
1397 // If no other fields on the property are set we must have created it in the backend.
1398 var bits = row.FieldAsInteger(1);
1399 if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (bits & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget)
1400 && WindowsInstallerConstants.MsidbCustomActionTypeInScript == (bits & WindowsInstallerConstants.MsidbCustomActionTypeInScript)
1401 && this.DecompilerHelper.TryGetIndexedElement("Property", row.FieldAsString(0), out var xProperty)
1402 && String.IsNullOrEmpty(xProperty.Attribute("Value")?.Value)
1403 && xProperty.Attribute("Secure")?.Value != "yes"
1404 && xProperty.Attribute("SuppressModularization")?.Value != "yes")
1405 {
1406 xProperty.Remove();
1407 }
1408 }
1409 }
1410
1411 /// <summary>
1412 /// Finalize the RemoveFile table.
1413 /// </summary>
1414 /// <param name="tables">The collection of all tables.</param>
1415 /// <remarks>
1416 /// Sets the directory/property for each RemoveFile row.
1417 /// </remarks>
1418 private void FinalizeRemoveFileTable(TableIndexedCollection tables)
1419 {
1420 foreach (var row in tables["RemoveFile"]?.Rows ?? Enumerable.Empty<Row>())
1421 {
1422 var xRemove = this.DecompilerHelper.GetIndexedElement(row);
1423 var property = row.FieldAsString(3);
1424
1425 if (this.DecompilerHelper.TryGetIndexedElement("Directory", property, out var _))
1426 {
1427 xRemove.SetAttributeValue("Directory", property);
1428 }
1429 else
1430 {
1431 xRemove.SetAttributeValue("Property", property);
1432 }
1433 }
1434 }
1435
1436 /// <summary>
1437 /// Finalize the LockPermissions or MsiLockPermissionsEx table.
1438 /// </summary>
1439 /// <param name="tables">The collection of all tables.</param>
1440 /// <param name="tableName">Which table to finalize.</param>
1441 /// <remarks>
1442 /// Nests the Permission elements below their parent elements. There are no declared foreign
1443 /// keys for the parents of the LockPermissions table.
1444 /// </remarks>
1445 private void FinalizePermissionsTable(TableIndexedCollection tables, string tableName)
1446 {
1447 var createFoldersById = this.IndexTableOneToMany(tables, tableName);
1448
1449 foreach (var row in tables[tableName]?.Rows ?? Enumerable.Empty<Row>())
1450 {
1451 var id = row.FieldAsString(0);
1452 var table = row.FieldAsString(1);
1453 var xPermission = this.DecompilerHelper.GetIndexedElement(row);
1454
1455 if ("CreateFolder" == table)
1456 {
1457 if (createFoldersById.TryGetValue(id, out var xCreateFolders))
1458 {
1459 foreach (var xCreateFolder in xCreateFolders)
1460 {
1461 xCreateFolder.Add(xPermission);
1462 }
1463 }
1464 else
1465 {
1466 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
1467 }
1468 }
1469 else
1470 {
1471 if (this.DecompilerHelper.TryGetIndexedElement(table, id, out var xParent))
1472 {
1473 xParent.Add(xPermission);
1474 }
1475 else
1476 {
1477 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, tableName, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "LockObject", id, table));
1478 }
1479 }
1480 }
1481 }
1482
1483 /// <summary>
1484 /// Finalize the LockPermissions table.
1485 /// </summary>
1486 /// <param name="tables">The collection of all tables.</param>
1487 /// <remarks>
1488 /// Nests the Permission elements below their parent elements. There are no declared foreign
1489 /// keys for the parents of the LockPermissions table.
1490 /// </remarks>
1491 private void FinalizeLockPermissionsTable(TableIndexedCollection tables)
1492 {
1493 this.FinalizePermissionsTable(tables, "LockPermissions");
1494 }
1495
1496 /// <summary>
1497 /// Finalize the MsiLockPermissionsEx table.
1498 /// </summary>
1499 /// <param name="tables">The collection of all tables.</param>
1500 /// <remarks>
1501 /// Nests the PermissionEx elements below their parent elements. There are no declared foreign
1502 /// keys for the parents of the MsiLockPermissionsEx table.
1503 /// </remarks>
1504 private void FinalizeMsiLockPermissionsExTable(TableIndexedCollection tables)
1505 {
1506 this.FinalizePermissionsTable(tables, "MsiLockPermissionsEx");
1507 }
1508
1509 private static Dictionary<string, List<string>> IndexTable(Table table, int keyColumn, int? dataColumn)
1510 {
1511 if (table == null)
1512 {
1513 return new Dictionary<string, List<string>>();
1514 }
1515
1516 return table.Rows
1517 .ToLookup(row => row.FieldAsString(keyColumn), row => dataColumn.HasValue ? row.FieldAsString(dataColumn.Value) : null)
1518 .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
1519 }
1520
1521 private static XElement FindComplianceDrive(XElement xSearch)
1522 {
1523 var xComplianceDrive = xSearch.Element(Names.ComplianceDriveElement);
1524 if (null == xComplianceDrive)
1525 {
1526 xComplianceDrive = new XElement(Names.ComplianceDriveElement);
1527 xSearch.Add(xComplianceDrive);
1528 }
1529
1530 return xComplianceDrive;
1531 }
1532
1533 /// <summary>
1534 /// Finalize the search tables.
1535 /// </summary>
1536 /// <param name="tables">The collection of all tables.</param>
1537 /// <remarks>Does all the complex linking required for the search tables.</remarks>
1538 private void FinalizeSearchTables(TableIndexedCollection tables)
1539 {
1540 var appSearches = IndexTable(tables["AppSearch"], keyColumn: 1, dataColumn: 0);
1541 var ccpSearches = IndexTable(tables["CCPSearch"], keyColumn: 0, dataColumn: null);
1542 var drLocators = tables["DrLocator"]?.Rows.ToDictionary(row => this.DecompilerHelper.GetIndexedElement(row), row => row);
1543
1544 var xComplianceCheck = new XElement(Names.ComplianceCheckElement);
1545 if (ccpSearches.Keys.Any(ccpSignature => !appSearches.ContainsKey(ccpSignature)))
1546 {
1547 this.DecompilerHelper.AddElementToRoot(xComplianceCheck);
1548 }
1549
1550 // index the locator tables by their signatures
1551 var locators =
1552 new[] { "CompLocator", "RegLocator", "IniLocator", "DrLocator", "Signature" }
1553 .SelectMany(table => tables[table]?.Rows ?? Enumerable.Empty<Row>())
1554 .ToLookup(row => row.FieldAsString(0), row => row)
1555 .ToDictionary(lookup => lookup.Key, lookup => lookup.ToList());
1556
1557 // move the DrLocator rows with a parent of CCP_DRIVE first to ensure they get FileSearch children (not FileSearchRef)
1558 foreach (var locatorRows in locators.Values)
1559 {
1560 var firstDrLocator = -1;
1561
1562 for (var i = 0; i < locatorRows.Count; i++)
1563 {
1564 var locatorRow = (Row)locatorRows[i];
1565
1566 if ("DrLocator" == locatorRow.TableDefinition.Name)
1567 {
1568 if (-1 == firstDrLocator)
1569 {
1570 firstDrLocator = i;
1571 }
1572
1573 if ("CCP_DRIVE" == Convert.ToString(locatorRow[1]))
1574 {
1575 locatorRows.RemoveAt(i);
1576 locatorRows.Insert(firstDrLocator, locatorRow);
1577 break;
1578 }
1579 }
1580 }
1581 }
1582
1583 var xUsedSearches = new HashSet<XElement>();
1584 var xUnusedSearches = new Dictionary<string, XElement>();
1585
1586 foreach (var signature in locators.Keys)
1587 {
1588 var locatorRows = locators[signature];
1589 var xSignatureSearches = new List<XElement>();
1590
1591 foreach (var locatorRow in locatorRows)
1592 {
1593 var used = true;
1594 var xSearch = this.DecompilerHelper.GetIndexedElement(locatorRow);
1595
1596 if ("Signature" == locatorRow.TableDefinition.Name && 0 < xSignatureSearches.Count)
1597 {
1598 foreach (var xSearchParent in xSignatureSearches)
1599 {
1600 if (!xUsedSearches.Contains(xSearch))
1601 {
1602 xSearchParent.Add(xSearch);
1603 xUsedSearches.Add(xSearch);
1604 }
1605 else
1606 {
1607 var xFileSearchRef = new XElement(Names.FileSearchRefElement,
1608 new XAttribute("Id", signature));
1609
1610 xSearchParent.Add(xFileSearchRef);
1611 }
1612 }
1613 }
1614 else if ("DrLocator" == locatorRow.TableDefinition.Name && !locatorRow.IsColumnNull(1))
1615 {
1616 var parentSignature = locatorRow.FieldAsString(1);
1617
1618 if ("CCP_DRIVE" == parentSignature)
1619 {
1620 if (appSearches.ContainsKey(signature)
1621 && appSearches.TryGetValue(signature, out var appSearchPropertyIds))
1622 {
1623 foreach (var propertyId in appSearchPropertyIds)
1624 {
1625 var xProperty = this.EnsureProperty(propertyId);
1626
1627 if (ccpSearches.ContainsKey(signature))
1628 {
1629 xProperty.SetAttributeValue("ComplianceCheck", "yes");
1630 }
1631
1632 var xComplianceDrive = FindComplianceDrive(xProperty);
1633
1634 if (!xUsedSearches.Contains(xSearch))
1635 {
1636 xComplianceDrive.Add(xSearch);
1637 xUsedSearches.Add(xSearch);
1638 }
1639 else
1640 {
1641 var directorySearchRef = new XElement(Names.DirectorySearchRefElement,
1642 new XAttribute("Id", signature),
1643 XAttributeIfNotNull("Parent", locatorRow, 1),
1644 XAttributeIfNotNull("Path", locatorRow, 2));
1645
1646 xComplianceDrive.Add(directorySearchRef);
1647 xSignatureSearches.Add(directorySearchRef);
1648 }
1649 }
1650 }
1651 else if (ccpSearches.ContainsKey(signature))
1652 {
1653 var xComplianceDrive = FindComplianceDrive(xComplianceCheck);
1654
1655 if (!xUsedSearches.Contains(xSearch))
1656 {
1657 xComplianceDrive.Add(xSearch);
1658 xUsedSearches.Add(xSearch);
1659 }
1660 else
1661 {
1662 var directorySearchRef = new XElement(Names.DirectorySearchRefElement,
1663 new XAttribute("Id", signature),
1664 XAttributeIfNotNull("Parent", locatorRow, 1),
1665 XAttributeIfNotNull("Path", locatorRow, 2));
1666
1667 xComplianceDrive.Add(directorySearchRef);
1668 xSignatureSearches.Add(directorySearchRef);
1669 }
1670 }
1671 }
1672 else
1673 {
1674 var usedDrLocator = false;
1675
1676 if (locators.TryGetValue(parentSignature, out var parentLocatorRows))
1677 {
1678 foreach (var parentLocatorRow in parentLocatorRows)
1679 {
1680 if ("DrLocator" == parentLocatorRow.TableDefinition.Name)
1681 {
1682 var xParentSearch = this.DecompilerHelper.GetIndexedElement(parentLocatorRow);
1683
1684 if (xParentSearch.HasElements)
1685 {
1686 var parentDrLocatorRow = drLocators[xParentSearch];
1687 var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement,
1688 new XAttribute("Id", parentSignature),
1689 XAttributeIfNotNull("Parent", parentDrLocatorRow, 1),
1690 XAttributeIfNotNull("Path", parentDrLocatorRow, 2));
1691
1692 xParentSearch = xDirectorySearchRef;
1693 xUnusedSearches.Add(parentSignature, xDirectorySearchRef);
1694 }
1695
1696 if (!xUsedSearches.Contains(xSearch))
1697 {
1698 xParentSearch.Add(xSearch);
1699 xUsedSearches.Add(xSearch);
1700 usedDrLocator = true;
1701 }
1702 else
1703 {
1704 var xDirectorySearchRef = new XElement(Names.DirectorySearchRefElement,
1705 new XAttribute("Id", signature),
1706 new XAttribute("Parent", parentSignature),
1707 XAttributeIfNotNull("Path", locatorRow, 2));
1708
1709 xParentSearch.Add(xSearch);
1710 usedDrLocator = true;
1711 }
1712 }
1713 else if ("RegLocator" == parentLocatorRow.TableDefinition.Name)
1714 {
1715 var xParentSearch = this.DecompilerHelper.GetIndexedElement(parentLocatorRow);
1716
1717 xParentSearch.Add(xSearch);
1718 xUsedSearches.Add(xSearch);
1719 usedDrLocator = true;
1720 }
1721 }
1722
1723 // keep track of unused DrLocator rows
1724 if (!usedDrLocator)
1725 {
1726 xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch);
1727 }
1728 }
1729 else
1730 {
1731 // TODO: warn
1732 }
1733 }
1734 }
1735 else if (appSearches.ContainsKey(signature)
1736 && appSearches.TryGetValue(signature, out var appSearchPropertyIds))
1737 {
1738 foreach (var propertyId in appSearchPropertyIds)
1739 {
1740 var xProperty = this.EnsureProperty(propertyId);
1741
1742 if (ccpSearches.ContainsKey(signature))
1743 {
1744 xProperty.SetAttributeValue("ComplianceCheck", "yes");
1745 }
1746
1747 if (!xUsedSearches.Contains(xSearch))
1748 {
1749 xProperty.Add(xSearch);
1750 xUsedSearches.Add(xSearch);
1751 }
1752 else if ("RegLocator" == locatorRow.TableDefinition.Name)
1753 {
1754 var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement,
1755 new XAttribute("Id", signature));
1756
1757 xProperty.Add(xRegistrySearchRef);
1758 xSignatureSearches.Add(xRegistrySearchRef);
1759 }
1760 else
1761 {
1762 // TODO: warn about unavailable Ref element
1763 }
1764 }
1765 }
1766 else if (ccpSearches.ContainsKey(signature))
1767 {
1768 if (!xUsedSearches.Contains(xSearch))
1769 {
1770 xComplianceCheck.Add(xSearch);
1771 xUsedSearches.Add(xSearch);
1772 }
1773 else if ("RegLocator" == locatorRow.TableDefinition.Name)
1774 {
1775 var xRegistrySearchRef = new XElement(Names.RegistrySearchRefElement,
1776 new XAttribute("Id", signature));
1777
1778 xComplianceCheck.Add(xRegistrySearchRef);
1779 xSignatureSearches.Add(xRegistrySearchRef);
1780 }
1781 else
1782 {
1783 // TODO: warn about unavailable Ref element
1784 }
1785 }
1786 else
1787 {
1788 if (xSearch.Name.LocalName == "DirectorySearch" || xSearch.Name.LocalName == "RegistrySearch")
1789 {
1790 xUnusedSearches.Add(xSearch.Attribute("Id").Value, xSearch);
1791 }
1792 else
1793 {
1794 // TODO: warn
1795 used = false;
1796 }
1797 }
1798
1799 // keep track of the search elements for this signature so that nested searches go in the proper parents
1800 if (used)
1801 {
1802 xSignatureSearches.Add(xSearch);
1803 }
1804 }
1805 }
1806
1807 // Iterate through the unused elements through a sorted list of their ids so the output is deterministic.
1808 foreach (var unusedSearch in xUnusedSearches.OrderBy(kvp => kvp.Key))
1809 {
1810 var used = false;
1811
1812 XElement xLeafDirectorySearch = null;
1813 var xUnusedSearch = unusedSearch.Value;
1814 var xParent = xUnusedSearch;
1815 var updatedLeaf = true;
1816 while (updatedLeaf)
1817 {
1818 updatedLeaf = false;
1819
1820 var xDirectorySearch = xParent.Element(Names.DirectorySearchElement);
1821 if (xDirectorySearch != null)
1822 {
1823 xParent = xLeafDirectorySearch = xDirectorySearch;
1824 updatedLeaf = true;
1825 }
1826 }
1827
1828 if (xLeafDirectorySearch != null)
1829 {
1830 var leafDirectorySearchId = xLeafDirectorySearch.Attribute("Id").Value;
1831 if (appSearches.TryGetValue(leafDirectorySearchId, out var appSearchPropertyIds))
1832 {
1833 var xProperty = this.EnsureProperty(appSearchPropertyIds[0]);
1834 xProperty.Add(xUnusedSearch);
1835 used = true;
1836 }
1837 else if (ccpSearches.ContainsKey(leafDirectorySearchId))
1838 {
1839 xComplianceCheck.Add(xUnusedSearch);
1840 used = true;
1841 }
1842 else
1843 {
1844 // TODO: warn
1845 }
1846 }
1847
1848 if (!used)
1849 {
1850 // TODO: warn
1851 }
1852 }
1853 }
1854
1855 /// <summary>
1856 /// Finalize the Shortcut table.
1857 /// </summary>
1858 /// <param name="tables">The collection of all tables.</param>
1859 /// <remarks>
1860 /// Sets Advertise to yes if Target points to a Feature.
1861 /// Occurs during finalization because it has to check against every feature row.
1862 /// </remarks>
1863 private void FinalizeShortcutTable(TableIndexedCollection tables)
1864 {
1865 var shortcutTable = tables["Shortcut"];
1866 if (null == shortcutTable)
1867 {
1868 return;
1869 }
1870
1871 foreach (var row in shortcutTable.Rows)
1872 {
1873 var xShortcut = this.DecompilerHelper.GetIndexedElement(row);
1874
1875 var target = row.FieldAsString(4);
1876
1877 if (this.DecompilerHelper.TryGetIndexedElement("Feature", target, out var _))
1878 {
1879 xShortcut.SetAttributeValue("Advertise", "yes");
1880 this.SetPrimaryFeature(row, 4, 3);
1881 }
1882 else
1883 {
1884 // TODO: use this value to do a "more-correct" nesting under the indicated File or CreateDirectory element
1885 xShortcut.SetAttributeValue("Target", target);
1886 }
1887 }
1888 }
1889
1890 /// <summary>
1891 /// Finalize the sequence tables.
1892 /// </summary>
1893 /// <param name="tables">The collection of all tables.</param>
1894 /// <remarks>
1895 /// Creates the sequence elements. Occurs during finalization because its
1896 /// not known if sequences refer to custom actions or dialogs during decompilation.
1897 /// </remarks>
1898 private void FinalizeSequenceTables(TableIndexedCollection tables)
1899 {
1900 // finalize the normal sequence tables
1901 if (OutputType.Package == this.OutputType)
1902 {
1903 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
1904 {
1905 var sequenceTableName = sequenceTable.WindowsInstallerTableName();
1906
1907 // if suppressing UI elements, skip UI-related sequence tables
1908 if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName))
1909 {
1910 continue;
1911 }
1912
1913 var table = tables[sequenceTableName];
1914
1915 if (null != table)
1916 {
1917 var actionSymbols = new List<WixActionSymbol>();
1918 var needAbsoluteScheduling = this.SuppressRelativeActionSequencing;
1919 var nonSequencedActionRows = new Dictionary<string, WixActionSymbol>();
1920 var suppressedRelativeActionRows = new Dictionary<string, WixActionSymbol>();
1921
1922 // create a sorted array of actions in this table
1923 foreach (var row in table.Rows)
1924 {
1925 var action = row.FieldAsString(0);
1926 var actionSymbol = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, action));
1927
1928 actionSymbol.Action = action;
1929
1930 if (!row.IsColumnNull(1))
1931 {
1932 actionSymbol.Condition = row.FieldAsString(1);
1933 }
1934
1935 actionSymbol.Sequence = row.FieldAsInteger(2);
1936
1937 actionSymbol.SequenceTable = sequenceTable;
1938
1939 actionSymbols.Add(actionSymbol);
1940 }
1941 actionSymbols = actionSymbols.OrderBy(t => t.Sequence).ToList();
1942
1943 for (var i = 0; i < actionSymbols.Count && !needAbsoluteScheduling; i++)
1944 {
1945 var actionSymbol = actionSymbols[i];
1946 this.StandardActions.TryGetValue(actionSymbol.Id.Id, out var standardActionRow);
1947
1948 // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions
1949 if ("AppSearch" == actionSymbol.Action || null == standardActionRow || actionSymbol.Condition != standardActionRow.Condition)
1950 {
1951 WixActionSymbol previousActionSymbol = null;
1952 WixActionSymbol nextActionSymbol = null;
1953
1954 // find the previous action row if there is one
1955 if (0 <= i - 1)
1956 {
1957 previousActionSymbol = actionSymbols[i - 1];
1958 }
1959
1960 // find the next action row if there is one
1961 if (actionSymbols.Count > i + 1)
1962 {
1963 nextActionSymbol = actionSymbols[i + 1];
1964 }
1965
1966 // the logic for setting the before or after attribute for an action:
1967 // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced.
1968 // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it.
1969 // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it.
1970 // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it.
1971 // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it.
1972 // 6. If this action is AppSearch and has all standard information, ignore it.
1973 // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information.
1974 // 8. Everything must be absolutely sequenced.
1975 if ((null != previousActionSymbol && actionSymbol.Sequence == previousActionSymbol.Sequence) || (null != nextActionSymbol && actionSymbol.Sequence == nextActionSymbol.Sequence))
1976 {
1977 needAbsoluteScheduling = true;
1978 }
1979 else if (null != nextActionSymbol && this.StandardActions.ContainsKey(nextActionSymbol.Id.Id) && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence)
1980 {
1981 actionSymbol.Before = nextActionSymbol.Action;
1982 }
1983 else if (null != previousActionSymbol && this.StandardActions.ContainsKey(previousActionSymbol.Id.Id) && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence)
1984 {
1985 actionSymbol.After = previousActionSymbol.Action;
1986 }
1987 else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence - 1 == previousActionSymbol.Sequence && previousActionSymbol.Before != actionSymbol.Action)
1988 {
1989 actionSymbol.After = previousActionSymbol.Action;
1990 }
1991 else if (null == standardActionRow && null != previousActionSymbol && actionSymbol.Sequence != previousActionSymbol.Sequence && null != nextActionSymbol && actionSymbol.Sequence + 1 == nextActionSymbol.Sequence)
1992 {
1993 actionSymbol.Before = nextActionSymbol.Action;
1994 }
1995 else if ("AppSearch" == actionSymbol.Action && null != standardActionRow && actionSymbol.Sequence == standardActionRow.Sequence && actionSymbol.Condition == standardActionRow.Condition)
1996 {
1997 // ignore an AppSearch row which has the WiX standard sequence and a standard condition
1998 }
1999 else if (null != standardActionRow && actionSymbol.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers
2000 {
2001 nonSequencedActionRows.Add(actionSymbol.Id.Id, actionSymbol);
2002 }
2003 else if (0 < actionSymbol.Sequence)
2004 {
2005 needAbsoluteScheduling = true;
2006 }
2007 }
2008 else
2009 {
2010 suppressedRelativeActionRows.Add(actionSymbol.Id.Id, actionSymbol);
2011 }
2012 }
2013
2014 // create the actions now that we know if they must be absolutely or relatively scheduled
2015 foreach (var actionRow in actionSymbols)
2016 {
2017 var key = actionRow.Id.Id;
2018
2019 if (needAbsoluteScheduling)
2020 {
2021 // remove any before/after information to ensure this is absolutely sequenced
2022 actionRow.Before = null;
2023 actionRow.After = null;
2024 }
2025 else if (nonSequencedActionRows.ContainsKey(key))
2026 {
2027 // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after)
2028 actionRow.Sequence = 0;
2029 }
2030 else if (suppressedRelativeActionRows.ContainsKey(key))
2031 {
2032 // skip the suppressed relatively scheduled action rows
2033 continue;
2034 }
2035
2036 // create the action element
2037 this.CreateActionElement(actionRow);
2038 }
2039 }
2040 }
2041 }
2042 else if (OutputType.Module == this.OutputType) // finalize the Module sequence tables
2043 {
2044 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
2045 {
2046 var sequenceTableName = sequenceTable.WindowsInstallerTableName();
2047
2048 // if suppressing UI elements, skip UI-related sequence tables
2049 if (this.SuppressUI && ("AdminUISequence" == sequenceTableName || "InstallUISequence" == sequenceTableName))
2050 {
2051 continue;
2052 }
2053
2054 var table = tables[String.Concat("Module", sequenceTableName)];
2055
2056 if (null != table)
2057 {
2058 foreach (var row in table.Rows)
2059 {
2060 var actionRow = new WixActionSymbol(null, new Identifier(AccessModifier.Global, sequenceTable, row.FieldAsString(0)));
2061
2062 actionRow.Action = row.FieldAsString(0);
2063
2064 if (!row.IsColumnNull(1))
2065 {
2066 actionRow.Sequence = row.FieldAsInteger(1);
2067 }
2068
2069 if (!row.IsColumnNull(2) && !row.IsColumnNull(3))
2070 {
2071 switch (row.FieldAsInteger(3))
2072 {
2073 case 0:
2074 actionRow.Before = row.FieldAsString(2);
2075 break;
2076 case 1:
2077 actionRow.After = row.FieldAsString(2);
2078 break;
2079 default:
2080 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3]));
2081 break;
2082 }
2083 }
2084
2085 if (!row.IsColumnNull(4))
2086 {
2087 actionRow.Condition = row.FieldAsString(4);
2088 }
2089
2090 actionRow.SequenceTable = sequenceTable;
2091
2092 // create action elements for non-standard actions
2093 if (!this.StandardActions.ContainsKey(actionRow.Id.Id) || null != actionRow.After || null != actionRow.Before)
2094 {
2095 this.CreateActionElement(actionRow);
2096 }
2097 }
2098 }
2099 }
2100 }
2101 }
2102
2103 /// <summary>
2104 /// Finalize the Upgrade table.
2105 /// </summary>
2106 /// <param name="tables">The collection of all tables.</param>
2107 /// <remarks>
2108 /// Decompile the rows from the Upgrade and LaunchCondition tables
2109 /// created by the MajorUpgrade element.
2110 /// </remarks>
2111 private void FinalizeUpgradeTable(TableIndexedCollection tables)
2112 {
2113 var launchConditionTable = tables["LaunchCondition"];
2114 var upgradeTable = tables["Upgrade"];
2115 string downgradeErrorMessage = null;
2116 string disallowUpgradeErrorMessage = null;
2117
2118 // find the DowngradePreventedCondition launch condition message
2119 if (null != launchConditionTable && 0 < launchConditionTable.Rows.Count)
2120 {
2121 foreach (var launchRow in launchConditionTable.Rows)
2122 {
2123 if (WixUpgradeConstants.DowngradePreventedCondition == Convert.ToString(launchRow[0]))
2124 {
2125 downgradeErrorMessage = Convert.ToString(launchRow[1]);
2126 }
2127 else if (WixUpgradeConstants.UpgradePreventedCondition == Convert.ToString(launchRow[0]))
2128 {
2129 disallowUpgradeErrorMessage = Convert.ToString(launchRow[1]);
2130 }
2131 }
2132 }
2133
2134 if (null != upgradeTable && 0 < upgradeTable.Rows.Count)
2135 {
2136 XElement xMajorUpgrade = null;
2137
2138 foreach (UpgradeRow upgradeRow in upgradeTable.Rows)
2139 {
2140 if (WixUpgradeConstants.UpgradeDetectedProperty == upgradeRow.ActionProperty)
2141 {
2142 var attr = upgradeRow.Attributes;
2143 var removeFeatures = upgradeRow.Remove;
2144 xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement);
2145
2146 if (WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive))
2147 {
2148 xMajorUpgrade.SetAttributeValue("AllowSameVersionUpgrades", "yes");
2149 }
2150
2151 if (WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures != (attr & WindowsInstallerConstants.MsidbUpgradeAttributesMigrateFeatures))
2152 {
2153 xMajorUpgrade.SetAttributeValue("MigrateFeatures", "no");
2154 }
2155
2156 if (WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure == (attr & WindowsInstallerConstants.MsidbUpgradeAttributesIgnoreRemoveFailure))
2157 {
2158 xMajorUpgrade.SetAttributeValue("IgnoreRemoveFailure", "yes");
2159 }
2160
2161 if (!String.IsNullOrEmpty(removeFeatures))
2162 {
2163 xMajorUpgrade.SetAttributeValue("RemoveFeatures", removeFeatures);
2164 }
2165 }
2166 else if (WixUpgradeConstants.DowngradeDetectedProperty == upgradeRow.ActionProperty)
2167 {
2168 xMajorUpgrade = xMajorUpgrade ?? new XElement(Names.MajorUpgradeElement);
2169 xMajorUpgrade.SetAttributeValue("DowngradeErrorMessage", downgradeErrorMessage);
2170 }
2171 }
2172
2173 if (xMajorUpgrade != null)
2174 {
2175 if (String.IsNullOrEmpty(downgradeErrorMessage))
2176 {
2177 xMajorUpgrade.SetAttributeValue("AllowDowngrades", "yes");
2178 }
2179
2180 if (!String.IsNullOrEmpty(disallowUpgradeErrorMessage))
2181 {
2182 xMajorUpgrade.SetAttributeValue("Disallow", "yes");
2183 xMajorUpgrade.SetAttributeValue("DisallowUpgradeErrorMessage", disallowUpgradeErrorMessage);
2184 }
2185
2186 var scheduledType = DetermineMajorUpgradeScheduling(tables);
2187 if (scheduledType != "afterInstallValidate")
2188 {
2189 xMajorUpgrade.SetAttributeValue("Schedule", scheduledType);
2190 }
2191
2192 this.DecompilerHelper.AddElementToRoot(xMajorUpgrade);
2193 }
2194 }
2195 }
2196
2197 /// <summary>
2198 /// Finalize the Verb table.
2199 /// </summary>
2200 /// <param name="tables">The collection of all tables.</param>
2201 /// <remarks>
2202 /// The Extension table is a foreign table for the Verb table, but the
2203 /// foreign key is only part of the primary key of the Extension table,
2204 /// so it needs special logic to be nested properly.
2205 /// </remarks>
2206 private void FinalizeVerbTable(TableIndexedCollection tables)
2207 {
2208 var xExtensions = this.IndexTableOneToMany(tables["Extension"]);
2209
2210 var verbTable = tables["Verb"];
2211 if (null != verbTable)
2212 {
2213 foreach (var row in verbTable.Rows)
2214 {
2215 if (xExtensions.TryGetValue(row.FieldAsString(0), out var xVerbExtensions))
2216 {
2217 var xVerb = this.DecompilerHelper.GetIndexedElement(row);
2218
2219 foreach (var xVerbExtension in xVerbExtensions)
2220 {
2221 xVerbExtension.Add(xVerb);
2222 }
2223 }
2224 else
2225 {
2226 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, verbTable.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Extension_", row.FieldAsString(0), "Extension"));
2227 }
2228 }
2229 }
2230 }
2231
2232 /// <summary>
2233 /// Get the path to a file in the source image.
2234 /// </summary>
2235 /// <param name="xFile">The file.</param>
2236 /// <returns>The path to the file in the source image.</returns>
2237 private string GetSourcePath(XElement xFile)
2238 {
2239 var sourcePath = new StringBuilder();
2240
2241 var component = xFile.Parent;
2242 var xDirectory = component.Parent;
2243
2244 while (xDirectory?.Name.LocalName == "Directory")
2245 {
2246 string name;
2247
2248 var dirSourceName = xDirectory.Attribute("SourceName")?.Value;
2249 var dirShortSourceName = xDirectory.Attribute("ShortSourceName")?.Value;
2250 var dirShortName = xDirectory.Attribute("ShortName")?.Value;
2251 var dirName = xDirectory.Attribute("Name")?.Value;
2252
2253 if (!this.ShortNames && null != dirSourceName)
2254 {
2255 name = dirSourceName;
2256 }
2257 else if (null != dirShortSourceName)
2258 {
2259 name = dirShortSourceName;
2260 }
2261 else if (!this.ShortNames || null == dirShortName)
2262 {
2263 name = dirName;
2264 }
2265 else
2266 {
2267 name = dirShortName;
2268 }
2269
2270 if (0 == sourcePath.Length)
2271 {
2272 sourcePath.Append(name);
2273 }
2274 else
2275 {
2276 sourcePath.Insert(0, Path.DirectorySeparatorChar);
2277 sourcePath.Insert(0, name);
2278 }
2279
2280 xDirectory = xDirectory.Parent;
2281 }
2282
2283 if (xDirectory?.Name.LocalName == "StandardDirectory" && WindowsInstallerStandard.TryGetStandardDirectoryName(xDirectory.Attribute("Id").Value, out var standardDirectoryName))
2284 {
2285 sourcePath.Insert(0, Path.DirectorySeparatorChar);
2286 sourcePath.Insert(0, standardDirectoryName);
2287 }
2288
2289 return sourcePath.ToString();
2290 }
2291
2292 /// <summary>
2293 /// Resolve the dependencies for a table (this is a helper method for GetSortedTableNames).
2294 /// </summary>
2295 /// <param name="tableName">The name of the table to resolve.</param>
2296 /// <param name="unsortedTableNames">The unsorted table names.</param>
2297 /// <param name="sortedTableNames">The sorted table names.</param>
2298 private void ResolveTableDependencies(string tableName, List<string> unsortedTableNames, HashSet<string> sortedTableNames)
2299 {
2300 unsortedTableNames.Remove(tableName);
2301
2302 foreach (var columnDefinition in this.TableDefinitions[tableName].Columns)
2303 {
2304 // no dependency to resolve because this column doesn't reference another table
2305 if (null == columnDefinition.KeyTable)
2306 {
2307 continue;
2308 }
2309
2310 foreach (var keyTable in columnDefinition.KeyTable.Split(';'))
2311 {
2312 if (tableName == keyTable)
2313 {
2314 continue; // self-referencing dependency
2315 }
2316 else if (sortedTableNames.Contains(keyTable))
2317 {
2318 continue; // dependent table has already been sorted
2319 }
2320 else if (!this.TableDefinitions.Contains(keyTable))
2321 {
2322 this.Messaging.Write(ErrorMessages.MissingTableDefinition(keyTable));
2323 }
2324 else if (unsortedTableNames.Contains(keyTable))
2325 {
2326 this.ResolveTableDependencies(keyTable, unsortedTableNames, sortedTableNames);
2327 }
2328 else
2329 {
2330 // found a circular dependency, so ignore it (this assumes that the tables will
2331 // use a finalize method to nest their elements since the ordering will not be
2332 // deterministic
2333 }
2334 }
2335 }
2336
2337 sortedTableNames.Add(tableName);
2338 }
2339
2340 /// <summary>
2341 /// Get the names of the tables to process in the order they should be processed, according to their dependencies.
2342 /// </summary>
2343 /// <returns>A StringCollection containing the ordered table names.</returns>
2344 private HashSet<string> GetOrderedTableNames()
2345 {
2346 var orderedTableNames = new HashSet<string>();
2347 var unsortedTableNames = new List<string>(this.TableDefinitions.Select(t => t.Name));
2348
2349 // resolve the dependencies for each table
2350 while (0 < unsortedTableNames.Count)
2351 {
2352 this.ResolveTableDependencies(unsortedTableNames[0], unsortedTableNames, orderedTableNames);
2353 }
2354
2355 return orderedTableNames;
2356 }
2357
2358 /// <summary>
2359 /// Initialize decompilation.
2360 /// </summary>
2361 /// <param name="tables">The collection of all tables.</param>
2362 /// <param name="codepage"></param>
2363 private void InitializeDecompile(TableIndexedCollection tables, int codepage)
2364 {
2365 // reset all the state information
2366 this.Compressed = false;
2367 this.ShortNames = false;
2368
2369 this.Singletons.Clear();
2370 //this.IndexedElements.Clear();
2371 this.PatchTargetFiles.Clear();
2372
2373 // set the codepage if its not neutral (0)
2374 if (0 != codepage)
2375 {
2376 this.DecompilerHelper.RootElement.SetAttributeValue("Codepage", codepage);
2377 }
2378
2379 if (this.OutputType == OutputType.Module)
2380 {
2381 var table = tables["_SummaryInformation"];
2382 var row = table.Rows.SingleOrDefault(r => r.FieldAsInteger(0) == 9);
2383 this.ModularizationGuid = row?.FieldAsString(1);
2384 this.DecompilerHelper.RootElement.SetAttributeValue("Guid", this.ModularizationGuid);
2385 }
2386
2387 foreach (var extension in this.Extensions)
2388 {
2389 extension.PreDecompileTables(tables);
2390 }
2391
2392 this.RemoveExtensionDataFromTables(tables);
2393 }
2394
2395 private void RemoveExtensionDataFromTables(TableIndexedCollection tables)
2396 {
2397 var tableDefinitionBySymbolDefinitionName = this.TableDefinitions.Where(t => t.SymbolDefinition != null).ToDictionary(t => t.SymbolDefinition.Name);
2398
2399 // index the rows from the extension libraries
2400 var indexedExtensionTables = new Dictionary<string, HashSet<string>>();
2401 foreach (var extension in this.ExtensionData)
2402 {
2403 // Get the optional library from the extension with the rows to be removed.
2404 var library = extension.GetLibrary(this.SymbolDefinitionCreator);
2405 if (library != null)
2406 {
2407 foreach (var symbol in library.Sections.SelectMany(s => s.Symbols))
2408 {
2409 if (this.TryGetPrimaryKeyFromSymbol(tableDefinitionBySymbolDefinitionName, symbol, out var tableName, out var primaryKey))
2410 {
2411 //// the Actions table needs to be handled specially
2412 //if (table.Name == "WixAction")
2413 //{
2414 // primaryKey = symbol.FieldAsString(1);
2415 // tableName = symbol.FieldAsString(0);
2416
2417 // if (this.outputType == OutputType.Module)
2418 // {
2419 // tableName = "Module" + tableName;
2420 // }
2421 //}
2422 //else
2423 //{
2424 // primaryKey = symbol.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter);
2425 // tableName = table.Name;
2426 //}
2427
2428 if (!indexedExtensionTables.TryGetValue(tableName, out var indexedExtensionRows))
2429 {
2430 indexedExtensionRows = new HashSet<string>();
2431 indexedExtensionTables.Add(tableName, indexedExtensionRows);
2432 }
2433
2434 indexedExtensionRows.Add(primaryKey);
2435 }
2436 }
2437 }
2438 }
2439
2440 // remove the rows from the extension libraries (to allow full round-tripping)
2441 foreach (var kvp in indexedExtensionTables)
2442 {
2443 var tableName = kvp.Key;
2444 var indexedExtensionRows = kvp.Value;
2445
2446 if (tables.TryGetTable(tableName, out var table))
2447 {
2448 var originalRows = new RowDictionary<Row>(table);
2449
2450 // remove the original rows so that they can be added back if they should remain
2451 table.Rows.Clear();
2452
2453 foreach (var row in originalRows.Values)
2454 {
2455 if (!indexedExtensionRows.Contains(row.GetPrimaryKey()))
2456 {
2457 table.Rows.Add(row);
2458 }
2459 }
2460 }
2461 }
2462 }
2463
2464 private bool TryGetPrimaryKeyFromSymbol(Dictionary<string, TableDefinition> tableDefinitionBySymbolDefinitionName, IntermediateSymbol symbol, out string tableName, out string primaryKey)
2465 {
2466 tableName = null;
2467 primaryKey = null;
2468
2469 if (symbol is WixActionSymbol actionSymbol)
2470 {
2471 tableName = actionSymbol.SequenceTable.WindowsInstallerTableName();
2472 primaryKey = actionSymbol.Action;
2473 return true;
2474 }
2475
2476 if (!tableDefinitionBySymbolDefinitionName.TryGetValue(symbol.Definition.Name, out var tableDefinition))
2477 {
2478 return false;
2479 }
2480
2481 tableName = tableDefinition.Name;
2482
2483 if (tableDefinition.SymbolIdIsPrimaryKey)
2484 {
2485 primaryKey = symbol.Id.Id;
2486 }
2487 else
2488 {
2489 var sb = new StringBuilder();
2490
2491 for (var i = 0; i < symbol.Fields.Length && i < tableDefinition.Columns.Length; ++i)
2492 {
2493 var column = tableDefinition.Columns[i];
2494 var field = symbol.Fields[i];
2495
2496 if (column.PrimaryKey)
2497 {
2498 if (sb.Length > 0)
2499 {
2500 sb.Append('/');
2501 }
2502
2503 sb.Append(field.AsString());
2504 }
2505 }
2506
2507 primaryKey = sb.ToString();
2508 }
2509
2510 return true;
2511 }
2512
2513 /// <summary>
2514 /// Decompile the tables.
2515 /// </summary>
2516 /// <param name="output">The output being decompiled.</param>
2517 private void DecompileTables(WindowsInstallerData output)
2518 {
2519 var orderedTableNames = this.GetOrderedTableNames();
2520 foreach (var tableName in orderedTableNames)
2521 {
2522 var table = output.Tables[tableName];
2523
2524 // table does not exist in this database or should not be decompiled
2525 if (null == table || !this.DecompilableTable(output, tableName))
2526 {
2527 continue;
2528 }
2529
2530 this.Messaging.Write(VerboseMessages.DecompilingTable(table.Name));
2531
2532 // empty tables may be kept with EnsureTable if the user set the proper option
2533 if (0 == table.Rows.Count && this.SuppressDroppingEmptyTables)
2534 {
2535 this.DecompilerHelper.AddElementToRoot(new XElement(Names.EnsureTableElement, new XAttribute("Id", table.Name)));
2536 }
2537
2538 switch (table.Name)
2539 {
2540 case "_SummaryInformation":
2541 // handled in FinalizeDecompile
2542 break;
2543 case "AdminExecuteSequence":
2544 case "AdminUISequence":
2545 case "AdvtExecuteSequence":
2546 case "InstallExecuteSequence":
2547 case "InstallUISequence":
2548 case "ModuleAdminExecuteSequence":
2549 case "ModuleAdminUISequence":
2550 case "ModuleAdvtExecuteSequence":
2551 case "ModuleInstallExecuteSequence":
2552 case "ModuleInstallUISequence":
2553 // handled in FinalizeSequenceTables
2554 break;
2555 case "ActionText":
2556 this.DecompileActionTextTable(table);
2557 break;
2558 case "AdvtUISequence":
2559 this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name));
2560 break;
2561 case "AppId":
2562 this.DecompileAppIdTable(table);
2563 break;
2564 case "AppSearch":
2565 // handled in FinalizeSearchTables
2566 break;
2567 case "BBControl":
2568 this.DecompileBBControlTable(table);
2569 break;
2570 case "Billboard":
2571 this.DecompileBillboardTable(table);
2572 break;
2573 case "Binary":
2574 this.DecompileBinaryTable(table);
2575 break;
2576 case "BindImage":
2577 this.DecompileBindImageTable(table);
2578 break;
2579 case "CCPSearch":
2580 // handled in FinalizeSearchTables
2581 break;
2582 case "CheckBox":
2583 // handled in FinalizeCheckBoxTable
2584 break;
2585 case "Class":
2586 this.DecompileClassTable(table);
2587 break;
2588 case "ComboBox":
2589 this.DecompileComboBoxTable(table);
2590 break;
2591 case "Control":
2592 this.DecompileControlTable(table);
2593 break;
2594 case "ControlCondition":
2595 this.DecompileControlConditionTable(table);
2596 break;
2597 case "ControlEvent":
2598 this.DecompileControlEventTable(table);
2599 break;
2600 case "CreateFolder":
2601 this.DecompileCreateFolderTable(table);
2602 break;
2603 case "CustomAction":
2604 this.DecompileCustomActionTable(table);
2605 break;
2606 case "CompLocator":
2607 this.DecompileCompLocatorTable(table);
2608 break;
2609 case "Complus":
2610 this.DecompileComplusTable(table);
2611 break;
2612 case "Component":
2613 this.DecompileComponentTable(table);
2614 break;
2615 case "Condition":
2616 this.DecompileConditionTable(table);
2617 break;
2618 case "Dialog":
2619 this.DecompileDialogTable(table);
2620 break;
2621 case "Directory":
2622 this.DecompileDirectoryTable(table);
2623 break;
2624 case "DrLocator":
2625 this.DecompileDrLocatorTable(table);
2626 break;
2627 case "DuplicateFile":
2628 this.DecompileDuplicateFileTable(table);
2629 break;
2630 case "Environment":
2631 this.DecompileEnvironmentTable(table);
2632 break;
2633 case "Error":
2634 this.DecompileErrorTable(table);
2635 break;
2636 case "EventMapping":
2637 this.DecompileEventMappingTable(table);
2638 break;
2639 case "Extension":
2640 this.DecompileExtensionTable(table);
2641 break;
2642 case "ExternalFiles":
2643 this.DecompileExternalFilesTable(table);
2644 break;
2645 case "FamilyFileRanges":
2646 // handled in FinalizeFamilyFileRangesTable
2647 break;
2648 case "Feature":
2649 this.DecompileFeatureTable(table);
2650 break;
2651 case "FeatureComponents":
2652 this.DecompileFeatureComponentsTable(table);
2653 break;
2654 case "File":
2655 this.DecompileFileTable(table);
2656 break;
2657 case "FileSFPCatalog":
2658 this.DecompileFileSFPCatalogTable(table);
2659 break;
2660 case "Font":
2661 this.DecompileFontTable(table);
2662 break;
2663 case "Icon":
2664 this.DecompileIconTable(table);
2665 break;
2666 case "ImageFamilies":
2667 this.DecompileImageFamiliesTable(table);
2668 break;
2669 case "IniFile":
2670 this.DecompileIniFileTable(table);
2671 break;
2672 case "IniLocator":
2673 this.DecompileIniLocatorTable(table);
2674 break;
2675 case "IsolatedComponent":
2676 this.DecompileIsolatedComponentTable(table);
2677 break;
2678 case "LaunchCondition":
2679 this.DecompileLaunchConditionTable(table);
2680 break;
2681 case "ListBox":
2682 this.DecompileListBoxTable(table);
2683 break;
2684 case "ListView":
2685 this.DecompileListViewTable(table);
2686 break;
2687 case "LockPermissions":
2688 this.DecompileLockPermissionsTable(table);
2689 break;
2690 case "Media":
2691 this.DecompileMediaTable(table);
2692 break;
2693 case "MIME":
2694 this.DecompileMIMETable(table);
2695 break;
2696 case "ModuleAdvtUISequence":
2697 this.Messaging.Write(WarningMessages.DeprecatedTable(table.Name));
2698 break;
2699 case "ModuleComponents":
2700 // handled by DecompileComponentTable (since the ModuleComponents table
2701 // rows are created by nesting components under the Module element)
2702 break;
2703 case "ModuleConfiguration":
2704 this.DecompileModuleConfigurationTable(table);
2705 break;
2706 case "ModuleDependency":
2707 this.DecompileModuleDependencyTable(table);
2708 break;
2709 case "ModuleExclusion":
2710 this.DecompileModuleExclusionTable(table);
2711 break;
2712 case "ModuleIgnoreTable":
2713 this.DecompileModuleIgnoreTableTable(table);
2714 break;
2715 case "ModuleSignature":
2716 this.DecompileModuleSignatureTable(table);
2717 break;
2718 case "ModuleSubstitution":
2719 this.DecompileModuleSubstitutionTable(table);
2720 break;
2721 case "MoveFile":
2722 this.DecompileMoveFileTable(table);
2723 break;
2724 case "MsiAssembly":
2725 // handled in FinalizeFileTable
2726 break;
2727 case "MsiDigitalCertificate":
2728 this.DecompileMsiDigitalCertificateTable(table);
2729 break;
2730 case "MsiDigitalSignature":
2731 this.DecompileMsiDigitalSignatureTable(table);
2732 break;
2733 case "MsiEmbeddedChainer":
2734 this.DecompileMsiEmbeddedChainerTable(table);
2735 break;
2736 case "MsiEmbeddedUI":
2737 this.DecompileMsiEmbeddedUITable(table);
2738 break;
2739 case "MsiLockPermissionsEx":
2740 this.DecompileMsiLockPermissionsExTable(table);
2741 break;
2742 case "MsiPackageCertificate":
2743 this.DecompileMsiPackageCertificateTable(table);
2744 break;
2745 case "MsiPatchCertificate":
2746 this.DecompileMsiPatchCertificateTable(table);
2747 break;
2748 case "MsiShortcutProperty":
2749 this.DecompileMsiShortcutPropertyTable(table);
2750 break;
2751 case "ODBCAttribute":
2752 this.DecompileODBCAttributeTable(table);
2753 break;
2754 case "ODBCDataSource":
2755 this.DecompileODBCDataSourceTable(table);
2756 break;
2757 case "ODBCDriver":
2758 this.DecompileODBCDriverTable(table);
2759 break;
2760 case "ODBCSourceAttribute":
2761 this.DecompileODBCSourceAttributeTable(table);
2762 break;
2763 case "ODBCTranslator":
2764 this.DecompileODBCTranslatorTable(table);
2765 break;
2766 case "PatchMetadata":
2767 this.DecompilePatchMetadataTable(table);
2768 break;
2769 case "PatchSequence":
2770 this.DecompilePatchSequenceTable(table);
2771 break;
2772 case "ProgId":
2773 this.DecompileProgIdTable(table);
2774 break;
2775 case "Properties":
2776 this.DecompilePropertiesTable(table);
2777 break;
2778 case "Property":
2779 this.DecompilePropertyTable(table);
2780 break;
2781 case "PublishComponent":
2782 this.DecompilePublishComponentTable(table);
2783 break;
2784 case "RadioButton":
2785 this.DecompileRadioButtonTable(table);
2786 break;
2787 case "Registry":
2788 this.DecompileRegistryTable(table);
2789 break;
2790 case "RegLocator":
2791 this.DecompileRegLocatorTable(table);
2792 break;
2793 case "RemoveFile":
2794 this.DecompileRemoveFileTable(table);
2795 break;
2796 case "RemoveIniFile":
2797 this.DecompileRemoveIniFileTable(table);
2798 break;
2799 case "RemoveRegistry":
2800 this.DecompileRemoveRegistryTable(table);
2801 break;
2802 case "ReserveCost":
2803 this.DecompileReserveCostTable(table);
2804 break;
2805 case "SelfReg":
2806 this.DecompileSelfRegTable(table);
2807 break;
2808 case "ServiceControl":
2809 this.DecompileServiceControlTable(table);
2810 break;
2811 case "ServiceInstall":
2812 this.DecompileServiceInstallTable(table);
2813 break;
2814 case "SFPCatalog":
2815 this.DecompileSFPCatalogTable(table);
2816 break;
2817 case "Shortcut":
2818 this.DecompileShortcutTable(table);
2819 break;
2820 case "Signature":
2821 this.DecompileSignatureTable(table);
2822 break;
2823 case "TargetFiles_OptionalData":
2824 this.DecompileTargetFiles_OptionalDataTable(table);
2825 break;
2826 case "TargetImages":
2827 this.DecompileTargetImagesTable(table);
2828 break;
2829 case "TextStyle":
2830 this.DecompileTextStyleTable(table);
2831 break;
2832 case "TypeLib":
2833 this.DecompileTypeLibTable(table);
2834 break;
2835 case "Upgrade":
2836 this.DecompileUpgradeTable(table);
2837 break;
2838 case "UpgradedFiles_OptionalData":
2839 this.DecompileUpgradedFiles_OptionalDataTable(table);
2840 break;
2841 case "UpgradedFilesToIgnore":
2842 this.DecompileUpgradedFilesToIgnoreTable(table);
2843 break;
2844 case "UpgradedImages":
2845 this.DecompileUpgradedImagesTable(table);
2846 break;
2847 case "UIText":
2848 this.DecompileUITextTable(table);
2849 break;
2850 case "Verb":
2851 this.DecompileVerbTable(table);
2852 break;
2853
2854 default:
2855 if (this.ExtensionsByTableName.TryGetValue(table.Name, out var extension))
2856 {
2857 extension.TryDecompileTable(table);
2858 }
2859 else if (!this.SuppressCustomTables)
2860 {
2861 this.DecompileCustomTable(table);
2862 }
2863 break;
2864 }
2865 }
2866 }
2867
2868 /// <summary>
2869 /// Determine if a particular table should be decompiled with the current settings.
2870 /// </summary>
2871 /// <param name="output">The output being decompiled.</param>
2872 /// <param name="tableName">The name of a table.</param>
2873 /// <returns>true if the table should be decompiled; false otherwise.</returns>
2874 private bool DecompilableTable(WindowsInstallerData output, string tableName)
2875 {
2876 switch (tableName)
2877 {
2878 case "ActionText":
2879 case "BBControl":
2880 case "Billboard":
2881 case "CheckBox":
2882 case "Control":
2883 case "ControlCondition":
2884 case "ControlEvent":
2885 case "Dialog":
2886 case "Error":
2887 case "EventMapping":
2888 case "RadioButton":
2889 case "TextStyle":
2890 case "UIText":
2891 return !this.SuppressUI;
2892 case "ModuleAdminExecuteSequence":
2893 case "ModuleAdminUISequence":
2894 case "ModuleAdvtExecuteSequence":
2895 case "ModuleAdvtUISequence":
2896 case "ModuleComponents":
2897 case "ModuleConfiguration":
2898 case "ModuleDependency":
2899 case "ModuleIgnoreTable":
2900 case "ModuleInstallExecuteSequence":
2901 case "ModuleInstallUISequence":
2902 case "ModuleExclusion":
2903 case "ModuleSignature":
2904 case "ModuleSubstitution":
2905 if (OutputType.Module != output.Type)
2906 {
2907 this.Messaging.Write(WarningMessages.SkippingMergeModuleTable(output.SourceLineNumbers, tableName));
2908 return false;
2909 }
2910 else
2911 {
2912 return true;
2913 }
2914 case "ExternalFiles":
2915 case "FamilyFileRanges":
2916 case "ImageFamilies":
2917 case "PatchMetadata":
2918 case "PatchSequence":
2919 case "Properties":
2920 case "TargetFiles_OptionalData":
2921 case "TargetImages":
2922 case "UpgradedFiles_OptionalData":
2923 case "UpgradedFilesToIgnore":
2924 case "UpgradedImages":
2925 if (OutputType.PatchCreation != output.Type)
2926 {
2927 this.Messaging.Write(WarningMessages.SkippingPatchCreationTable(output.SourceLineNumbers, tableName));
2928 return false;
2929 }
2930 else
2931 {
2932 return true;
2933 }
2934 case "MsiPatchHeaders":
2935 case "MsiPatchMetadata":
2936 case "MsiPatchOldAssemblyName":
2937 case "MsiPatchOldAssemblyFile":
2938 case "MsiPatchSequence":
2939 case "Patch":
2940 case "PatchPackage":
2941 this.Messaging.Write(WarningMessages.PatchTable(output.SourceLineNumbers, tableName));
2942 return false;
2943 case "_SummaryInformation":
2944 return true;
2945 case "_Validation":
2946 case "MsiAssemblyName":
2947 case "MsiFileHash":
2948 return false;
2949 default: // all other tables are allowed in any output except for a patch creation package
2950 if (OutputType.PatchCreation == output.Type)
2951 {
2952 this.Messaging.Write(WarningMessages.IllegalPatchCreationTable(output.SourceLineNumbers, tableName));
2953 return false;
2954 }
2955 else
2956 {
2957 return true;
2958 }
2959 }
2960 }
2961
2962 /// <summary>
2963 /// Decompile the _SummaryInformation table.
2964 /// </summary>
2965 /// <param name="tables">The tables to decompile.</param>
2966 private void FinalizeSummaryInformationStream(TableIndexedCollection tables)
2967 {
2968 var table = tables["_SummaryInformation"];
2969
2970 if (OutputType.Module == this.OutputType || OutputType.Package == this.OutputType)
2971 {
2972 var xSummaryInformation = new XElement(Names.SummaryInformationElement);
2973
2974 foreach (var row in table.Rows)
2975 {
2976 var value = row.FieldAsString(1);
2977
2978 if (!String.IsNullOrEmpty(value))
2979 {
2980 switch (row.FieldAsInteger(0))
2981 {
2982 case 1:
2983 if ("1252" != value)
2984 {
2985 xSummaryInformation.SetAttributeValue("Codepage", value);
2986 }
2987 break;
2988 case 3:
2989 {
2990 var productName = this.DecompilerHelper.RootElement.Attribute("Name")?.Value;
2991 if (value != productName)
2992 {
2993 xSummaryInformation.SetAttributeValue("Description", value);
2994 }
2995 break;
2996 }
2997 case 4:
2998 {
2999 var productManufacturer = this.DecompilerHelper.RootElement.Attribute("Manufacturer")?.Value;
3000 if (value != productManufacturer)
3001 {
3002 xSummaryInformation.SetAttributeValue("Manufacturer", value);
3003 }
3004 break;
3005 }
3006 case 5:
3007 if ("Installer" != value)
3008 {
3009 xSummaryInformation.SetAttributeValue("Keywords", value);
3010 }
3011 break;
3012 case 7:
3013 var template = value.Split(';');
3014 if (0 < template.Length && 0 < template[template.Length - 1].Length)
3015 {
3016 this.DecompilerHelper.RootElement.SetAttributeValue("Language", template[template.Length - 1]);
3017 }
3018 break;
3019 case 14:
3020 var installerVersion = row.FieldAsInteger(1);
3021 // Default InstallerVersion.
3022 if (installerVersion != 500)
3023 {
3024 this.DecompilerHelper.RootElement.SetAttributeValue("InstallerVersion", installerVersion);
3025 }
3026 break;
3027 case 15:
3028 var wordCount = row.FieldAsInteger(1);
3029 if (0x1 == (wordCount & 0x1))
3030 {
3031 this.ShortNames = true;
3032 if (OutputType.Package == this.OutputType)
3033 {
3034 this.DecompilerHelper.RootElement.SetAttributeValue("ShortNames", "yes");
3035 }
3036 }
3037
3038 if (0x2 == (wordCount & 0x2))
3039 {
3040 this.Compressed = true;
3041 }
3042
3043 if (OutputType.Package == this.OutputType)
3044 {
3045 if (0x8 == (wordCount & 0x8))
3046 {
3047 this.DecompilerHelper.RootElement.SetAttributeValue("Scope", "perUser");
3048 }
3049 else
3050 {
3051 var xAllUsers = this.DecompilerHelper.RootElement.Elements(Names.PropertyElement).SingleOrDefault(p => p.Attribute("Id")?.Value == "ALLUSERS");
3052 if (xAllUsers?.Attribute("Value")?.Value == "1")
3053 {
3054 xAllUsers?.Remove();
3055 }
3056 }
3057 }
3058
3059 break;
3060 }
3061 }
3062 }
3063
3064 if (OutputType.Package == this.OutputType && !this.Compressed)
3065 {
3066 this.DecompilerHelper.RootElement.SetAttributeValue("Compressed", "no");
3067 }
3068
3069 if (xSummaryInformation.HasAttributes)
3070 {
3071 this.DecompilerHelper.AddElementToRoot(xSummaryInformation);
3072 }
3073 }
3074 else
3075 {
3076 var xPatchInformation = new XElement(Names.PatchInformationElement);
3077
3078 foreach (var row in table.Rows)
3079 {
3080 var propertyId = row.FieldAsInteger(0);
3081 var value = row.FieldAsString(1);
3082
3083 if (!String.IsNullOrEmpty(value))
3084 {
3085 switch (propertyId)
3086 {
3087 case 1:
3088 if ("1252" != value)
3089 {
3090 xPatchInformation.SetAttributeValue("SummaryCodepage", value);
3091 }
3092 break;
3093 case 3:
3094 xPatchInformation.SetAttributeValue("Description", value);
3095 break;
3096 case 4:
3097 xPatchInformation.SetAttributeValue("Manufacturer", value);
3098 break;
3099 case 5:
3100 if ("Installer,Patching,PCP,Database" != value)
3101 {
3102 xPatchInformation.SetAttributeValue("Keywords", value);
3103 }
3104 break;
3105 case 6:
3106 xPatchInformation.SetAttributeValue("Comments", value);
3107 break;
3108 case 19:
3109 var security = Convert.ToInt32(value, CultureInfo.InvariantCulture);
3110 switch (security)
3111 {
3112 case 0:
3113 xPatchInformation.SetAttributeValue("ReadOnly", "no");
3114 break;
3115 case 4:
3116 xPatchInformation.SetAttributeValue("ReadOnly", "yes");
3117 break;
3118 }
3119 break;
3120 }
3121 }
3122 }
3123
3124 this.DecompilerHelper.AddElementToRoot(xPatchInformation);
3125 }
3126 }
3127
3128 /// <summary>
3129 /// Decompile the ActionText table.
3130 /// </summary>
3131 /// <param name="table">The table to decompile.</param>
3132 private void DecompileActionTextTable(Table table)
3133 {
3134 foreach (var row in table.Rows)
3135 {
3136 var progressText = new XElement(Names.ProgressTextElement,
3137 new XAttribute("Action", row.FieldAsString(0)),
3138 row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)),
3139 row.IsColumnNull(2) ? null : new XAttribute("Template", row.FieldAsString(2)));
3140
3141 this.UIElement.Add(progressText);
3142 }
3143 }
3144
3145 /// <summary>
3146 /// Decompile the AppId table.
3147 /// </summary>
3148 /// <param name="table">The table to decompile.</param>
3149 private void DecompileAppIdTable(Table table)
3150 {
3151 foreach (var row in table.Rows)
3152 {
3153 var appId = new XElement(Names.AppIdElement,
3154 new XAttribute("Advertise", "yes"),
3155 new XAttribute("Id", row.FieldAsString(0)),
3156 row.IsColumnNull(1) ? null : new XAttribute("RemoteServerName", row.FieldAsString(1)),
3157 row.IsColumnNull(2) ? null : new XAttribute("LocalService", row.FieldAsString(2)),
3158 row.IsColumnNull(3) ? null : new XAttribute("ServiceParameters", row.FieldAsString(3)),
3159 row.IsColumnNull(4) ? null : new XAttribute("DllSurrogate", row.FieldAsString(4)),
3160 row.IsColumnNull(5) || row.FieldAsInteger(5) != 1 ? null : new XAttribute("ActivateAtStorage", "yes"),
3161 row.IsColumnNull(6) || row.FieldAsInteger(6) != 1 ? null : new XAttribute("RunAsInteractiveUser", "yes"));
3162
3163 this.DecompilerHelper.AddElementToRoot(appId);
3164 this.DecompilerHelper.IndexElement(row, appId);
3165 }
3166 }
3167
3168 /// <summary>
3169 /// Decompile the BBControl table.
3170 /// </summary>
3171 /// <param name="table">The table to decompile.</param>
3172 private void DecompileBBControlTable(Table table)
3173 {
3174 foreach (BBControlRow bbControlRow in table.Rows)
3175 {
3176 var xControl = new XElement(Names.ControlElement,
3177 new XAttribute("Id", bbControlRow.BBControl),
3178 new XAttribute("Type", bbControlRow.Type),
3179 new XAttribute("X", bbControlRow.X),
3180 new XAttribute("Y", bbControlRow.Y),
3181 new XAttribute("Width", bbControlRow.Width),
3182 new XAttribute("Height", bbControlRow.Height),
3183 null == bbControlRow.Text ? null : new XAttribute("Text", bbControlRow.Text));
3184
3185 if (null != bbControlRow[7])
3186 {
3187 SetControlAttributes(bbControlRow.Attributes, xControl);
3188 }
3189
3190 if (this.DecompilerHelper.TryGetIndexedElement("Billboard", bbControlRow.Billboard, out var xBillboard))
3191 {
3192 xBillboard.Add(xControl);
3193 }
3194 else
3195 {
3196 this.Messaging.Write(WarningMessages.ExpectedForeignRow(bbControlRow.SourceLineNumbers, table.Name, bbControlRow.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Billboard_", bbControlRow.Billboard, "Billboard"));
3197 }
3198 }
3199 }
3200
3201 /// <summary>
3202 /// Decompile the Billboard table.
3203 /// </summary>
3204 /// <param name="table">The table to decompile.</param>
3205 private void DecompileBillboardTable(Table table)
3206 {
3207 var billboards = new SortedList<string, Row>();
3208
3209 foreach (var row in table.Rows)
3210 {
3211 var xBillboard = new XElement(Names.BillboardElement,
3212 new XAttribute("Id", row.FieldAsString(0)),
3213 new XAttribute("Feature", row.FieldAsString(1)));
3214
3215 this.DecompilerHelper.IndexElement(row, xBillboard);
3216 billboards.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1:0000000000}", row[0], row[3]), row);
3217 }
3218
3219 var billboardActions = new Dictionary<string, XElement>();
3220
3221 foreach (var row in billboards.Values)
3222 {
3223 var xBillboard = this.DecompilerHelper.GetIndexedElement(row);
3224
3225 if (!billboardActions.TryGetValue(row.FieldAsString(2), out var xBillboardAction))
3226 {
3227 xBillboardAction = new XElement(Names.BillboardActionElement,
3228 new XAttribute("Id", row.FieldAsString(2)));
3229
3230 this.UIElement.Add(xBillboardAction);
3231 billboardActions.Add(row.FieldAsString(2), xBillboardAction);
3232 }
3233
3234 xBillboardAction.Add(xBillboard);
3235 }
3236 }
3237
3238 /// <summary>
3239 /// Decompile the Binary table.
3240 /// </summary>
3241 /// <param name="table">The table to decompile.</param>
3242 private void DecompileBinaryTable(Table table)
3243 {
3244 foreach (var row in table.Rows)
3245 {
3246 var xBinary = new XElement(Names.BinaryElement,
3247 new XAttribute("Id", row.FieldAsString(0)),
3248 new XAttribute("SourceFile", row.FieldAsString(1)));
3249
3250 this.DecompilerHelper.AddElementToRoot(xBinary);
3251 }
3252 }
3253
3254 /// <summary>
3255 /// Decompile the BindImage table.
3256 /// </summary>
3257 /// <param name="table">The table to decompile.</param>
3258 private void DecompileBindImageTable(Table table)
3259 {
3260 foreach (var row in table.Rows)
3261 {
3262 if (this.DecompilerHelper.TryGetIndexedElement("File", row.FieldAsString(0), out var xFile))
3263 {
3264 xFile.SetAttributeValue("BindPath", row.FieldAsString(1));
3265 }
3266 else
3267 {
3268 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
3269 }
3270 }
3271 }
3272
3273 /// <summary>
3274 /// Decompile the Class table.
3275 /// </summary>
3276 /// <param name="table">The table to decompile.</param>
3277 private void DecompileClassTable(Table table)
3278 {
3279 foreach (var row in table.Rows)
3280 {
3281 var xClass = new XElement(Names.ClassElement,
3282 new XAttribute("Id", row.FieldAsString(0)),
3283 new XAttribute("Advertise", "yes"),
3284 new XAttribute("Context", row.FieldAsString(1)),
3285 row.IsColumnNull(4) ? null : new XAttribute("Description", row.FieldAsString(4)),
3286 row.IsColumnNull(5) ? null : new XAttribute("AppId", row.FieldAsString(5)),
3287 row.IsColumnNull(7) ? null : new XAttribute("Icon", row.FieldAsString(7)),
3288 row.IsColumnNull(8) ? null : new XAttribute("IconIndex", row.FieldAsString(8)),
3289 row.IsColumnNull(9) ? null : new XAttribute("Handler", row.FieldAsString(9)),
3290 row.IsColumnNull(10) ? null : new XAttribute("Argument", row.FieldAsString(10)));
3291
3292 if (!row.IsColumnNull(6))
3293 {
3294 var fileTypeMaskStrings = row.FieldAsString(6).Split(';');
3295
3296 try
3297 {
3298 foreach (var fileTypeMaskString in fileTypeMaskStrings)
3299 {
3300 var fileTypeMaskParts = fileTypeMaskString.Split(',');
3301
3302 if (4 == fileTypeMaskParts.Length)
3303 {
3304 var xFileTypeMask = new XElement(Names.FileTypeMaskElement,
3305 new XAttribute("Offset", Convert.ToInt32(fileTypeMaskParts[0], CultureInfo.InvariantCulture)),
3306 new XAttribute("Mask", fileTypeMaskParts[2]),
3307 new XAttribute("Value", fileTypeMaskParts[3]));
3308
3309 xClass.Add(xFileTypeMask);
3310 }
3311 else
3312 {
3313 // TODO: warn
3314 }
3315 }
3316 }
3317 catch (FormatException)
3318 {
3319 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
3320 }
3321 catch (OverflowException)
3322 {
3323 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
3324 }
3325 }
3326
3327 if (!row.IsColumnNull(12))
3328 {
3329 if (1 == row.FieldAsInteger(12))
3330 {
3331 xClass.SetAttributeValue("RelativePath", "yes");
3332 }
3333 else
3334 {
3335 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[12].Column.Name, row[12]));
3336 }
3337 }
3338
3339 this.AddChildToParent("Component", xClass, row, 2);
3340 this.DecompilerHelper.IndexElement(row, xClass);
3341 }
3342 }
3343
3344 /// <summary>
3345 /// Decompile the ComboBox table.
3346 /// </summary>
3347 /// <param name="table">The table to decompile.</param>
3348 private void DecompileComboBoxTable(Table table)
3349 {
3350 // sort the combo boxes by their property and order
3351 var comboBoxRows = table.Rows.Select(row => row).OrderBy(row => String.Format("{0}|{1:0000000000}", row.FieldAsString(0), row.FieldAsInteger(1)));
3352
3353 XElement xComboBox = null;
3354 string property = null;
3355 foreach (var row in comboBoxRows)
3356 {
3357 if (null == xComboBox || row.FieldAsString(0) != property)
3358 {
3359 property = row.FieldAsString(0);
3360
3361 xComboBox = new XElement(Names.ComboBoxElement,
3362 new XAttribute("Property", property));
3363
3364 this.UIElement.Add(xComboBox);
3365 }
3366
3367 var xListItem = new XElement(Names.ListItemElement,
3368 new XAttribute("Value", row.FieldAsString(2)),
3369 row.IsColumnNull(3) ? null : new XAttribute("Text", row.FieldAsString(3)));
3370 xComboBox.Add(xListItem);
3371 }
3372 }
3373
3374 /// <summary>
3375 /// Decompile the Control table.
3376 /// </summary>
3377 /// <param name="table">The table to decompile.</param>
3378 private void DecompileControlTable(Table table)
3379 {
3380 foreach (ControlRow controlRow in table.Rows)
3381 {
3382 var xControl = new XElement(Names.ControlElement,
3383 new XAttribute("Id", controlRow.Control),
3384 new XAttribute("Type", controlRow.Type),
3385 new XAttribute("X", controlRow.X),
3386 new XAttribute("Y", controlRow.Y),
3387 new XAttribute("Width", controlRow.Width),
3388 new XAttribute("Height", controlRow.Height),
3389 XAttributeIfNotNull("Text", controlRow.Text));
3390
3391 if (!controlRow.IsColumnNull(7))
3392 {
3393 string[] specialAttributes;
3394
3395 // sets various common attributes like Disabled, Indirect, Integer, ...
3396 SetControlAttributes(controlRow.Attributes, xControl);
3397
3398 switch (controlRow.Type)
3399 {
3400 case "Bitmap":
3401 specialAttributes = BitmapControlAttributes;
3402 break;
3403 case "CheckBox":
3404 specialAttributes = CheckboxControlAttributes;
3405 break;
3406 case "ComboBox":
3407 specialAttributes = ComboboxControlAttributes;
3408 break;
3409 case "DirectoryCombo":
3410 specialAttributes = VolumeControlAttributes;
3411 break;
3412 case "Edit":
3413 specialAttributes = EditControlAttributes;
3414 break;
3415 case "Icon":
3416 specialAttributes = IconControlAttributes;
3417 break;
3418 case "ListBox":
3419 specialAttributes = ListboxControlAttributes;
3420 break;
3421 case "ListView":
3422 specialAttributes = ListviewControlAttributes;
3423 break;
3424 case "MaskedEdit":
3425 specialAttributes = EditControlAttributes;
3426 break;
3427 case "PathEdit":
3428 specialAttributes = EditControlAttributes;
3429 break;
3430 case "ProgressBar":
3431 specialAttributes = ProgressControlAttributes;
3432 break;
3433 case "PushButton":
3434 specialAttributes = ButtonControlAttributes;
3435 break;
3436 case "RadioButtonGroup":
3437 specialAttributes = RadioControlAttributes;
3438 break;
3439 case "Text":
3440 specialAttributes = TextControlAttributes;
3441 break;
3442 case "VolumeCostList":
3443 specialAttributes = VolumeControlAttributes;
3444 break;
3445 case "VolumeSelectCombo":
3446 specialAttributes = VolumeControlAttributes;
3447 break;
3448 default:
3449 specialAttributes = null;
3450 break;
3451 }
3452
3453 if (null != specialAttributes)
3454 {
3455 var iconSizeSet = false;
3456
3457 for (var i = 16; 32 > i; i++)
3458 {
3459 if (1 == ((controlRow.Attributes >> i) & 1))
3460 {
3461 string attribute = null;
3462
3463 if (specialAttributes.Length > (i - 16))
3464 {
3465 attribute = specialAttributes[i - 16];
3466 }
3467
3468 // unknown attribute
3469 if (null == attribute)
3470 {
3471 this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
3472 continue;
3473 }
3474
3475 switch (attribute)
3476 {
3477 case "Bitmap":
3478 xControl.SetAttributeValue("Bitmap", "yes");
3479 break;
3480 case "CDROM":
3481 xControl.SetAttributeValue("CDROM", "yes");
3482 break;
3483 case "ComboList":
3484 xControl.SetAttributeValue("ComboList", "yes");
3485 break;
3486 case "ElevationShield":
3487 xControl.SetAttributeValue("ElevationShield", "yes");
3488 break;
3489 case "Fixed":
3490 xControl.SetAttributeValue("Fixed", "yes");
3491 break;
3492 case "FixedSize":
3493 xControl.SetAttributeValue("FixedSize", "yes");
3494 break;
3495 case "Floppy":
3496 xControl.SetAttributeValue("Floppy", "yes");
3497 break;
3498 case "FormatSize":
3499 xControl.SetAttributeValue("FormatSize", "yes");
3500 break;
3501 case "HasBorder":
3502 xControl.SetAttributeValue("HasBorder", "yes");
3503 break;
3504 case "Icon":
3505 xControl.SetAttributeValue("Icon", "yes");
3506 break;
3507 case "Icon16":
3508 if (iconSizeSet)
3509 {
3510 xControl.SetAttributeValue("IconSize", "48");
3511 }
3512 else
3513 {
3514 iconSizeSet = true;
3515 xControl.SetAttributeValue("IconSize", "16");
3516 }
3517 break;
3518 case "Icon32":
3519 if (iconSizeSet)
3520 {
3521 xControl.SetAttributeValue("IconSize", "48");
3522 }
3523 else
3524 {
3525 iconSizeSet = true;
3526 xControl.SetAttributeValue("IconSize", "32");
3527 }
3528 break;
3529 case "Image":
3530 xControl.SetAttributeValue("Image", "yes");
3531 break;
3532 case "Multiline":
3533 xControl.SetAttributeValue("Multiline", "yes");
3534 break;
3535 case "NoPrefix":
3536 xControl.SetAttributeValue("NoPrefix", "yes");
3537 break;
3538 case "NoWrap":
3539 xControl.SetAttributeValue("NoWrap", "yes");
3540 break;
3541 case "Password":
3542 xControl.SetAttributeValue("Password", "yes");
3543 break;
3544 case "ProgressBlocks":
3545 xControl.SetAttributeValue("ProgressBlocks", "yes");
3546 break;
3547 case "PushLike":
3548 xControl.SetAttributeValue("PushLike", "yes");
3549 break;
3550 case "RAMDisk":
3551 xControl.SetAttributeValue("RAMDisk", "yes");
3552 break;
3553 case "Remote":
3554 xControl.SetAttributeValue("Remote", "yes");
3555 break;
3556 case "Removable":
3557 xControl.SetAttributeValue("Removable", "yes");
3558 break;
3559 case "ShowRollbackCost":
3560 xControl.SetAttributeValue("ShowRollbackCost", "yes");
3561 break;
3562 case "Sorted":
3563 xControl.SetAttributeValue("Sorted", "yes");
3564 break;
3565 case "Transparent":
3566 xControl.SetAttributeValue("Transparent", "yes");
3567 break;
3568 case "UserLanguage":
3569 xControl.SetAttributeValue("UserLanguage", "yes");
3570 break;
3571 default:
3572 throw new InvalidOperationException($"Unknown control attribute: '{attribute}'.");
3573 }
3574 }
3575 }
3576 }
3577 else if (0 < (controlRow.Attributes & 0xFFFF0000))
3578 {
3579 this.Messaging.Write(WarningMessages.IllegalColumnValue(controlRow.SourceLineNumbers, table.Name, controlRow.Fields[7].Column.Name, controlRow.Attributes));
3580 }
3581 }
3582
3583 // FinalizeCheckBoxTable adds Control/@Property|@CheckBoxPropertyRef
3584 if (null != controlRow.Property && 0 != String.CompareOrdinal("CheckBox", controlRow.Type))
3585 {
3586 xControl.SetAttributeValue("Property", controlRow.Property);
3587 }
3588
3589 if (null != controlRow.Help)
3590 {
3591 var help = controlRow.Help.Split('|');
3592
3593 if (2 == help.Length)
3594 {
3595 if (0 < help[0].Length)
3596 {
3597 xControl.SetAttributeValue("ToolTip", help[0]);
3598 }
3599
3600 if (0 < help[1].Length)
3601 {
3602 xControl.SetAttributeValue("Help", help[1]);
3603 }
3604 }
3605 }
3606
3607 this.DecompilerHelper.IndexElement(controlRow, xControl);
3608 }
3609 }
3610
3611 /// <summary>
3612 /// Decompile the ControlCondition table.
3613 /// </summary>
3614 /// <param name="table">The table to decompile.</param>
3615 private void DecompileControlConditionTable(Table table)
3616 {
3617 foreach (var row in table.Rows)
3618 {
3619 if (this.DecompilerHelper.TryGetIndexedElement("Control", row.FieldAsString(0), row.FieldAsString(1), out var xControl))
3620 {
3621 switch (row.FieldAsString(2))
3622 {
3623 case "Default":
3624 xControl.SetAttributeValue("DefaultCondition", row.FieldAsString(3));
3625 break;
3626 case "Disable":
3627 xControl.SetAttributeValue("DisableCondition", row.FieldAsString(3));
3628 break;
3629 case "Enable":
3630 xControl.SetAttributeValue("EnableCondition", row.FieldAsString(3));
3631 break;
3632 case "Hide":
3633 xControl.SetAttributeValue("HideCondition", row.FieldAsString(3));
3634 break;
3635 case "Show":
3636 xControl.SetAttributeValue("ShowCondition", row.FieldAsString(3));
3637 break;
3638 default:
3639 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
3640 break;
3641 }
3642 }
3643 else
3644 {
3645 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
3646 }
3647 }
3648 }
3649
3650 /// <summary>
3651 /// Decompile the ControlEvent table.
3652 /// </summary>
3653 /// <param name="table">The table to decompile.</param>
3654 private void DecompileControlEventTable(Table table)
3655 {
3656 var controlEvents = new SortedList<string, Row>();
3657
3658 foreach (var row in table.Rows)
3659 {
3660 var xPublish = new XElement(Names.PublishElement);
3661 var condition = row.FieldAsString(4);
3662
3663 if (!String.IsNullOrEmpty(condition) && condition != "1")
3664 {
3665 xPublish.Add(new XAttribute("Condition", condition));
3666 }
3667
3668 var publishEvent = row.FieldAsString(2);
3669 if (publishEvent.StartsWith("[", StringComparison.Ordinal) && publishEvent.EndsWith("]", StringComparison.Ordinal))
3670 {
3671 xPublish.SetAttributeValue("Property", publishEvent.Substring(1, publishEvent.Length - 2));
3672
3673 if ("{}" != row.FieldAsString(3))
3674 {
3675 xPublish.SetAttributeValue("Value", row.FieldAsString(3));
3676 }
3677 }
3678 else
3679 {
3680 xPublish.SetAttributeValue("Event", publishEvent);
3681 xPublish.SetAttributeValue("Value", row.FieldAsString(3));
3682 }
3683
3684 controlEvents.Add(String.Format(CultureInfo.InvariantCulture, "{0}|{1}|{2:0000000000}|{3}|{4}|{5}", row.FieldAsString(0), row.FieldAsString(1), row.FieldAsNullableInteger(5) ?? 0, row.FieldAsString(2), row.FieldAsString(3), row.FieldAsString(4)), row);
3685
3686 this.DecompilerHelper.IndexElement(row, xPublish);
3687 }
3688
3689 foreach (var row in controlEvents.Values)
3690 {
3691 if (this.DecompilerHelper.TryGetIndexedElement("Control", row.FieldAsString(0), row.FieldAsString(1), out var xControl))
3692 {
3693 var xPublish = this.DecompilerHelper.GetIndexedElement(row);
3694 xControl.Add(xPublish);
3695 }
3696 else
3697 {
3698 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
3699 }
3700 }
3701 }
3702
3703 /// <summary>
3704 /// Decompile a custom table.
3705 /// </summary>
3706 /// <param name="table">The table to decompile.</param>
3707 private void DecompileCustomTable(Table table)
3708 {
3709 if (0 < table.Rows.Count || this.SuppressDroppingEmptyTables)
3710 {
3711 this.Messaging.Write(WarningMessages.DecompilingAsCustomTable(table.Rows[0].SourceLineNumbers, table.Name));
3712
3713 var xCustomTable = new XElement(Names.CustomTableElement,
3714 new XAttribute("Id", table.Name));
3715
3716 foreach (var columnDefinition in table.Definition.Columns)
3717 {
3718 var xColumn = new XElement(Names.ColumnElement,
3719 new XAttribute("Id", columnDefinition.Name),
3720 columnDefinition.Description == null ? null : new XAttribute("Description", columnDefinition.Description),
3721 columnDefinition.KeyTable == null ? null : new XAttribute("KeyTable", columnDefinition.KeyTable),
3722 !columnDefinition.KeyColumn.HasValue ? null : new XAttribute("KeyColumn", columnDefinition.KeyColumn.Value),
3723 !columnDefinition.IsLocalizable ? null : new XAttribute("Localizable", "yes"),
3724 !columnDefinition.MaxValue.HasValue ? null : new XAttribute("MaxValue", columnDefinition.MaxValue.Value),
3725 !columnDefinition.MinValue.HasValue ? null : new XAttribute("MinValue", columnDefinition.MinValue.Value),
3726 !columnDefinition.Nullable ? null : new XAttribute("Nullable", "yes"),
3727 !columnDefinition.PrimaryKey ? null : new XAttribute("PrimaryKey", "yes"),
3728 columnDefinition.Possibilities == null ? null : new XAttribute("Possibilities", "yes"),
3729 new XAttribute("Width", columnDefinition.Length));
3730
3731 if (ColumnCategory.Unknown != columnDefinition.Category)
3732 {
3733 switch (columnDefinition.Category)
3734 {
3735 case ColumnCategory.Text:
3736 xColumn.SetAttributeValue("Category", "text");
3737 break;
3738 case ColumnCategory.UpperCase:
3739 xColumn.SetAttributeValue("Category", "upperCase");
3740 break;
3741 case ColumnCategory.LowerCase:
3742 xColumn.SetAttributeValue("Category", "lowerCase");
3743 break;
3744 case ColumnCategory.Integer:
3745 xColumn.SetAttributeValue("Category", "integer");
3746 break;
3747 case ColumnCategory.DoubleInteger:
3748 xColumn.SetAttributeValue("Category", "doubleInteger");
3749 break;
3750 case ColumnCategory.TimeDate:
3751 xColumn.SetAttributeValue("Category", "timeDate");
3752 break;
3753 case ColumnCategory.Identifier:
3754 xColumn.SetAttributeValue("Category", "identifier");
3755 break;
3756 case ColumnCategory.Property:
3757 xColumn.SetAttributeValue("Category", "property");
3758 break;
3759 case ColumnCategory.Filename:
3760 xColumn.SetAttributeValue("Category", "filename");
3761 break;
3762 case ColumnCategory.WildCardFilename:
3763 xColumn.SetAttributeValue("Category", "wildCardFilename");
3764 break;
3765 case ColumnCategory.Path:
3766 xColumn.SetAttributeValue("Category", "path");
3767 break;
3768 case ColumnCategory.Paths:
3769 xColumn.SetAttributeValue("Category", "paths");
3770 break;
3771 case ColumnCategory.AnyPath:
3772 xColumn.SetAttributeValue("Category", "anyPath");
3773 break;
3774 case ColumnCategory.DefaultDir:
3775 xColumn.SetAttributeValue("Category", "defaultDir");
3776 break;
3777 case ColumnCategory.RegPath:
3778 xColumn.SetAttributeValue("Category", "regPath");
3779 break;
3780 case ColumnCategory.Formatted:
3781 xColumn.SetAttributeValue("Category", "formatted");
3782 break;
3783 case ColumnCategory.FormattedSDDLText:
3784 xColumn.SetAttributeValue("Category", "formattedSddl");
3785 break;
3786 case ColumnCategory.Template:
3787 xColumn.SetAttributeValue("Category", "template");
3788 break;
3789 case ColumnCategory.Condition:
3790 xColumn.SetAttributeValue("Category", "condition");
3791 break;
3792 case ColumnCategory.Guid:
3793 xColumn.SetAttributeValue("Category", "guid");
3794 break;
3795 case ColumnCategory.Version:
3796 xColumn.SetAttributeValue("Category", "version");
3797 break;
3798 case ColumnCategory.Language:
3799 xColumn.SetAttributeValue("Category", "language");
3800 break;
3801 case ColumnCategory.Binary:
3802 xColumn.SetAttributeValue("Category", "binary");
3803 break;
3804 case ColumnCategory.CustomSource:
3805 xColumn.SetAttributeValue("Category", "customSource");
3806 break;
3807 case ColumnCategory.Cabinet:
3808 xColumn.SetAttributeValue("Category", "cabinet");
3809 break;
3810 case ColumnCategory.Shortcut:
3811 xColumn.SetAttributeValue("Category", "shortcut");
3812 break;
3813 default:
3814 throw new InvalidOperationException($"Unknown custom column category '{columnDefinition.Category.ToString()}'.");
3815 }
3816 }
3817
3818 if (ColumnModularizeType.None != columnDefinition.ModularizeType)
3819 {
3820 switch (columnDefinition.ModularizeType)
3821 {
3822 case ColumnModularizeType.Column:
3823 xColumn.SetAttributeValue("Modularize", "Column");
3824 break;
3825 case ColumnModularizeType.Condition:
3826 xColumn.SetAttributeValue("Modularize", "Condition");
3827 break;
3828 case ColumnModularizeType.Icon:
3829 xColumn.SetAttributeValue("Modularize", "Icon");
3830 break;
3831 case ColumnModularizeType.Property:
3832 xColumn.SetAttributeValue("Modularize", "Property");
3833 break;
3834 case ColumnModularizeType.SemicolonDelimited:
3835 xColumn.SetAttributeValue("Modularize", "SemicolonDelimited");
3836 break;
3837 default:
3838 throw new InvalidOperationException($"Unknown custom column modularization type '{columnDefinition.ModularizeType.ToString()}'.");
3839 }
3840 }
3841
3842 if (ColumnType.Unknown != columnDefinition.Type)
3843 {
3844 switch (columnDefinition.Type)
3845 {
3846 case ColumnType.Localized:
3847 xColumn.SetAttributeValue("Localizable", "yes");
3848 xColumn.SetAttributeValue("Type", "string");
3849 break;
3850 case ColumnType.Number:
3851 xColumn.SetAttributeValue("Type", "int");
3852 break;
3853 case ColumnType.Object:
3854 xColumn.SetAttributeValue("Type", "binary");
3855 break;
3856 case ColumnType.Preserved:
3857 case ColumnType.String:
3858 xColumn.SetAttributeValue("Type", "string");
3859 break;
3860 default:
3861 throw new InvalidOperationException($"Unknown custom column type '{columnDefinition.Type}'.");
3862 }
3863 }
3864
3865 xCustomTable.Add(xColumn);
3866 }
3867
3868 foreach (var row in table.Rows)
3869 {
3870 var xRow = new XElement(Names.RowElement);
3871
3872 foreach (var field in row.Fields.Where(f => f.Data != null))
3873 {
3874 var xData = new XElement(Names.DataElement,
3875 new XAttribute("Column", field.Column.Name),
3876 new XAttribute("Value", field.AsString()));
3877
3878 xRow.Add(xData);
3879 }
3880
3881 xCustomTable.Add(xRow);
3882 }
3883
3884 this.DecompilerHelper.AddElementToRoot(xCustomTable);
3885 }
3886 }
3887
3888 /// <summary>
3889 /// Decompile the CreateFolder table.
3890 /// </summary>
3891 /// <param name="table">The table to decompile.</param>
3892 private void DecompileCreateFolderTable(Table table)
3893 {
3894 foreach (var row in table.Rows)
3895 {
3896 var xCreateFolder = new XElement(Names.CreateFolderElement,
3897 new XAttribute("Directory", row.FieldAsString(0)));
3898
3899 this.AddChildToParent("Component", xCreateFolder, row, 1);
3900 this.DecompilerHelper.IndexElement(row, xCreateFolder);
3901 }
3902 }
3903
3904 /// <summary>
3905 /// Decompile the CustomAction table.
3906 /// </summary>
3907 /// <param name="table">The table to decompile.</param>
3908 private void DecompileCustomActionTable(Table table)
3909 {
3910 foreach (var row in table.Rows)
3911 {
3912 var xCustomAction = new XElement(Names.CustomActionElement,
3913 new XAttribute("Id", row.FieldAsString(0)));
3914
3915 var type = row.FieldAsInteger(1);
3916
3917 if (WindowsInstallerConstants.MsidbCustomActionTypeHideTarget == (type & WindowsInstallerConstants.MsidbCustomActionTypeHideTarget))
3918 {
3919 xCustomAction.SetAttributeValue("HideTarget", "yes");
3920 }
3921
3922 if (WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate == (type & WindowsInstallerConstants.MsidbCustomActionTypeNoImpersonate))
3923 {
3924 xCustomAction.SetAttributeValue("Impersonate", "no");
3925 }
3926
3927 if (WindowsInstallerConstants.MsidbCustomActionTypeTSAware == (type & WindowsInstallerConstants.MsidbCustomActionTypeTSAware))
3928 {
3929 xCustomAction.SetAttributeValue("TerminalServerAware", "yes");
3930 }
3931
3932 if (WindowsInstallerConstants.MsidbCustomActionType64BitScript == (type & WindowsInstallerConstants.MsidbCustomActionType64BitScript))
3933 {
3934 xCustomAction.SetAttributeValue("Bitness", "always64");
3935 }
3936 else if (WindowsInstallerConstants.MsidbCustomActionTypeVBScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeVBScript) ||
3937 WindowsInstallerConstants.MsidbCustomActionTypeJScript == (type & WindowsInstallerConstants.MsidbCustomActionTypeJScript))
3938 {
3939 xCustomAction.SetAttributeValue("Bitness", "always32");
3940 }
3941
3942 switch (type & WindowsInstallerConstants.MsidbCustomActionTypeExecuteBits)
3943 {
3944 case 0:
3945 // this is the default value
3946 break;
3947 case WindowsInstallerConstants.MsidbCustomActionTypeFirstSequence:
3948 xCustomAction.SetAttributeValue("Execute", "firstSequence");
3949 break;
3950 case WindowsInstallerConstants.MsidbCustomActionTypeOncePerProcess:
3951 xCustomAction.SetAttributeValue("Execute", "oncePerProcess");
3952 break;
3953 case WindowsInstallerConstants.MsidbCustomActionTypeClientRepeat:
3954 xCustomAction.SetAttributeValue("Execute", "secondSequence");
3955 break;
3956 case WindowsInstallerConstants.MsidbCustomActionTypeInScript:
3957 xCustomAction.SetAttributeValue("Execute", "deferred");
3958 break;
3959 case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeRollback:
3960 xCustomAction.SetAttributeValue("Execute", "rollback");
3961 break;
3962 case WindowsInstallerConstants.MsidbCustomActionTypeInScript + WindowsInstallerConstants.MsidbCustomActionTypeCommit:
3963 xCustomAction.SetAttributeValue("Execute", "commit");
3964 break;
3965 default:
3966 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
3967 break;
3968 }
3969
3970 switch (type & WindowsInstallerConstants.MsidbCustomActionTypeReturnBits)
3971 {
3972 case 0:
3973 // this is the default value
3974 break;
3975 case WindowsInstallerConstants.MsidbCustomActionTypeContinue:
3976 xCustomAction.SetAttributeValue("Return", "ignore");
3977 break;
3978 case WindowsInstallerConstants.MsidbCustomActionTypeAsync:
3979 xCustomAction.SetAttributeValue("Return", "asyncWait");
3980 break;
3981 case WindowsInstallerConstants.MsidbCustomActionTypeAsync + WindowsInstallerConstants.MsidbCustomActionTypeContinue:
3982 xCustomAction.SetAttributeValue("Return", "asyncNoWait");
3983 break;
3984 default:
3985 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
3986 break;
3987 }
3988
3989 var source = type & WindowsInstallerConstants.MsidbCustomActionTypeSourceBits;
3990 switch (source)
3991 {
3992 case WindowsInstallerConstants.MsidbCustomActionTypeBinaryData:
3993 xCustomAction.SetAttributeValue("BinaryRef", row.FieldAsString(2));
3994 break;
3995 case WindowsInstallerConstants.MsidbCustomActionTypeSourceFile:
3996 if (!row.IsColumnNull(2))
3997 {
3998 xCustomAction.SetAttributeValue("FileRef", row.FieldAsString(2));
3999 }
4000 break;
4001 case WindowsInstallerConstants.MsidbCustomActionTypeDirectory:
4002 if (!row.IsColumnNull(2))
4003 {
4004 xCustomAction.SetAttributeValue("Directory", row.FieldAsString(2));
4005 }
4006 break;
4007 case WindowsInstallerConstants.MsidbCustomActionTypeProperty:
4008 xCustomAction.SetAttributeValue("Property", row.FieldAsString(2));
4009 break;
4010 default:
4011 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4012 break;
4013 }
4014
4015 switch (type & WindowsInstallerConstants.MsidbCustomActionTypeTargetBits)
4016 {
4017 case WindowsInstallerConstants.MsidbCustomActionTypeDll:
4018 xCustomAction.SetAttributeValue("DllEntry", row.FieldAsString(3));
4019 break;
4020 case WindowsInstallerConstants.MsidbCustomActionTypeExe:
4021 xCustomAction.SetAttributeValue("ExeCommand", row.FieldAsString(3));
4022 break;
4023 case WindowsInstallerConstants.MsidbCustomActionTypeTextData:
4024 if (WindowsInstallerConstants.MsidbCustomActionTypeSourceFile == source)
4025 {
4026 xCustomAction.SetAttributeValue("Error", row.FieldAsString(3));
4027 }
4028 else
4029 {
4030 xCustomAction.SetAttributeValue("Value", row.FieldAsString(3));
4031 }
4032 break;
4033 case WindowsInstallerConstants.MsidbCustomActionTypeJScript:
4034 if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source)
4035 {
4036 xCustomAction.SetAttributeValue("Script", "jscript");
4037 // TODO: Extract to @ScriptFile?
4038 // xCustomAction.Content = row.FieldAsString(3);
4039 }
4040 else
4041 {
4042 xCustomAction.SetAttributeValue("JScriptCall", row.FieldAsString(3));
4043 }
4044 break;
4045 case WindowsInstallerConstants.MsidbCustomActionTypeVBScript:
4046 if (WindowsInstallerConstants.MsidbCustomActionTypeDirectory == source)
4047 {
4048 xCustomAction.SetAttributeValue("Script", "vbscript");
4049 // TODO: Extract to @ScriptFile?
4050 // xCustomAction.Content = row.FieldAsString(3);
4051 }
4052 else
4053 {
4054 xCustomAction.SetAttributeValue("VBScriptCall", row.FieldAsString(3));
4055 }
4056 break;
4057 case WindowsInstallerConstants.MsidbCustomActionTypeInstall:
4058 this.Messaging.Write(WarningMessages.NestedInstall(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4059 continue;
4060 default:
4061 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[1].Column.Name, row[1]));
4062 break;
4063 }
4064
4065 var extype = 4 < row.Fields.Length && !row.IsColumnNull(4) ? row.FieldAsInteger(4) : 0;
4066 if (WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall == (extype & WindowsInstallerConstants.MsidbCustomActionTypePatchUninstall))
4067 {
4068 xCustomAction.SetAttributeValue("PatchUninstall", "yes");
4069 }
4070
4071 this.DecompilerHelper.AddElementToRoot(xCustomAction);
4072 this.DecompilerHelper.IndexElement(row, xCustomAction);
4073 }
4074 }
4075
4076 /// <summary>
4077 /// Decompile the CompLocator table.
4078 /// </summary>
4079 /// <param name="table">The table to decompile.</param>
4080 private void DecompileCompLocatorTable(Table table)
4081 {
4082 foreach (var row in table.Rows)
4083 {
4084 var xComponentSearch = new XElement(Names.ComponentSearchElement,
4085 new XAttribute("Id", row.FieldAsString(0)),
4086 new XAttribute("Guid", row.FieldAsString(1)));
4087
4088 if (!row.IsColumnNull(2))
4089 {
4090 switch (row.FieldAsInteger(2))
4091 {
4092 case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
4093 xComponentSearch.SetAttributeValue("Type", "directory");
4094 break;
4095 case WindowsInstallerConstants.MsidbLocatorTypeFileName:
4096 // this is the default value
4097 break;
4098 default:
4099 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[2].Column.Name, row[2]));
4100 break;
4101 }
4102 }
4103
4104 this.DecompilerHelper.IndexElement(row, xComponentSearch);
4105 }
4106 }
4107
4108 /// <summary>
4109 /// Decompile the Complus table.
4110 /// </summary>
4111 /// <param name="table">The table to decompile.</param>
4112 private void DecompileComplusTable(Table table)
4113 {
4114 foreach (var row in table.Rows)
4115 {
4116 if (!row.IsColumnNull(1))
4117 {
4118 if (this.DecompilerHelper.TryGetIndexedElement("Component", row.FieldAsString(0), out var xComponent))
4119 {
4120 xComponent.SetAttributeValue("ComPlusFlags", row.FieldAsInteger(1));
4121 }
4122 else
4123 {
4124 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Component_", row.FieldAsString(0), "Component"));
4125 }
4126 }
4127 }
4128 }
4129
4130 /// <summary>
4131 /// Decompile the Component table.
4132 /// </summary>
4133 /// <param name="table">The table to decompile.</param>
4134 private void DecompileComponentTable(Table table)
4135 {
4136 foreach (var row in table.Rows)
4137 {
4138 var xComponent = new XElement(Names.ComponentElement,
4139 new XAttribute("Id", row.FieldAsString(0)),
4140 new XAttribute("Guid", row.FieldAsString(1) ?? String.Empty));
4141
4142 var attributes = row.FieldAsInteger(3);
4143
4144 if (WindowsInstallerConstants.MsidbComponentAttributesSourceOnly == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSourceOnly))
4145 {
4146 xComponent.SetAttributeValue("Location", "source");
4147 }
4148 else if (WindowsInstallerConstants.MsidbComponentAttributesOptional == (attributes & WindowsInstallerConstants.MsidbComponentAttributesOptional))
4149 {
4150 xComponent.SetAttributeValue("Location", "either");
4151 }
4152
4153 if (WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount == (attributes & WindowsInstallerConstants.MsidbComponentAttributesSharedDllRefCount))
4154 {
4155 xComponent.SetAttributeValue("SharedDllRefCount", "yes");
4156 }
4157
4158 if (WindowsInstallerConstants.MsidbComponentAttributesPermanent == (attributes & WindowsInstallerConstants.MsidbComponentAttributesPermanent))
4159 {
4160 xComponent.SetAttributeValue("Permanent", "yes");
4161 }
4162
4163 if (WindowsInstallerConstants.MsidbComponentAttributesTransitive == (attributes & WindowsInstallerConstants.MsidbComponentAttributesTransitive))
4164 {
4165 xComponent.SetAttributeValue("Transitive", "yes");
4166 }
4167
4168 if (WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite == (attributes & WindowsInstallerConstants.MsidbComponentAttributesNeverOverwrite))
4169 {
4170 xComponent.SetAttributeValue("NeverOverwrite", "yes");
4171 }
4172
4173 if (WindowsInstallerConstants.MsidbComponentAttributes64bit == (attributes & WindowsInstallerConstants.MsidbComponentAttributes64bit))
4174 {
4175 xComponent.SetAttributeValue("Bitness", "always64");
4176 }
4177 else
4178 {
4179 xComponent.SetAttributeValue("Bitness", "always32");
4180 }
4181
4182 if (WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection == (attributes & WindowsInstallerConstants.MsidbComponentAttributesDisableRegistryReflection))
4183 {
4184 xComponent.SetAttributeValue("DisableRegistryReflection", "yes");
4185 }
4186
4187 if (WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence == (attributes & WindowsInstallerConstants.MsidbComponentAttributesUninstallOnSupersedence))
4188 {
4189 xComponent.SetAttributeValue("UninstallWhenSuperseded", "yes");
4190 }
4191
4192 if (WindowsInstallerConstants.MsidbComponentAttributesShared == (attributes & WindowsInstallerConstants.MsidbComponentAttributesShared))
4193 {
4194 xComponent.SetAttributeValue("Shared", "yes");
4195 }
4196
4197 if (!row.IsColumnNull(4))
4198 {
4199 xComponent.SetAttributeValue("Condition", row.FieldAsString(4));
4200 }
4201
4202 this.AddChildToParent("Directory", xComponent, row, 2);
4203 this.DecompilerHelper.IndexElement(row, xComponent);
4204 }
4205 }
4206
4207 /// <summary>
4208 /// Decompile the Condition table.
4209 /// </summary>
4210 /// <param name="table">The table to decompile.</param>
4211 private void DecompileConditionTable(Table table)
4212 {
4213 foreach (var row in table.Rows)
4214 {
4215 if (this.DecompilerHelper.TryGetIndexedElement("Feature", row.FieldAsString(0), out var xFeature))
4216 {
4217 var xLevel = new XElement(Names.LevelElement,
4218 row.IsColumnNull(2) ? null : new XAttribute("Condition", row.FieldAsString(2)),
4219 new XAttribute("Level", row.FieldAsInteger(1)));
4220
4221 xFeature.Add(xLevel);
4222 }
4223 else
4224 {
4225 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_", row.FieldAsString(0), "Feature"));
4226 }
4227 }
4228 }
4229
4230 /// <summary>
4231 /// Decompile the Dialog table.
4232 /// </summary>
4233 /// <param name="table">The table to decompile.</param>
4234 private void DecompileDialogTable(Table table)
4235 {
4236 foreach (var row in table.Rows)
4237 {
4238 var attributes = row.FieldAsNullableInteger(5) ?? 0;
4239
4240 var xDialog = new XElement(Names.DialogElement,
4241 new XAttribute("Id", row.FieldAsString(0)),
4242 new XAttribute("X", row.FieldAsString(1)),
4243 new XAttribute("Y", row.FieldAsString(2)),
4244 new XAttribute("Width", row.FieldAsString(3)),
4245 new XAttribute("Height", row.FieldAsString(4)),
4246 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesVisible) ? new XAttribute("Hidden", "yes") : null,
4247 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesModal) ? new XAttribute("Modeless", "yes") : null,
4248 0 == (attributes & WindowsInstallerConstants.MsidbDialogAttributesMinimize) ? new XAttribute("NoMinimize", "yes") : null,
4249 WindowsInstallerConstants.MsidbDialogAttributesSysModal == (attributes & WindowsInstallerConstants.MsidbDialogAttributesSysModal) ? new XAttribute("SystemModal", "yes") : null,
4250 WindowsInstallerConstants.MsidbDialogAttributesKeepModeless == (attributes & WindowsInstallerConstants.MsidbDialogAttributesKeepModeless) ? new XAttribute("KeepModeless", "yes") : null,
4251 WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace == (attributes & WindowsInstallerConstants.MsidbDialogAttributesTrackDiskSpace) ? new XAttribute("TrackDiskSpace", "yes") : null,
4252 WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette == (attributes & WindowsInstallerConstants.MsidbDialogAttributesUseCustomPalette) ? new XAttribute("CustomPalette", "yes") : null,
4253 WindowsInstallerConstants.MsidbDialogAttributesLeftScroll == (attributes & WindowsInstallerConstants.MsidbDialogAttributesLeftScroll) ? new XAttribute("LeftScroll", "yes") : null,
4254 WindowsInstallerConstants.MsidbDialogAttributesError == (attributes & WindowsInstallerConstants.MsidbDialogAttributesError) ? new XAttribute("ErrorDialog", "yes") : null,
4255 WindowsInstallerConstants.MsidbDialogAttributesRightAligned == (attributes & WindowsInstallerConstants.MsidbDialogAttributesRightAligned) ? new XAttribute("RightAligned", "yes") : null,
4256 WindowsInstallerConstants.MsidbDialogAttributesRTLRO == (attributes & WindowsInstallerConstants.MsidbDialogAttributesRTLRO) ? new XAttribute("RightToLeft", "yes") : null,
4257 !row.IsColumnNull(6) ? new XAttribute("Title", row.FieldAsString(6)) : null);
4258
4259 this.UIElement.Add(xDialog);
4260 this.DecompilerHelper.IndexElement(row, xDialog);
4261 }
4262 }
4263
4264 /// <summary>
4265 /// Decompile the Directory table.
4266 /// </summary>
4267 /// <param name="table">The table to decompile.</param>
4268 private void DecompileDirectoryTable(Table table)
4269 {
4270 foreach (var row in table.Rows)
4271 {
4272 var id = row.FieldAsString(0);
4273 var elementName = WindowsInstallerStandard.IsStandardDirectory(id) ? Names.StandardDirectoryElement : Names.DirectoryElement;
4274 var xDirectory = new XElement(elementName,
4275 new XAttribute("Id", id));
4276
4277 if (!WindowsInstallerStandard.IsStandardDirectory(id))
4278 {
4279 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(2));
4280
4281 if (id == "TARGETDIR" && names[0] != "SourceDir")
4282 {
4283 this.Messaging.Write(WarningMessages.TargetDirCorrectedDefaultDir());
4284 xDirectory.SetAttributeValue("Name", "SourceDir");
4285 }
4286 else
4287 {
4288 if (null != names[0] && "." != names[0])
4289 {
4290 if (null != names[1])
4291 {
4292 xDirectory.SetAttributeValue("ShortName", names[0]);
4293 }
4294 else
4295 {
4296 xDirectory.SetAttributeValue("Name", names[0]);
4297 }
4298 }
4299
4300 if (null != names[1])
4301 {
4302 xDirectory.SetAttributeValue("Name", names[1]);
4303 }
4304 }
4305
4306 if (null != names[2])
4307 {
4308 if (null != names[3])
4309 {
4310 xDirectory.SetAttributeValue("ShortSourceName", names[2]);
4311 }
4312 else
4313 {
4314 xDirectory.SetAttributeValue("SourceName", names[2]);
4315 }
4316 }
4317
4318 if (null != names[3])
4319 {
4320 xDirectory.SetAttributeValue("SourceName", names[3]);
4321 }
4322 }
4323
4324 this.DecompilerHelper.IndexElement(row, xDirectory);
4325 }
4326
4327 // nest the directories
4328 foreach (var row in table.Rows)
4329 {
4330 var xDirectory = this.DecompilerHelper.GetIndexedElement(row);
4331
4332 var id = row.FieldAsString(0);
4333
4334 if (id == "TARGETDIR")
4335 {
4336 // Skip TARGETDIR -- but it will be added for any components directly targeted.
4337 }
4338 else if (row.IsColumnNull(1) || WindowsInstallerStandard.IsStandardDirectory(id))
4339 {
4340 this.DecompilerHelper.AddElementToRoot(xDirectory);
4341 }
4342 else
4343 {
4344 var parentDirectoryId = row.FieldAsString(1);
4345
4346 if (!this.DecompilerHelper.TryGetIndexedElement("Directory", parentDirectoryId, out var xParentDirectory))
4347 {
4348 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Directory_Parent", row.FieldAsString(1), "Directory"));
4349 }
4350 else if (xParentDirectory == xDirectory) // another way to specify a root directory
4351 {
4352 this.DecompilerHelper.AddElementToRoot(xDirectory);
4353 }
4354 else
4355 {
4356 // TARGETDIR is omitted but if this directory is a first-generation descendant, add it as a root.
4357 if (parentDirectoryId == "TARGETDIR")
4358 {
4359 this.DecompilerHelper.AddElementToRoot(xDirectory);
4360 }
4361 else
4362 {
4363 xParentDirectory.Add(xDirectory);
4364 }
4365 }
4366 }
4367 }
4368 }
4369
4370 /// <summary>
4371 /// Decompile the DrLocator table.
4372 /// </summary>
4373 /// <param name="table">The table to decompile.</param>
4374 private void DecompileDrLocatorTable(Table table)
4375 {
4376 foreach (var row in table.Rows)
4377 {
4378 var xDirectorySearch = new XElement(Names.DirectorySearchElement,
4379 new XAttribute("Id", row.FieldAsString(0)),
4380 XAttributeIfNotNull("Path", row, 2),
4381 XAttributeIfNotNull("Depth", row, 3));
4382
4383 this.DecompilerHelper.IndexElement(row, xDirectorySearch);
4384 }
4385 }
4386
4387 /// <summary>
4388 /// Decompile the DuplicateFile table.
4389 /// </summary>
4390 /// <param name="table">The table to decompile.</param>
4391 private void DecompileDuplicateFileTable(Table table)
4392 {
4393 foreach (var row in table.Rows)
4394 {
4395 var xCopyFile = new XElement(Names.CopyFileElement,
4396 new XAttribute("Id", row.FieldAsString(0)),
4397 new XAttribute("FileId", row.FieldAsString(2)));
4398
4399 if (!row.IsColumnNull(3))
4400 {
4401 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(3));
4402 if (null != names[0] && null != names[1])
4403 {
4404 xCopyFile.SetAttributeValue("DestinationShortName", names[0]);
4405 xCopyFile.SetAttributeValue("DestinationName", names[1]);
4406 }
4407 else if (null != names[0])
4408 {
4409 xCopyFile.SetAttributeValue("DestinationName", names[0]);
4410 }
4411 }
4412
4413 // destination directory/property is set in FinalizeDuplicateMoveFileTables
4414
4415 this.AddChildToParent("Component", xCopyFile, row, 1);
4416 this.DecompilerHelper.IndexElement(row, xCopyFile);
4417 }
4418 }
4419
4420 /// <summary>
4421 /// Decompile the Environment table.
4422 /// </summary>
4423 /// <param name="table">The table to decompile.</param>
4424 private void DecompileEnvironmentTable(Table table)
4425 {
4426 foreach (var row in table.Rows)
4427 {
4428 var xEnvironment = new XElement(Names.EnvironmentElement,
4429 new XAttribute("Id", row.FieldAsString(0)));
4430
4431 var done = false;
4432 var permanent = true;
4433 var name = row.FieldAsString(1);
4434 for (var i = 0; i < name.Length && !done; i++)
4435 {
4436 switch (name[i])
4437 {
4438 case '=':
4439 xEnvironment.SetAttributeValue("Action", "set");
4440 break;
4441 case '+':
4442 xEnvironment.SetAttributeValue("Action", "create");
4443 break;
4444 case '-':
4445 permanent = false;
4446 break;
4447 case '!':
4448 xEnvironment.SetAttributeValue("Action", "remove");
4449 break;
4450 case '*':
4451 xEnvironment.SetAttributeValue("System", "yes");
4452 break;
4453 default:
4454 xEnvironment.SetAttributeValue("Name", name.Substring(i));
4455 done = true;
4456 break;
4457 }
4458 }
4459
4460 if (permanent)
4461 {
4462 xEnvironment.SetAttributeValue("Permanent", "yes");
4463 }
4464
4465 if (!row.IsColumnNull(2))
4466 {
4467 var value = row.FieldAsString(2);
4468
4469 if (value.StartsWith("[~]", StringComparison.Ordinal))
4470 {
4471 xEnvironment.SetAttributeValue("Part", "last");
4472
4473 if (3 < value.Length)
4474 {
4475 xEnvironment.SetAttributeValue("Separator", value.Substring(3, 1));
4476 xEnvironment.SetAttributeValue("Value", value.Substring(4));
4477 }
4478 }
4479 else if (value.EndsWith("[~]", StringComparison.Ordinal))
4480 {
4481 xEnvironment.SetAttributeValue("Part", "first");
4482
4483 if (3 < value.Length)
4484 {
4485 xEnvironment.SetAttributeValue("Separator", value.Substring(value.Length - 4, 1));
4486 xEnvironment.SetAttributeValue("Value", value.Substring(0, value.Length - 4));
4487 }
4488 }
4489 else
4490 {
4491 xEnvironment.SetAttributeValue("Value", value);
4492 }
4493 }
4494
4495 this.AddChildToParent("Component", xEnvironment, row, 3);
4496 }
4497 }
4498
4499 /// <summary>
4500 /// Decompile the Error table.
4501 /// </summary>
4502 /// <param name="table">The table to decompile.</param>
4503 private void DecompileErrorTable(Table table)
4504 {
4505 foreach (var row in table.Rows)
4506 {
4507 var xError = new XElement(Names.ErrorElement,
4508 new XAttribute("Id", row.FieldAsString(0)),
4509 row.IsColumnNull(1) ? null : new XAttribute("Message", row.FieldAsString(1)));
4510
4511 this.UIElement.Add(xError);
4512 }
4513 }
4514
4515 /// <summary>
4516 /// Decompile the EventMapping table.
4517 /// </summary>
4518 /// <param name="table">The table to decompile.</param>
4519 private void DecompileEventMappingTable(Table table)
4520 {
4521 foreach (var row in table.Rows)
4522 {
4523 var xSubscribe = new XElement(Names.SubscribeElement,
4524 new XAttribute("Event", row.FieldAsString(2)),
4525 new XAttribute("Attribute", row.FieldAsString(3)));
4526
4527 if (this.DecompilerHelper.TryGetIndexedElement("Control", row.FieldAsString(0), row.FieldAsString(1), out var xControl))
4528 {
4529 xControl.Add(xSubscribe);
4530 }
4531 else
4532 {
4533 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Dialog_", row.FieldAsString(0), "Control_", row.FieldAsString(1), "Control"));
4534 }
4535 }
4536 }
4537
4538 /// <summary>
4539 /// Decompile the Extension table.
4540 /// </summary>
4541 /// <param name="table">The table to decompile.</param>
4542 private void DecompileExtensionTable(Table table)
4543 {
4544 foreach (var row in table.Rows)
4545 {
4546 var xExtension = new XElement(Names.ExtensionElement,
4547 new XAttribute("Id", row.FieldAsString(0)),
4548 new XAttribute("Advertise", "yes"));
4549
4550 if (!row.IsColumnNull(3))
4551 {
4552 if (this.DecompilerHelper.TryGetIndexedElement("MIME", row.FieldAsString(3), out var xMime))
4553 {
4554 xMime.SetAttributeValue("Default", "yes");
4555 }
4556 else
4557 {
4558 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "MIME_", row.FieldAsString(3), "MIME"));
4559 }
4560 }
4561
4562 if (!row.IsColumnNull(2))
4563 {
4564 this.AddChildToParent("ProgId", xExtension, row, 2);
4565 }
4566 else
4567 {
4568 this.AddChildToParent("Component", xExtension, row, 1);
4569 }
4570
4571 this.DecompilerHelper.IndexElement(row, xExtension);
4572 }
4573 }
4574
4575 /// <summary>
4576 /// Decompile the ExternalFiles table.
4577 /// </summary>
4578 /// <param name="table">The table to decompile.</param>
4579 private void DecompileExternalFilesTable(Table table)
4580 {
4581 foreach (var row in table.Rows)
4582 {
4583 var xExternalFile = new XElement(Names.ExternalFileElement,
4584 new XAttribute("File", row.FieldAsString(1)),
4585 new XAttribute("Source", row.FieldAsString(2)));
4586
4587 AddSymbolPaths(row, 3, xExternalFile);
4588
4589 if (!row.IsColumnNull(4) && !row.IsColumnNull(5))
4590 {
4591 var ignoreOffsets = row.FieldAsString(4).Split(',');
4592 var ignoreLengths = row.FieldAsString(5).Split(',');
4593
4594 if (ignoreOffsets.Length == ignoreLengths.Length)
4595 {
4596 for (var i = 0; i < ignoreOffsets.Length; i++)
4597 {
4598 var xIgnoreRange = new XElement(Names.IgnoreRangeElement);
4599
4600 if (ignoreOffsets[i].StartsWith("0x", StringComparison.Ordinal))
4601 {
4602 xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i].Substring(2), 16));
4603 }
4604 else
4605 {
4606 xIgnoreRange.SetAttributeValue("Offset", Convert.ToInt32(ignoreOffsets[i], CultureInfo.InvariantCulture));
4607 }
4608
4609 if (ignoreLengths[i].StartsWith("0x", StringComparison.Ordinal))
4610 {
4611 xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i].Substring(2), 16));
4612 }
4613 else
4614 {
4615 xIgnoreRange.SetAttributeValue("Length", Convert.ToInt32(ignoreLengths[i], CultureInfo.InvariantCulture));
4616 }
4617
4618 xExternalFile.Add(xIgnoreRange);
4619 }
4620 }
4621 else
4622 {
4623 // TODO: warn
4624 }
4625 }
4626 else if (!row.IsColumnNull(4) || !row.IsColumnNull(5))
4627 {
4628 // TODO: warn about mismatch between columns
4629 }
4630
4631 // the RetainOffsets column is handled in FinalizeFamilyFileRangesTable
4632
4633 if (!row.IsColumnNull(7))
4634 {
4635 xExternalFile.SetAttributeValue("Order", row.FieldAsInteger(7));
4636 }
4637
4638 this.AddChildToParent("ImageFamilies", xExternalFile, row, 0);
4639 this.DecompilerHelper.IndexElement(row, xExternalFile);
4640 }
4641 }
4642
4643 /// <summary>
4644 /// Decompile the Feature table.
4645 /// </summary>
4646 /// <param name="table">The table to decompile.</param>
4647 private void DecompileFeatureTable(Table table)
4648 {
4649 var sortedFeatures = new SortedList<string, Row>();
4650
4651 foreach (var row in table.Rows)
4652 {
4653 var feature = new XElement(Names.FeatureElement,
4654 new XAttribute("Id", row.FieldAsString(0)),
4655 row.IsColumnNull(2) ? null : new XAttribute("Title", row.FieldAsString(2)),
4656 row.IsColumnNull(3) ? null : new XAttribute("Description", row.FieldAsString(3)),
4657 new XAttribute("Level", row.FieldAsInteger(5)),
4658 row.IsColumnNull(6) ? null : new XAttribute("ConfigurableDirectory", row.FieldAsString(6)));
4659
4660 if (row.IsColumnNull(4))
4661 {
4662 feature.SetAttributeValue("Display", "hidden");
4663 }
4664 else
4665 {
4666 var display = row.FieldAsInteger(4);
4667
4668 if (0 == display)
4669 {
4670 feature.SetAttributeValue("Display", "hidden");
4671 }
4672 else if (1 == display % 2)
4673 {
4674 feature.SetAttributeValue("Display", "expand");
4675 }
4676 }
4677
4678 var attributes = row.FieldAsInteger(7);
4679
4680 if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource) && WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent))
4681 {
4682 // TODO: display a warning for setting favor local and follow parent together
4683 }
4684 else if (WindowsInstallerConstants.MsidbFeatureAttributesFavorSource == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorSource))
4685 {
4686 feature.SetAttributeValue("InstallDefault", "source");
4687 }
4688 else if (WindowsInstallerConstants.MsidbFeatureAttributesFollowParent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFollowParent))
4689 {
4690 feature.SetAttributeValue("InstallDefault", "followParent");
4691 }
4692
4693 if (WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesFavorAdvertise))
4694 {
4695 feature.SetAttributeValue("InstallDefault", "advertise");
4696 }
4697
4698 if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise) &&
4699 WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise))
4700 {
4701 this.Messaging.Write(WarningMessages.InvalidAttributeCombination(row.SourceLineNumbers, "msidbFeatureAttributesDisallowAdvertise", "msidbFeatureAttributesNoUnsupportedAdvertise", "Feature.AllowAdvertiseType", "no"));
4702 feature.SetAttributeValue("AllowAdvertise", "no");
4703 }
4704 else if (WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesDisallowAdvertise))
4705 {
4706 feature.SetAttributeValue("AllowAdvertise", "no");
4707 }
4708 else if (WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesNoUnsupportedAdvertise))
4709 {
4710 feature.SetAttributeValue("AllowAdvertise", "system");
4711 }
4712
4713 if (WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent == (attributes & WindowsInstallerConstants.MsidbFeatureAttributesUIDisallowAbsent))
4714 {
4715 feature.SetAttributeValue("Absent", "disallow");
4716 }
4717
4718 this.DecompilerHelper.IndexElement(row, feature);
4719
4720 // sort the features by their display column (and append the identifier to ensure unique keys)
4721 sortedFeatures.Add(String.Format(CultureInfo.InvariantCulture, "{0:00000}|{1}", row.FieldAsInteger(4), row[0]), row);
4722 }
4723
4724 // nest the features
4725 foreach (var row in sortedFeatures.Values)
4726 {
4727 var xFeature = this.DecompilerHelper.GetIndexedElement("Feature", row.FieldAsString(0));
4728
4729 if (row.IsColumnNull(1))
4730 {
4731 this.DecompilerHelper.AddElementToRoot(xFeature);
4732 }
4733 else
4734 {
4735 if (this.DecompilerHelper.TryGetIndexedElement("Feature", row.FieldAsString(1), out var xParentFeature))
4736 {
4737 if (xParentFeature == xFeature)
4738 {
4739 // TODO: display a warning about self-nesting
4740 }
4741 else
4742 {
4743 xParentFeature.Add(xFeature);
4744 }
4745 }
4746 else
4747 {
4748 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "Feature_Parent", row.FieldAsString(1), "Feature"));
4749 }
4750 }
4751 }
4752 }
4753
4754 /// <summary>
4755 /// Decompile the FeatureComponents table.
4756 /// </summary>
4757 /// <param name="table">The table to decompile.</param>
4758 private void DecompileFeatureComponentsTable(Table table)
4759 {
4760 foreach (var row in table.Rows)
4761 {
4762 var xComponentRef = new XElement(Names.ComponentRefElement,
4763 new XAttribute("Id", row.FieldAsString(1)));
4764
4765 this.AddChildToParent("Feature", xComponentRef, row, 0);
4766 this.DecompilerHelper.IndexElement(row, xComponentRef);
4767 }
4768 }
4769
4770 /// <summary>
4771 /// Decompile the File table.
4772 /// </summary>
4773 /// <param name="table">The table to decompile.</param>
4774 private void DecompileFileTable(Table table)
4775 {
4776 foreach (FileRow fileRow in table.Rows)
4777 {
4778 var xFile = new XElement(Names.FileElement,
4779 new XAttribute("Id", fileRow.File),
4780 WindowsInstallerConstants.MsidbFileAttributesReadOnly == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesReadOnly) ? new XAttribute("ReadOnly", "yes") : null,
4781 WindowsInstallerConstants.MsidbFileAttributesHidden == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesHidden) ? new XAttribute("Hidden", "yes") : null,
4782 WindowsInstallerConstants.MsidbFileAttributesSystem == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesSystem) ? new XAttribute("System", "yes") : null,
4783 WindowsInstallerConstants.MsidbFileAttributesChecksum == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesChecksum) ? new XAttribute("Checksum", "yes") : null,
4784 WindowsInstallerConstants.MsidbFileAttributesVital != (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesVital) ? new XAttribute("Vital", "no") : null,
4785 null != fileRow.Version && 0 < fileRow.Version.Length && !Char.IsDigit(fileRow.Version[0]) ? new XAttribute("CompanionFile", fileRow.Version) : null);
4786
4787 var names = this.BackendHelper.SplitMsiFileName(fileRow.FileName);
4788 if (null != names[0] && null != names[1])
4789 {
4790 xFile.SetAttributeValue("ShortName", names[0]);
4791 xFile.SetAttributeValue("Name", names[1]);
4792 }
4793 else if (null != names[0])
4794 {
4795 xFile.SetAttributeValue("Name", names[0]);
4796 }
4797
4798 if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) &&
4799 WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed))
4800 {
4801 // TODO: error
4802 }
4803 else if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesNoncompressed))
4804 {
4805 xFile.SetAttributeValue("Compressed", "no");
4806 }
4807 else if (WindowsInstallerConstants.MsidbFileAttributesCompressed == (fileRow.Attributes & WindowsInstallerConstants.MsidbFileAttributesCompressed))
4808 {
4809 xFile.SetAttributeValue("Compressed", "yes");
4810 }
4811
4812 this.DecompilerHelper.IndexElement(fileRow, xFile);
4813 }
4814 }
4815
4816 /// <summary>
4817 /// Decompile the FileSFPCatalog table.
4818 /// </summary>
4819 /// <param name="table">The table to decompile.</param>
4820 private void DecompileFileSFPCatalogTable(Table table)
4821 {
4822 foreach (var row in table.Rows)
4823 {
4824 var xSfpFile = new XElement(Names.SFPFileElement,
4825 new XAttribute("Id", row.FieldAsString(0)));
4826
4827 this.AddChildToParent("SFPCatalog", xSfpFile, row, 1);
4828 }
4829 }
4830
4831 /// <summary>
4832 /// Decompile the Font table.
4833 /// </summary>
4834 /// <param name="table">The table to decompile.</param>
4835 private void DecompileFontTable(Table table)
4836 {
4837 foreach (var row in table.Rows)
4838 {
4839 if (this.DecompilerHelper.TryGetIndexedElement("File", row.FieldAsString(0), out var xFile))
4840 {
4841 if (!row.IsColumnNull(1))
4842 {
4843 xFile.SetAttributeValue("FontTitle", row.FieldAsString(1));
4844 }
4845 else
4846 {
4847 xFile.SetAttributeValue("TrueType", "yes");
4848 }
4849 }
4850 else
4851 {
4852 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(DecompilerConstants.PrimaryKeyDelimiter), "File_", row.FieldAsString(0), "File"));
4853 }
4854 }
4855 }
4856
4857 /// <summary>
4858 /// Decompile the Icon table.
4859 /// </summary>
4860 /// <param name="table">The table to decompile.</param>
4861 private void DecompileIconTable(Table table)
4862 {
4863 foreach (var row in table.Rows)
4864 {
4865 var icon = new XElement(Names.IconElement,
4866 new XAttribute("Id", row.FieldAsString(0)),
4867 new XAttribute("SourceFile", row.FieldAsString(1)));
4868
4869 this.DecompilerHelper.AddElementToRoot(icon);
4870 }
4871 }
4872
4873 /// <summary>
4874 /// Decompile the ImageFamilies table.
4875 /// </summary>
4876 /// <param name="table">The table to decompile.</param>
4877 private void DecompileImageFamiliesTable(Table table)
4878 {
4879 foreach (var row in table.Rows)
4880 {
4881 var family = new XElement(Names.FamilyElement,
4882 new XAttribute("Name", row.FieldAsString(0)),
4883 row.IsColumnNull(1) ? null : new XAttribute("MediaSrcProp", row.FieldAsString(1)),
4884 row.IsColumnNull(2) ? null : new XAttribute("DiskId", row.FieldAsString(2)),
4885 row.IsColumnNull(3) ? null : new XAttribute("SequenceStart", row.FieldAsString(3)),
4886 row.IsColumnNull(4) ? null : new XAttribute("DiskPrompt", row.FieldAsString(4)),
4887 row.IsColumnNull(5) ? null : new XAttribute("VolumeLabel", row.FieldAsString(5)));
4888
4889 this.DecompilerHelper.AddElementToRoot(family);
4890 this.DecompilerHelper.IndexElement(row, family);
4891 }
4892 }
4893
4894 /// <summary>
4895 /// Decompile the IniFile table.
4896 /// </summary>
4897 /// <param name="table">The table to decompile.</param>
4898 private void DecompileIniFileTable(Table table)
4899 {
4900 foreach (var row in table.Rows)
4901 {
4902 var xIniFile = new XElement(Names.IniFileElement,
4903 new XAttribute("Id", row.FieldAsString(0)),
4904 new XAttribute("Section", row.FieldAsString(3)),
4905 new XAttribute("Key", row.FieldAsString(4)),
4906 new XAttribute("Value", row.FieldAsString(5)),
4907 row.IsColumnNull(2) ? null : new XAttribute("Directory", row.FieldAsString(2)));
4908
4909 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
4910
4911 if (null != names[0])
4912 {
4913 if (null == names[1])
4914 {
4915 xIniFile.SetAttributeValue("Name", names[0]);
4916 }
4917 else
4918 {
4919 xIniFile.SetAttributeValue("ShortName", names[0]);
4920 }
4921 }
4922
4923 if (null != names[1])
4924 {
4925 xIniFile.SetAttributeValue("Name", names[1]);
4926 }
4927
4928 switch (row.FieldAsInteger(6))
4929 {
4930 case WindowsInstallerConstants.MsidbIniFileActionAddLine:
4931 xIniFile.SetAttributeValue("Action", "addLine");
4932 break;
4933 case WindowsInstallerConstants.MsidbIniFileActionCreateLine:
4934 xIniFile.SetAttributeValue("Action", "createLine");
4935 break;
4936 case WindowsInstallerConstants.MsidbIniFileActionAddTag:
4937 xIniFile.SetAttributeValue("Action", "addTag");
4938 break;
4939 default:
4940 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[6].Column.Name, row[6]));
4941 break;
4942 }
4943
4944 this.AddChildToParent("Component", xIniFile, row, 7);
4945 }
4946 }
4947
4948 /// <summary>
4949 /// Decompile the IniLocator table.
4950 /// </summary>
4951 /// <param name="table">The table to decompile.</param>
4952 private void DecompileIniLocatorTable(Table table)
4953 {
4954 foreach (var row in table.Rows)
4955 {
4956 var xIniFileSearch = new XElement(Names.IniFileSearchElement,
4957 new XAttribute("Id", row.FieldAsString(0)),
4958 new XAttribute("Section", row.FieldAsString(2)),
4959 new XAttribute("Key", row.FieldAsString(3)),
4960 row.IsColumnNull(4) || row.FieldAsInteger(4) == 0 ? null : new XAttribute("Field", row.FieldAsInteger(4)));
4961
4962 var names = this.BackendHelper.SplitMsiFileName(row.FieldAsString(1));
4963 if (null != names[0] && null != names[1])
4964 {
4965 xIniFileSearch.SetAttributeValue("ShortName", names[0]);
4966 xIniFileSearch.SetAttributeValue("Name", names[1]);
4967 }
4968 else if (null != names[0])
4969 {
4970 xIniFileSearch.SetAttributeValue("Name", names[0]);
4971 }
4972
4973 if (!row.IsColumnNull(5))
4974 {
4975 switch (row.FieldAsInteger(5))
4976 {
4977 case WindowsInstallerConstants.MsidbLocatorTypeDirectory:
4978 xIniFileSearch.SetAttributeValue("Type", "directory");
4979 break;
4980 case WindowsInstallerConstants.MsidbLocatorTypeFileName:
4981 // this is the default value
4982 break;
4983 case WindowsInstallerConstants.MsidbLocatorTypeRawValue:
4984 xIniFileSearch.SetAttributeValue("Type", "raw");
4985 break;
4986 default:
4987 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[5].Column.Name, row[5]));
4988 break;
4989 }
4990 }
4991
4992 this.DecompilerHelper.IndexElement(row, xIniFileSearch);
4993 }
4994 }
4995
4996 /// <summary>
4997 /// Decompile the IsolatedComponent table.
4998 /// </summary>
4999 /// <param name="table">The table to decompile.</param>
5000 private void DecompileIsolatedComponentTable(Table table)
Showing first 5,000 of 7,610 lines. View raw