main
cs 1,229 lines 53 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.Util
4 {
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.IO;
9 using System.Linq;
10 using System.Text;
11 using System.Xml.Linq;
12 using WixToolset.Data;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility;
15 using WixToolset.Util.Symbols;
16
17 /// <summary>
18 /// The decompiler for the WiX Toolset Utility Extension.
19 /// </summary>
20 internal sealed class UtilDecompiler : BaseWindowsInstallerDecompilerExtension
21 {
22 public override IReadOnlyCollection<TableDefinition> TableDefinitions => UtilTableDefinitions.All;
23
24 private static readonly Dictionary<string, XName> CustomActionMapping = new Dictionary<string, XName>()
25 {
26 { "Wix4BroadcastEnvironmentChange_X86", UtilConstants.BroadcastEnvironmentChange },
27 { "Wix4BroadcastEnvironmentChange_X64", UtilConstants.BroadcastEnvironmentChange },
28 { "Wix4BroadcastEnvironmentChange_ARM64", UtilConstants.BroadcastEnvironmentChange },
29 { "Wix4BroadcastSettingChange_X86", UtilConstants.BroadcastSettingChange },
30 { "Wix4BroadcastSettingChange_X64", UtilConstants.BroadcastSettingChange },
31 { "Wix4BroadcastSettingChange_ARM64", UtilConstants.BroadcastSettingChange },
32 { "Wix4CheckRebootRequired_X86", UtilConstants.CheckRebootRequired },
33 { "Wix4CheckRebootRequired_X64", UtilConstants.CheckRebootRequired },
34 { "Wix4CheckRebootRequired_ARM64", UtilConstants.CheckRebootRequired },
35 { "Wix4QueryNativeMachine_X86", UtilConstants.QueryNativeMachine },
36 { "Wix4QueryNativeMachine_X64", UtilConstants.QueryNativeMachine },
37 { "Wix4QueryNativeMachine_ARM64", UtilConstants.QueryNativeMachine },
38 { "Wix4QueryOsDriverInfo_X86", UtilConstants.QueryWindowsDriverInfo },
39 { "Wix4QueryOsDriverInfo_X64", UtilConstants.QueryWindowsDriverInfo },
40 { "Wix4QueryOsDriverInfo_ARM64", UtilConstants.QueryWindowsDriverInfo },
41 { "Wix4QueryOsInfo_X86", UtilConstants.QueryWindowsSuiteInfo },
42 { "Wix4QueryOsInfo_X64", UtilConstants.QueryWindowsSuiteInfo },
43 { "Wix4QueryOsInfo_ARM64", UtilConstants.QueryWindowsSuiteInfo },
44 };
45
46 private IReadOnlyCollection<string> customActionNames;
47
48 /// <summary>
49 /// Called at the beginning of the decompilation of a database.
50 /// </summary>
51 /// <param name="tables">The collection of all tables.</param>
52 public override void PreDecompileTables(TableIndexedCollection tables)
53 {
54 this.RememberCustomActionNames(tables);
55 this.CleanupSecureCustomProperties(tables);
56 this.CleanupInternetShortcutRemoveFileTables(tables);
57 }
58
59 private void RememberCustomActionNames(TableIndexedCollection tables)
60 {
61 var customActionTable = tables["CustomAction"];
62 this.customActionNames = customActionTable?.Rows.Select(r => r.GetPrimaryKey()).Distinct().ToList() ?? (IReadOnlyCollection<string>)Array.Empty<string>();
63 }
64
65 /// <summary>
66 /// Decompile the SecureCustomProperties field to PropertyRefs for known extension properties.
67 /// </summary>
68 /// <remarks>
69 /// If we've referenced any of the suite or directory properties, add
70 /// a PropertyRef to refer to the Property (and associated custom action)
71 /// from the extension's library. Then remove the property from
72 /// SecureCustomExtensions property so later decompilation won't create
73 /// new Property elements.
74 /// </remarks>
75 /// <param name="tables">The collection of all tables.</param>
76 private void CleanupSecureCustomProperties(TableIndexedCollection tables)
77 {
78 var propertyTable = tables["Property"];
79
80 if (null != propertyTable)
81 {
82 foreach (var row in propertyTable.Rows)
83 {
84 if ("SecureCustomProperties" == row[0].ToString())
85 {
86 var remainingProperties = new StringBuilder();
87 var secureCustomProperties = row[1].ToString().Split(';');
88 foreach (var property in secureCustomProperties)
89 {
90 if (property.StartsWith("WIX_SUITE_", StringComparison.Ordinal) || property.StartsWith("WIX_DIR_", StringComparison.Ordinal)
91 || property.StartsWith("WIX_ACCOUNT_", StringComparison.Ordinal))
92 {
93 this.DecompilerHelper.AddElementToRoot("PropertyRef", new XAttribute("Id", property));
94 }
95 else
96 {
97 if (0 < remainingProperties.Length)
98 {
99 remainingProperties.Append(";");
100 }
101 remainingProperties.Append(property);
102 }
103 }
104
105 row[1] = remainingProperties.ToString();
106 break;
107 }
108 }
109 }
110 }
111
112 /// <summary>
113 /// Remove RemoveFile rows that the InternetShortcut compiler extension adds for us.
114 /// </summary>
115 /// <param name="tables">The collection of all tables.</param>
116 private void CleanupInternetShortcutRemoveFileTables(TableIndexedCollection tables)
117 {
118 // index the WixInternetShortcut table
119 var wixInternetShortcutTable = tables["WixInternetShortcut"];
120 var wixInternetShortcuts = new Hashtable();
121 if (null != wixInternetShortcutTable)
122 {
123 foreach (var row in wixInternetShortcutTable.Rows)
124 {
125 wixInternetShortcuts.Add(row.GetPrimaryKey(), row);
126 }
127 }
128
129 // remove the RemoveFile rows with primary keys that match the WixInternetShortcut table's
130 var removeFileTable = tables["RemoveFile"];
131 if (null != removeFileTable)
132 {
133 for (var i = removeFileTable.Rows.Count - 1; 0 <= i; i--)
134 {
135 if (null != wixInternetShortcuts[removeFileTable.Rows[i][0]])
136 {
137 removeFileTable.Rows.RemoveAt(i);
138 }
139 }
140 }
141 }
142
143 /// <summary>
144 /// Decompiles an extension table.
145 /// </summary>
146 /// <param name="table">The table to decompile.</param>
147 public override bool TryDecompileTable(Table table)
148 {
149 switch (table.Name)
150 {
151 case "WixCloseApplication":
152 case "Wix4CloseApplication":
153 this.DecompileWixCloseApplicationTable(table);
154 break;
155 case "WixRemoveFolderEx":
156 case "Wix4RemoveFolderEx":
157 this.DecompileWixRemoveFolderExTable(table);
158 break;
159 case "WixRestartResource":
160 case "Wix4RestartResource":
161 this.DecompileWixRestartResourceTable(table);
162 break;
163 case "FileShare":
164 case "Wix4FileShare":
165 this.DecompileFileShareTable(table);
166 break;
167 case "FileSharePermissions":
168 case "Wix4FileSharePermissions":
169 this.DecompileFileSharePermissionsTable(table);
170 break;
171 case "WixInternetShortcut":
172 case "Wix4InternetShortcut":
173 this.DecompileWixInternetShortcutTable(table);
174 break;
175 case "Group":
176 case "Wix4Group":
177 this.DecompileGroupTable(table);
178 break;
179 case "Perfmon":
180 case "Wix4Perfmon":
181 this.DecompilePerfmonTable(table);
182 break;
183 case "PerfmonManifest":
184 case "Wix4PerfmonManifest":
185 this.DecompilePerfmonManifestTable(table);
186 break;
187 case "EventManifest":
188 case "Wix4EventManifest":
189 this.DecompileEventManifestTable(table);
190 break;
191 case "SecureObjects":
192 case "Wix4SecureObjects":
193 this.DecompileSecureObjectsTable(table);
194 break;
195 case "ServiceConfig":
196 case "Wix4ServiceConfig":
197 this.DecompileServiceConfigTable(table);
198 break;
199 case "User":
200 case "Wix4User":
201 this.DecompileUserTable(table);
202 break;
203 case "UserGroup":
204 case "Wix4UserGroup":
205 this.DecompileUserGroupTable(table);
206 break;
207 case "XmlConfig":
208 case "Wix4XmlConfig":
209 this.DecompileXmlConfigTable(table);
210 break;
211 case "XmlFile":
212 case "Wix4XmlFile":
213 // XmlFile decompilation has been moved to FinalizeXmlFileTable function
214 break;
215 default:
216 return false;
217 }
218
219 return true;
220 }
221
222 /// <summary>
223 /// Finalize decompilation.
224 /// </summary>
225 /// <param name="tables">The collection of all tables.</param>
226 public override void PostDecompileTables(TableIndexedCollection tables)
227 {
228 this.FinalizeCustomActions();
229 this.FinalizePerfmonTable(tables);
230 this.FinalizePerfmonManifestTable(tables);
231 this.FinalizeSecureObjectsTable(tables);
232 this.FinalizeServiceConfigTable(tables);
233 this.FinalizeXmlConfigTable(tables);
234 this.FinalizeXmlFileTable(tables);
235 this.FinalizeEventManifestTable(tables);
236 }
237
238 /// <summary>
239 /// Decompile the WixCloseApplication table.
240 /// </summary>
241 /// <param name="table">The table to decompile.</param>
242 private void DecompileWixCloseApplicationTable(Table table)
243 {
244 foreach (var row in table.Rows)
245 {
246 var attribute = row.FieldAsNullableInteger(4) ?? 0x2;
247
248 this.DecompilerHelper.AddElementToRoot(UtilConstants.CloseApplicationName,
249 new XAttribute("Id", row.FieldAsString(0)),
250 new XAttribute("Target", row.FieldAsString(1)),
251 AttributeIfNotNull("Description", row, 2),
252 AttributeIfNotNull("Content", row, 3),
253 AttributeIfNotNull("CloseMessage", 0x1 == (attribute & 0x1)),
254 AttributeIfNotNull("RebootPrompt", 0x2 == (attribute & 0x2)),
255 AttributeIfNotNull("ElevatedCloseMessage", 0x4 == (attribute & 0x4)),
256 NumericAttributeIfNotNull("Sequence", row, 5),
257 AttributeIfNotNull("Property", row, 6)
258 );
259 }
260 }
261
262 /// <summary>
263 /// Decompile the WixRemoveFolderEx table.
264 /// </summary>
265 /// <param name="table">The table to decompile.</param>
266 private void DecompileWixRemoveFolderExTable(Table table)
267 {
268 foreach (var row in table.Rows)
269 {
270 var on = String.Empty;
271 var installMode = row.FieldAsInteger(3);
272 switch (installMode)
273 {
274 case (int)WixRemoveFolderExInstallMode.Install:
275 on = "install";
276 break;
277
278 case (int)WixRemoveFolderExInstallMode.Uninstall:
279 on = "uninstall";
280 break;
281
282 case (int)WixRemoveFolderExInstallMode.Both:
283 on = "both";
284 break;
285
286 default:
287 this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, "InstallMode", installMode));
288 break;
289 }
290
291 var removeFolder = new XElement(UtilConstants.RemoveFolderExName,
292 AttributeIfNotNull("Id", row, 0),
293 AttributeIfNotNull("Property", row, 2),
294 AttributeIfNotNull("On", on)
295 );
296
297 // Add to the appropriate Component or section element.
298 var componentId = row.FieldAsString(1);
299
300 if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component))
301 {
302 component.Add(removeFolder);
303 }
304 else
305 {
306 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Component_", componentId, "Component"));
307 }
308 }
309 }
310
311 /// <summary>
312 /// Decompile the WixRestartResource table.
313 /// </summary>
314 /// <param name="table">The table to decompile.</param>
315 private void DecompileWixRestartResourceTable(Table table)
316 {
317 foreach (var row in table.Rows)
318 {
319 var restartResource = new XElement(UtilConstants.RestartResourceName,
320 new XAttribute("Id", row.FieldAsString(0)));
321
322 // Determine the resource type and set accordingly.
323 var resource = row.FieldAsString(2);
324 var attributes = row.FieldAsInteger(3);
325 var type = (WixRestartResourceAttributes)attributes;
326
327 switch (type)
328 {
329 case WixRestartResourceAttributes.Filename:
330 restartResource.Add(new XAttribute("Path", resource));
331 break;
332
333 case WixRestartResourceAttributes.ProcessName:
334 restartResource.Add(new XAttribute("ProcessName", resource));
335 break;
336
337 case WixRestartResourceAttributes.ServiceName:
338 restartResource.Add(new XAttribute("ServiceName", resource));
339 break;
340
341 default:
342 this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, "Attributes", attributes));
343 break;
344 }
345
346 // Add to the appropriate Component or section element.
347 var componentId = row.FieldAsString(1);
348 if (!String.IsNullOrEmpty(componentId))
349 {
350 if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component))
351 {
352 component.Add(restartResource);
353 }
354 else
355 {
356 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Component_", componentId, "Component"));
357 }
358 }
359 else
360 {
361 this.DecompilerHelper.AddElementToRoot(restartResource);
362 }
363 }
364 }
365
366 /// <summary>
367 /// Decompile the FileShare table.
368 /// </summary>
369 /// <param name="table">The table to decompile.</param>
370 private void DecompileFileShareTable(Table table)
371 {
372 foreach (var row in table.Rows)
373 {
374 var fileShare = new XElement(UtilConstants.FileShareName,
375 new XAttribute("Id", row.FieldAsString(0)),
376 new XAttribute("Name", row.FieldAsString(1)),
377 AttributeIfNotNull("Description", row, 3)
378 );
379
380 // the Directory_ column is set by the parent Component
381
382 // the User_ and Permissions columns are deprecated
383
384 if (this.DecompilerHelper.TryGetIndexedElement("Component", row.FieldAsString(2), out var component))
385 {
386 component.Add(fileShare);
387 }
388 else
389 {
390 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Component_", (string)row[2], "Component"));
391 }
392
393 this.DecompilerHelper.IndexElement(row, fileShare);
394 }
395 }
396
397 /// <summary>
398 /// Decompile the FileSharePermissions table.
399 /// </summary>
400 /// <param name="table">The table to decompile.</param>
401 private void DecompileFileSharePermissionsTable(Table table)
402 {
403 foreach (var row in table.Rows)
404 {
405 var fileSharePermission = new XElement(UtilConstants.FileSharePermissionName,
406 new XAttribute("User", row.FieldAsString(1)));
407
408 this.AddPermissionAttributes(fileSharePermission, row, 2, UtilConstants.FolderPermissions);
409
410 if (this.DecompilerHelper.TryGetIndexedElement("Wix4FileShare", row.FieldAsString(0), out var fileShare) ||
411 this.DecompilerHelper.TryGetIndexedElement("FileShare", row.FieldAsString(0), out fileShare))
412 {
413 fileShare.Add(fileSharePermission);
414 }
415 else
416 {
417 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "FileShare_", (string)row[0], "Wix4FileShare"));
418 }
419 }
420 }
421
422 /// <summary>
423 /// Decompile the Group table.
424 /// </summary>
425 /// <param name="table">The table to decompile.</param>
426 private void DecompileGroupTable(Table table)
427 {
428 foreach (var row in table.Rows)
429 {
430 if (null != row[1])
431 {
432 this.Messaging.Write(WarningMessages.UnrepresentableColumnValue(row.SourceLineNumbers, table.Name, "Component_", (string)row[1]));
433 }
434
435 this.DecompilerHelper.AddElementToRoot(UtilConstants.GroupName,
436 new XAttribute("Id", row.FieldAsString(0)),
437 new XAttribute("Name", row.FieldAsString(1)),
438 AttributeIfNotNull("Domain", row, 3)
439 );
440 }
441 }
442
443 /// <summary>
444 /// Decompile the WixInternetShortcut table.
445 /// </summary>
446 /// <param name="table">The table to decompile.</param>
447 private void DecompileWixInternetShortcutTable(Table table)
448 {
449 foreach (var row in table.Rows)
450 {
451 var type = String.Empty;
452 var shortcutType = (UtilCompiler.InternetShortcutType)row.FieldAsInteger(5);
453 switch (shortcutType)
454 {
455 case UtilCompiler.InternetShortcutType.Link:
456 type = "link";
457 break;
458 case UtilCompiler.InternetShortcutType.Url:
459 type = "url";
460 break;
461 }
462
463 var internetShortcut = new XElement(UtilConstants.InternetShortcutName,
464 new XAttribute("Id", row.FieldAsString(0)),
465 new XAttribute("Directory", row.FieldAsString(2)),
466 new XAttribute("Name", Path.GetFileNameWithoutExtension(row.FieldAsString(3))), // remove .lnk/.url extension because compiler extension adds it back for us
467 new XAttribute("Type", type),
468 new XAttribute("Target", row.FieldAsString(4)),
469 new XAttribute("IconFile", row.FieldAsString(6)),
470 NumericAttributeIfNotNull("IconIndex", row, 7)
471 );
472
473 var componentId = row.FieldAsString(1);
474 if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component))
475 {
476 component.Add(internetShortcut);
477 }
478 else
479 {
480 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Component_", componentId, "Component"));
481 }
482
483 this.DecompilerHelper.IndexElement(row, internetShortcut);
484 }
485 }
486
487 /// <summary>
488 /// Decompile the Perfmon table.
489 /// </summary>
490 /// <param name="table">The table to decompile.</param>
491 private void DecompilePerfmonTable(Table table)
492 {
493 foreach (var row in table.Rows)
494 {
495 this.DecompilerHelper.IndexElement(row, new XElement(UtilConstants.PerfCounterName, new XAttribute("Name", row.FieldAsString(2))));
496 }
497 }
498
499 /// <summary>
500 /// Decompile the PerfmonManifest table.
501 /// </summary>
502 /// <param name="table">The table to decompile.</param>
503 private void DecompilePerfmonManifestTable(Table table)
504 {
505 foreach (var row in table.Rows)
506 {
507 this.DecompilerHelper.IndexElement(row, new XElement(UtilConstants.PerfCounterManifestName, new XAttribute("ResourceFileDirectory", row.FieldAsString(2))));
508 }
509 }
510
511 /// <summary>
512 /// Decompile the EventManifest table.
513 /// </summary>
514 /// <param name="table">The table to decompile.</param>
515 private void DecompileEventManifestTable(Table table)
516 {
517 foreach (var row in table.Rows)
518 {
519 this.DecompilerHelper.IndexElement(row, new XElement(UtilConstants.EventManifestName));
520 }
521 }
522
523 /// <summary>
524 /// Decompile the SecureObjects table.
525 /// </summary>
526 /// <param name="table">The table to decompile.</param>
527 private void DecompileSecureObjectsTable(Table table)
528 {
529 foreach (var row in table.Rows)
530 {
531 var permissionEx = new XElement(UtilConstants.PermissionExName,
532 AttributeIfNotNull("Domain", row, 2),
533 AttributeIfNotNull("User", row, 3)
534 );
535
536 string[] specialPermissions;
537 switch ((string)row[1])
538 {
539 case "CreateFolder":
540 specialPermissions = UtilConstants.FolderPermissions;
541 break;
542 case "File":
543 specialPermissions = UtilConstants.FilePermissions;
544 break;
545 case "Registry":
546 specialPermissions = UtilConstants.RegistryPermissions;
547 break;
548 case "ServiceInstall":
549 specialPermissions = UtilConstants.ServicePermissions;
550 break;
551 default:
552 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, row.Table.Name, row.Fields[1].Column.Name, row[1]));
553 return;
554 }
555
556 this.AddPermissionAttributes(permissionEx, row, 4, specialPermissions);
557
558 this.DecompilerHelper.IndexElement(row, permissionEx);
559 }
560 }
561
562 /// <summary>
563 /// Decompile the ServiceConfig table.
564 /// </summary>
565 /// <param name="table">The table to decompile.</param>
566 private void DecompileServiceConfigTable(Table table)
567 {
568 foreach (var row in table.Rows)
569 {
570 var serviceConfig = new XElement(UtilConstants.ServiceConfigName,
571 new XAttribute("ServiceName", row.FieldAsString(0)),
572 AttributeIfNotNull("FirstFailureActionType", row, 3),
573 AttributeIfNotNull("SecondFailureActionType", row, 4),
574 AttributeIfNotNull("ThirdFailureActionType", row, 5),
575 NumericAttributeIfNotNull("ResetPeriodInDays", row, 6),
576 NumericAttributeIfNotNull("RestartServiceDelayInSeconds", row, 7),
577 AttributeIfNotNull("ProgramCommandLine", row, 8),
578 AttributeIfNotNull("RebootMessage", row, 9)
579 );
580
581 this.DecompilerHelper.IndexElement(row, serviceConfig);
582 }
583 }
584
585 /// <summary>
586 /// Decompile the User table.
587 /// </summary>
588 /// <param name="table">The table to decompile.</param>
589 private void DecompileUserTable(Table table)
590 {
591 foreach (var row in table.Rows)
592 {
593 var attributes = row.FieldAsNullableInteger(6) ?? 0;
594
595 var user = new XElement(UtilConstants.UserName,
596 new XAttribute("Id", row.FieldAsString(0)),
597 new XAttribute("Name", row.FieldAsString(2)),
598 AttributeIfNotNull("Domain", row, 3),
599 AttributeIfNotNull("Password", row, 4),
600 AttributeIfNotNull("Comment", row, 5),
601 AttributeIfTrue("PasswordNeverExpires", UtilCompiler.UserDontExpirePasswrd == (attributes & UtilCompiler.UserDontExpirePasswrd)),
602 AttributeIfTrue("CanNotChangePassword", UtilCompiler.UserPasswdCantChange == (attributes & UtilCompiler.UserPasswdCantChange)),
603 AttributeIfTrue("PasswordExpired", UtilCompiler.UserPasswdChangeReqdOnLogin == (attributes & UtilCompiler.UserPasswdChangeReqdOnLogin)),
604 AttributeIfTrue("Disabled", UtilCompiler.UserDisableAccount == (attributes & UtilCompiler.UserDisableAccount)),
605 AttributeIfTrue("FailIfExists", UtilCompiler.UserFailIfExists == (attributes & UtilCompiler.UserFailIfExists)),
606 AttributeIfTrue("UpdateIfExists", UtilCompiler.UserUpdateIfExists == (attributes & UtilCompiler.UserUpdateIfExists)),
607 AttributeIfTrue("LogonAsService", UtilCompiler.UserLogonAsService == (attributes & UtilCompiler.UserLogonAsService)),
608 AttributeIfTrue("LogonAsBatchJob", UtilCompiler.UserLogonAsBatchJob == (attributes & UtilCompiler.UserLogonAsBatchJob)),
609 AttributeIfTrue("RemoveComment", UtilCompiler.UserRemoveComment == (attributes & UtilCompiler.UserRemoveComment))
610 );
611
612 if (UtilCompiler.UserDontRemoveOnUninstall == (attributes & UtilCompiler.UserDontRemoveOnUninstall))
613 {
614 user.Add(new XAttribute("RemoveOnUninstall", "no"));
615 }
616
617 if (UtilCompiler.UserDontCreateUser == (attributes & UtilCompiler.UserDontCreateUser))
618 {
619 user.Add(new XAttribute("CreateUser", "no"));
620 }
621
622 if (UtilCompiler.UserNonVital == (attributes & UtilCompiler.UserNonVital))
623 {
624 user.Add(new XAttribute("Vital", "no"));
625 }
626
627 var componentId = row.FieldAsString(1);
628 if (!String.IsNullOrEmpty(componentId))
629 {
630 if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component))
631 {
632 component.Add(user);
633 }
634 else
635 {
636 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Component_", componentId, "Component"));
637 }
638 }
639 else
640 {
641 this.DecompilerHelper.AddElementToRoot(user);
642 }
643
644 this.DecompilerHelper.IndexElement(row, user);
645 }
646 }
647
648 /// <summary>
649 /// Decompile the UserGroup table.
650 /// </summary>
651 /// <param name="table">The table to decompile.</param>
652 private void DecompileUserGroupTable(Table table)
653 {
654 foreach (var row in table.Rows)
655 {
656 var userId = row.FieldAsString(0);
657 if (this.DecompilerHelper.TryGetIndexedElement("User", userId, out var user))
658 {
659 user.Add(new XElement(UtilConstants.GroupRefName, new XAttribute("Id", row.FieldAsString(1))));
660 }
661 else
662 {
663 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, table.Name, row.GetPrimaryKey(), "Group_", userId, "Group"));
664 }
665 }
666 }
667
668 /// <summary>
669 /// Decompile the XmlConfig table.
670 /// </summary>
671 /// <param name="table">The table to decompile.</param>
672 private void DecompileXmlConfigTable(Table table)
673 {
674 foreach (var row in table.Rows)
675 {
676 var flags = row.FieldAsNullableInteger(7) ?? 0;
677 string node = null;
678 string action = null;
679 string on = null;
680
681 if (0x1 == (flags & 0x1))
682 {
683 node = "element";
684 }
685 else if (0x2 == (flags & 0x2))
686 {
687 node = "value";
688 }
689 else if (0x4 == (flags & 0x4))
690 {
691 node = "document";
692 }
693
694 if (0x10 == (flags & 0x10))
695 {
696 action = "create";
697 }
698 else if (0x20 == (flags & 0x20))
699 {
700 action = "delete";
701 }
702
703 if (0x100 == (flags & 0x100))
704 {
705 on = "install";
706 }
707 else if (0x200 == (flags & 0x200))
708 {
709 on = "uninstall";
710 }
711
712 var xmlConfig = new XElement(UtilConstants.XmlConfigName,
713 new XAttribute("Id", row.FieldAsString(0)),
714 new XAttribute("File", row.FieldAsString(1)),
715 AttributeIfNotNull("ElementId", row, 2),
716 AttributeIfNotNull("ElementPath", row, 3),
717 AttributeIfNotNull("VerifyPath", row, 4),
718 AttributeIfNotNull("Name", row, 5),
719 AttributeIfNotNull("Value", row, 6),
720 AttributeIfNotNull("Node", node),
721 AttributeIfNotNull("Action", action),
722 AttributeIfNotNull("On", on),
723 AttributeIfTrue("PreserveModifiedDate", 0x00001000 == (flags & 0x00001000)),
724 NumericAttributeIfNotNull("Sequence", row, 9)
725 );
726
727 this.DecompilerHelper.IndexElement(row, xmlConfig);
728 }
729 }
730
731 private void FinalizeCustomActions()
732 {
733 foreach (var customActionName in this.customActionNames)
734 {
735 if (CustomActionMapping.TryGetValue(customActionName, out var elementName))
736 {
737 this.DecompilerHelper.AddElementToRoot(elementName);
738 }
739 }
740 }
741
742 /// <summary>
743 /// Finalize the Perfmon table.
744 /// </summary>
745 /// <param name="tables">The collection of all tables.</param>
746 /// <remarks>
747 /// Since the PerfCounter element nests under a File element, but
748 /// the Perfmon table does not have a foreign key relationship with
749 /// the File table (instead it has a formatted string that usually
750 /// refers to a file row - but doesn't have to), the nesting must
751 /// be inferred during finalization.
752 /// </remarks>
753 private void FinalizePerfmonTable(TableIndexedCollection tables)
754 {
755 if (tables.TryGetTable("Perfmon", out var perfmonTable))
756 {
757 foreach (var row in perfmonTable.Rows)
758 {
759 var formattedFile = row.FieldAsString(1);
760
761 // try to "de-format" the File column's value to determine the proper parent File element
762 if ((formattedFile.StartsWith("[#", StringComparison.Ordinal) || formattedFile.StartsWith("[!", StringComparison.Ordinal))
763 && formattedFile.EndsWith("]", StringComparison.Ordinal))
764 {
765 var fileId = formattedFile.Substring(2, formattedFile.Length - 3);
766 if (this.DecompilerHelper.TryGetIndexedElement("File", fileId, out var file))
767 {
768 var perfCounter = this.DecompilerHelper.GetIndexedElement(row);
769 file.Add(perfCounter);
770 }
771 else
772 {
773 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, perfmonTable.Name, row.GetPrimaryKey(), "File", formattedFile, "File"));
774 }
775 }
776 else
777 {
778 this.Messaging.Write(UtilErrors.IllegalFileValueInPerfmonOrManifest(formattedFile, "Perfmon"));
779 }
780 }
781 }
782 }
783
784 /// <summary>
785 /// Finalize the PerfmonManifest table.
786 /// </summary>
787 /// <param name="tables">The collection of all tables.</param>
788 private void FinalizePerfmonManifestTable(TableIndexedCollection tables)
789 {
790 if (tables.TryGetTable("PerfmonManifest", out var perfmonManifestTable))
791 {
792 foreach (var row in perfmonManifestTable.Rows)
793 {
794 var formattedFile = row.FieldAsString(1);
795
796 // try to "de-format" the File column's value to determine the proper parent File element
797 if ((formattedFile.StartsWith("[#", StringComparison.Ordinal) || formattedFile.StartsWith("[!", StringComparison.Ordinal))
798 && formattedFile.EndsWith("]", StringComparison.Ordinal))
799 {
800 var perfCounterManifest = this.DecompilerHelper.GetIndexedElement(row);
801 var fileId = formattedFile.Substring(2, formattedFile.Length - 3);
802
803 if (this.DecompilerHelper.TryGetIndexedElement("File", fileId, out var file))
804 {
805 file.Add(perfCounterManifest);
806 }
807 else
808 {
809 var resourceFileDirectory = perfCounterManifest.Attribute("ResourceFileDirectory")?.Value;
810
811 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, resourceFileDirectory, row.GetPrimaryKey(), "File", formattedFile, "File"));
812 }
813 }
814 else
815 {
816 this.Messaging.Write(UtilErrors.IllegalFileValueInPerfmonOrManifest(formattedFile, "PerfmonManifest"));
817 }
818 }
819 }
820 }
821
822 /// <summary>
823 /// Finalize the SecureObjects table.
824 /// </summary>
825 /// <param name="tables">The collection of all tables.</param>
826 /// <remarks>
827 /// Nests the PermissionEx elements below their parent elements. There are no declared foreign
828 /// keys for the parents of the SecureObjects table.
829 /// </remarks>
830 private void FinalizeSecureObjectsTable(TableIndexedCollection tables)
831 {
832 var createFolderElementsByDirectoryId = new Dictionary<string, List<XElement>>();
833
834 // index the CreateFolder table because the foreign key to this table from the
835 // LockPermissions table is only part of the primary key of this table
836 if (tables.TryGetTable("CreateFolder", out var createFolderTable))
837 {
838 foreach (var row in createFolderTable.Rows)
839 {
840 var directoryId = row.FieldAsString(0);
841
842 if (!createFolderElementsByDirectoryId.TryGetValue(directoryId, out var createFolderElements))
843 {
844 createFolderElements = new List<XElement>();
845 createFolderElementsByDirectoryId.Add(directoryId, createFolderElements);
846 }
847
848 var createFolder = this.DecompilerHelper.GetIndexedElement(row);
849 createFolderElements.Add(createFolder);
850 }
851 }
852
853 if (tables.TryGetTable("SecureObjects", out var secureObjectsTable))
854 {
855 foreach (var row in secureObjectsTable.Rows)
856 {
857 var id = row.FieldAsString(0);
858 var table = row.FieldAsString(1);
859
860 var permissionEx = this.DecompilerHelper.GetIndexedElement(row);
861
862 if (table == "CreateFolder")
863 {
864 if (createFolderElementsByDirectoryId.TryGetValue(id, out var createFolderElements))
865 {
866 foreach (var createFolder in createFolderElements)
867 {
868 createFolder.Add(permissionEx);
869 }
870 }
871 else
872 {
873 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "SecureObjects", row.GetPrimaryKey(), "LockObject", id, table));
874 }
875 }
876 else
877 {
878 var parentElement = this.DecompilerHelper.GetIndexedElement(table, id);
879
880 if (parentElement != null)
881 {
882 parentElement.Add(permissionEx);
883 }
884 else
885 {
886 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, "SecureObjects", row.GetPrimaryKey(), "LockObject", id, table));
887 }
888 }
889 }
890 }
891 }
892
893 /// <summary>
894 /// Finalize the ServiceConfig table.
895 /// </summary>
896 /// <param name="tables">The collection of all tables.</param>
897 /// <remarks>
898 /// Since there is no foreign key from the ServiceName column to the
899 /// ServiceInstall table, this relationship must be handled late.
900 /// </remarks>
901 private void FinalizeServiceConfigTable(TableIndexedCollection tables)
902 {
903 //var serviceInstalls = new Hashtable();
904 var serviceInstallElementsByName = new Dictionary<string, List<XElement>>();
905
906 // index the ServiceInstall table because the foreign key used by the ServiceConfig
907 // table is actually the ServiceInstall.Name, not the ServiceInstall.ServiceInstall
908 // this is unfortunate because the service Name is not guaranteed to be unique, so
909 // decompiler must assume there could be multiple matches and add the ServiceConfig to each
910 // TODO: the Component column information should be taken into acount to accurately identify
911 // the correct column to use
912 if (tables.TryGetTable("ServiceInstall", out var serviceInstallTable))
913 {
914 foreach (var row in serviceInstallTable.Rows)
915 {
916 var name = row.FieldAsString(1);
917
918 if (!serviceInstallElementsByName.TryGetValue(name, out var serviceInstallElements))
919 {
920 serviceInstallElements = new List<XElement>();
921 serviceInstallElementsByName.Add(name, serviceInstallElements);
922 }
923
924 var serviceInstall = this.DecompilerHelper.GetIndexedElement(row);
925 serviceInstallElements.Add(serviceInstall);
926 }
927 }
928
929 if (tables.TryGetTable("ServiceConfig", out var serviceConfigTable))
930 {
931 foreach (var row in serviceConfigTable.Rows)
932 {
933 var serviceConfig = this.DecompilerHelper.GetIndexedElement(row);
934
935 if (row.FieldAsInteger(2) == 0)
936 {
937 var componentId = row.FieldAsString(1);
938 if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component))
939 {
940 component.Add(serviceConfig);
941 }
942 else
943 {
944 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, serviceConfigTable.Name, row.GetPrimaryKey(), "Component_", componentId, "Component"));
945 }
946 }
947 else
948 {
949 var name = row.FieldAsString(0);
950 if (serviceInstallElementsByName.TryGetValue(name, out var serviceInstallElements))
951 {
952 foreach (var serviceInstall in serviceInstallElements)
953 {
954 serviceInstall.Add(serviceConfig);
955 }
956 }
957 else
958 {
959 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, serviceConfigTable.Name, row.GetPrimaryKey(), "ServiceName", name, "ServiceInstall"));
960 }
961 }
962 }
963 }
964 }
965
966 /// <summary>
967 /// Finalize the XmlConfig table.
968 /// </summary>
969 /// <param name="tables">Collection of all tables.</param>
970 private void FinalizeXmlConfigTable(TableIndexedCollection tables)
971 {
972 if (tables.TryGetTable("Wix4XmlConfig", out var xmlConfigTable))
973 {
974 foreach (var row in xmlConfigTable.Rows)
975 {
976 var xmlConfig = this.DecompilerHelper.GetIndexedElement(row);
977
978 if (null != row[2])
979 {
980 var id = row.FieldAsString(2);
981 if (this.DecompilerHelper.TryGetIndexedElement("Wix4XmlConfig", id, out var parentXmlConfig))
982 {
983 parentXmlConfig.Add(xmlConfig);
984 }
985 else
986 {
987 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, xmlConfigTable.Name, row.GetPrimaryKey(), "ElementPath", (string)row[2], "XmlConfig"));
988 }
989 }
990 else
991 {
992 var componentId = row.FieldAsString(8);
993 if (this.DecompilerHelper.TryGetIndexedElement("Component", componentId, out var component))
994 {
995 component.Add(xmlConfig);
996 }
997 else
998 {
999 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, xmlConfigTable.Name, row.GetPrimaryKey(), "Component_", componentId, "Component"));
1000 }
1001 }
1002 }
1003 }
1004 }
1005
1006
1007 /// <summary>
1008 /// Finalize the XmlFile table.
1009 /// </summary>
1010 /// <param name="tables">The collection of all tables.</param>
1011 /// <remarks>
1012 /// Some of the XmlFile table rows are compiler generated from util:EventManifest node
1013 /// These rows should not be appended to component.
1014 /// </remarks>
1015 private void FinalizeXmlFileTable(TableIndexedCollection tables)
1016 {
1017 if (tables.TryGetTable("XmlFile", out var xmlFileTable))
1018 {
1019 var eventManifestTable = tables["EventManifest"];
1020
1021 foreach (var row in xmlFileTable.Rows)
1022 {
1023 var manifestGenerated = false;
1024 var xmlFileConfigId = (string)row[0];
1025 if (null != eventManifestTable)
1026 {
1027 foreach (var emrow in eventManifestTable.Rows)
1028 {
1029 var formattedFile = (string)emrow[1];
1030 if ((formattedFile.StartsWith("[#", StringComparison.Ordinal) || formattedFile.StartsWith("[!", StringComparison.Ordinal))
1031 && formattedFile.EndsWith("]", StringComparison.Ordinal))
1032 {
1033 var fileId = formattedFile.Substring(2, formattedFile.Length - 3);
1034 if (String.Equals(String.Concat("Config_", fileId, "ResourceFile"), xmlFileConfigId))
1035 {
1036 if (this.DecompilerHelper.TryGetIndexedElement(emrow, out var eventManifest))
1037 {
1038 eventManifest.Add(new XAttribute("ResourceFile", row.FieldAsString(4)));
1039 }
1040 manifestGenerated = true;
1041 }
1042
1043 else if (String.Equals(String.Concat("Config_", fileId, "MessageFile"), xmlFileConfigId))
1044 {
1045 if (this.DecompilerHelper.TryGetIndexedElement(emrow, out var eventManifest))
1046 {
1047 eventManifest.Add(new XAttribute("MessageFile", row.FieldAsString(4)));
1048 }
1049 manifestGenerated = true;
1050 }
1051 }
1052 }
1053 }
1054
1055 if (manifestGenerated)
1056 {
1057 continue;
1058 }
1059
1060 var action = "setValue";
1061 var flags = row.FieldAsInteger(5);
1062 if (0x1 == (flags & 0x1) && 0x2 == (flags & 0x2))
1063 {
1064 this.Messaging.Write(WarningMessages.IllegalColumnValue(row.SourceLineNumbers, xmlFileTable.Name, row.Fields[5].Column.Name, row[5]));
1065 }
1066 else if (0x1 == (flags & 0x1))
1067 {
1068 action = "createElement";
1069 }
1070 else if (0x2 == (flags & 0x2))
1071 {
1072 action = "deleteValue";
1073 }
1074
1075 var selectionLanguage = (0x100 == (flags & 0x100)) ? "XPath" : null;
1076 var preserveModifiedDate = 0x00001000 == (flags & 0x00001000);
1077 var permanent = 0x00010000 == (flags & 0x00010000);
1078
1079 if (this.DecompilerHelper.TryGetIndexedElement("Component", row.FieldAsString(6), out var component))
1080 {
1081 var xmlFile = new XElement(UtilConstants.XmlFileName,
1082 AttributeIfNotNull("Id", row, 0),
1083 AttributeIfNotNull("File", row, 1),
1084 AttributeIfNotNull("ElementPath", row, 2),
1085 AttributeIfNotNull("Name", row, 3),
1086 AttributeIfNotNull("Value", row, 4),
1087 AttributeIfNotNull("Action", action),
1088 AttributeIfNotNull("SelectionLanguage", selectionLanguage),
1089 AttributeIfTrue("PreserveModifiedDate", preserveModifiedDate),
1090 AttributeIfTrue("Permanent", permanent),
1091 NumericAttributeIfNotNull("Sequence", row, 7)
1092 );
1093
1094 component.Add(xmlFile);
1095 }
1096 else
1097 {
1098 this.Messaging.Write(WarningMessages.ExpectedForeignRow(row.SourceLineNumbers, xmlFileTable.Name, row.GetPrimaryKey(), "Component_", (string)row[6], "Component"));
1099 }
1100 }
1101 }
1102 }
1103
1104 /// <summary>
1105 /// Finalize the eventManifest table.
1106 /// This function must be called after FinalizeXmlFileTable
1107 /// </summary>
1108 /// <param name="tables">The collection of all tables.</param>
1109 private void FinalizeEventManifestTable(TableIndexedCollection tables)
1110 {
1111 if (tables.TryGetTable("EventManifest", out var eventManifestTable))
1112 {
1113 foreach (var row in eventManifestTable.Rows)
1114 {
1115 var eventManifest = this.DecompilerHelper.GetIndexedElement(row);
1116 var formattedFile = row.FieldAsString(1);
1117
1118 // try to "de-format" the File column's value to determine the proper parent File element
1119 if ((formattedFile.StartsWith("[#", StringComparison.Ordinal) || formattedFile.StartsWith("[!", StringComparison.Ordinal))
1120 && formattedFile.EndsWith("]", StringComparison.Ordinal))
1121 {
1122 var fileId = formattedFile.Substring(2, formattedFile.Length - 3);
1123
1124 if (this.DecompilerHelper.TryGetIndexedElement("File", fileId, out var file))
1125 {
1126 file.Add(eventManifest);
1127 }
1128 }
1129 else
1130 {
1131 this.Messaging.Write(UtilErrors.IllegalFileValueInPerfmonOrManifest(formattedFile, "EventManifest"));
1132 }
1133 }
1134 }
1135 }
1136
1137 private void AddPermissionAttributes(XElement element, Row row, int column, string[] specialPermissions)
1138 {
1139 var permissions = row.FieldAsInteger(column);
1140 for (var i = 0; i < 32; i++)
1141 {
1142 if (0 != ((permissions >> i) & 1))
1143 {
1144 string name = null;
1145
1146 if (16 > i && specialPermissions.Length > i)
1147 {
1148 name = specialPermissions[i];
1149 }
1150 else if (28 > i && UtilConstants.StandardPermissions.Length > (i - 16))
1151 {
1152 name = UtilConstants.StandardPermissions[i - 16];
1153 }
1154 else if (0 <= (i - 28) && UtilConstants.GenericPermissions.Length > (i - 28))
1155 {
1156 name = UtilConstants.GenericPermissions[i - 28];
1157 }
1158
1159 if (!String.IsNullOrEmpty(name))
1160 {
1161 element.Add(new XAttribute(name, "yes"));
1162 }
1163 else
1164 {
1165 this.Messaging.Write(WarningMessages.UnknownPermission(row.SourceLineNumbers, row.Table.Name, row.GetPrimaryKey(), i));
1166 }
1167 }
1168 }
1169 }
1170
1171 private static XAttribute AttributeIfNotNull(string name, string value)
1172 {
1173 return value == null ? null : new XAttribute(name, value);
1174 }
1175
1176 private static XAttribute AttributeIfNotNull(string name, bool value)
1177 {
1178 return new XAttribute(name, value ? "yes" : "no");
1179 }
1180
1181 private static XAttribute AttributeIfNotNull(string name, Row row, int field)
1182 {
1183 if (row[field] != null)
1184 {
1185 return new XAttribute(name, row.FieldAsString(field));
1186 }
1187
1188 return null;
1189 }
1190
1191 private static XAttribute NumericAttributeIfNotNull(string name, Row row, int field)
1192 {
1193 if (row[field] != null)
1194 {
1195 return new XAttribute(name, row.FieldAsInteger(field));
1196 }
1197
1198 return null;
1199 }
1200
1201 private static XAttribute AttributeIfTrue(string name, bool value)
1202 {
1203 return value ? new XAttribute(name, "yes") : null;
1204 }
1205 }
1206
1207 internal static class XElementExtensions
1208 {
1209 public static XElement AttributeIfNotNull(this XElement element, string name, Row row, int field)
1210 {
1211 if (row[field] != null)
1212 {
1213 element.Add(new XAttribute(name, row.FieldAsString(field)));
1214 }
1215
1216 return element;
1217 }
1218
1219 public static XElement NumericAttributeIfNotNull(this XElement element, string name, Row row, int field)
1220 {
1221 if (row[field] != null)
1222 {
1223 element.Add(new XAttribute(name, row.FieldAsInteger(field)));
1224 }
1225
1226 return element;
1227 }
1228 }
1229 }