aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs')
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs671
1 files changed, 671 insertions, 0 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
new file mode 100644
index 00000000..cf9c0332
--- /dev/null
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
@@ -0,0 +1,671 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.WindowsInstaller.Bind
4{
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.Linq;
9 using WixToolset.Core.Native;
10 using WixToolset.Data;
11 using WixToolset.Data.Tuples;
12
13 internal class SequenceActionsCommand
14 {
15 public SequenceActionsCommand(IntermediateSection section)
16 {
17 this.Section = section;
18
19 this.RelativeActionsForActions = new Dictionary<string, RelativeActions>();
20
21 this.StandardActionsById = WindowsInstallerStandard.StandardActions().ToDictionary(a => a.Id.Id);
22 }
23
24 private IntermediateSection Section { get; }
25
26 private Dictionary<string, RelativeActions> RelativeActionsForActions { get; }
27
28 private Dictionary<string, WixActionTuple> StandardActionsById { get; }
29
30 public Messaging Messaging { private get; set; }
31
32 public void Execute()
33 {
34 var actions = this.Section.Tuples.OfType<WixActionTuple>().ToList();
35 var suppressActions = this.Section.Tuples.OfType<WixSuppressActionTuple>().ToList();
36
37 this.SequenceActions(actions, suppressActions);
38 }
39
40 /// <summary>
41 /// Set sequence numbers for all the actions and create rows in the output object.
42 /// </summary>
43 /// <param name="actionRows">Collection of actions to schedule.</param>
44 /// <param name="suppressActionRows">Collection of actions to suppress.</param>
45 private void SequenceActions(List<WixActionTuple> actionRows, List<WixSuppressActionTuple> suppressActionRows)
46 {
47 var overridableActionRows = new Dictionary<string, WixActionTuple>();
48 var requiredActionRows = new Dictionary<string, WixActionTuple>();
49
50 // Get the standard actions required based on tuples in the section.
51 var requiredActionIds = this.GetRequiredActionIds();
52
53 foreach (var actionId in requiredActionIds)
54 {
55 var standardAction = this.StandardActionsById[actionId];
56
57 overridableActionRows.Add(standardAction.Id.Id, standardAction);
58 }
59
60 // Index all the action rows and look for collisions.
61 foreach (var actionRow in this.Section.Tuples.OfType<WixActionTuple>())
62 {
63 if (actionRow.Overridable) // overridable action
64 {
65 if (overridableActionRows.TryGetValue(actionRow.Id.Id, out var collidingActionRow))
66 {
67 this.Messaging.OnMessage(WixErrors.OverridableActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
68 if (null != collidingActionRow.SourceLineNumbers)
69 {
70 this.Messaging.OnMessage(WixErrors.OverridableActionCollision2(collidingActionRow.SourceLineNumbers));
71 }
72 }
73 else
74 {
75 overridableActionRows.Add(actionRow.Id.Id, actionRow);
76 }
77 }
78 else // unsequenced or sequenced action.
79 {
80 // Unsequenced action (allowed for certain standard actions).
81 if (null == actionRow.Before && null == actionRow.After && 0 == actionRow.Sequence)
82 {
83 if (this.StandardActionsById.TryGetValue(actionRow.Id.Id, out var standardAction))
84 {
85 // Populate the sequence from the standard action
86 actionRow.Sequence = standardAction.Sequence;
87 }
88 else // not a supported unscheduled action.
89 {
90 throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet);
91 }
92 }
93
94 if (overridableActionRows.TryGetValue(actionRow.Id.Id, out var collidingActionRow))
95 {
96 this.Messaging.OnMessage(WixErrors.ActionCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
97 if (null != collidingActionRow.SourceLineNumbers)
98 {
99 this.Messaging.OnMessage(WixErrors.ActionCollision2(collidingActionRow.SourceLineNumbers));
100 }
101 }
102 else
103 {
104 requiredActionRows.Add(actionRow.Id.Id, actionRow);
105 }
106 }
107 }
108
109 // Add the overridable action rows that are not overridden to the required action rows.
110 foreach (var actionRow in overridableActionRows.Values)
111 {
112 if (!requiredActionRows.ContainsKey(actionRow.Id.Id))
113 {
114 requiredActionRows.Add(actionRow.Id.Id, actionRow);
115 }
116 }
117
118 // Suppress the required actions that are overridable.
119 foreach (var suppressActionRow in suppressActionRows)
120 {
121 var key = suppressActionRow.Id.Id;
122
123 // If there is an overridable row to suppress; suppress it. There is no warning if there
124 // is no action to suppress because the action may be suppressed from a merge module in
125 // the binder.
126 if (requiredActionRows.TryGetValue(key, out var requiredActionRow))
127 {
128 if (requiredActionRow.Overridable)
129 {
130 this.Messaging.OnMessage(WixWarnings.SuppressAction(suppressActionRow.SourceLineNumbers, suppressActionRow.Action, suppressActionRow.SequenceTable.ToString()));
131 if (null != requiredActionRow.SourceLineNumbers)
132 {
133 this.Messaging.OnMessage(WixWarnings.SuppressAction2(requiredActionRow.SourceLineNumbers));
134 }
135
136 requiredActionRows.Remove(key);
137 }
138 else // suppressing a non-overridable action row
139 {
140 this.Messaging.OnMessage(WixErrors.SuppressNonoverridableAction(suppressActionRow.SourceLineNumbers, suppressActionRow.SequenceTable.ToString(), suppressActionRow.Action));
141 if (null != requiredActionRow.SourceLineNumbers)
142 {
143 this.Messaging.OnMessage(WixErrors.SuppressNonoverridableAction2(requiredActionRow.SourceLineNumbers));
144 }
145 }
146 }
147 }
148
149 // Build up dependency trees of the relatively scheduled actions.
150 // Use ToList() to create a copy of the required action rows so that new tuples can
151 // be added while enumerating.
152 foreach (var actionRow in requiredActionRows.Values.ToList())
153 {
154 if (0 == actionRow.Sequence)
155 {
156 // check for standard actions that don't have a sequence number in a merge module
157 if (SectionType.Module == this.Section.Type && WindowsInstallerStandard.IsStandardAction(actionRow.Action))
158 {
159 this.Messaging.OnMessage(WixErrors.StandardActionRelativelyScheduledInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
160 }
161
162 this.SequenceActionRow(actionRow, requiredActionRows);
163 }
164 else if (SectionType.Module == this.Section.Type && 0 < actionRow.Sequence && !WindowsInstallerStandard.IsStandardAction(actionRow.Action)) // check for custom actions and dialogs that have a sequence number
165 {
166 this.Messaging.OnMessage(WixErrors.CustomActionSequencedInModule(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action));
167 }
168 }
169
170 // Look for standard actions with sequence restrictions that aren't necessarily scheduled based
171 // on the presence of a particular table.
172 if (requiredActionRows.ContainsKey("InstallExecuteSequence/DuplicateFiles") && !requiredActionRows.ContainsKey("InstallExecuteSequence/InstallFiles"))
173 {
174 var standardAction = this.StandardActionsById["InstallExecuteSequence/InstallFiles"];
175 requiredActionRows.Add(standardAction.Id.Id, standardAction);
176 }
177
178 // Schedule actions.
179 List<WixActionTuple> scheduledActionRows;
180 if (SectionType.Module == this.Section.Type)
181 {
182 scheduledActionRows = requiredActionRows.Values.ToList();
183 }
184 else
185 {
186 scheduledActionRows = ScheduleActions(requiredActionRows);
187 }
188
189 // Remove all existing WixActionTuples from the section then add the
190 // scheduled actions back to the section. Note: we add the indices in
191 // reverse order to make it easy to remove them from the list later.
192 var removeIndices = new List<int>();
193 for (var i = this.Section.Tuples.Count - 1; i >= 0; --i)
194 {
195 var tuple = this.Section.Tuples[i];
196 if (tuple.Definition.Type == TupleDefinitionType.WixAction)
197 {
198 removeIndices.Add(i);
199 }
200 }
201
202 foreach (var removeIndex in removeIndices)
203 {
204 this.Section.Tuples.RemoveAt(removeIndex);
205 }
206
207 foreach (var action in scheduledActionRows)
208 {
209 this.Section.Tuples.Add(action);
210 }
211 }
212
213 private List<WixActionTuple> ScheduleActions(Dictionary<string, WixActionTuple> requiredActionRows)
214 {
215 var scheduledActionRows = new List<WixActionTuple>();
216
217 // Process each sequence table individually.
218 foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable)))
219 {
220 // Create a collection of just the action rows in this sequence
221 var sequenceActionRows = requiredActionRows.Values.Where(a => a.SequenceTable == sequenceTable).ToList();
222
223 // Schedule the absolutely scheduled actions (by sorting them by their sequence numbers).
224 var absoluteActionRows = new List<WixActionTuple>();
225 foreach (var actionRow in sequenceActionRows)
226 {
227 if (0 != actionRow.Sequence)
228 {
229 // Look for sequence number collisions
230 foreach (var sequenceScheduledActionRow in absoluteActionRows)
231 {
232 if (sequenceScheduledActionRow.Sequence == actionRow.Sequence)
233 {
234 this.Messaging.OnMessage(WixWarnings.ActionSequenceCollision(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, sequenceScheduledActionRow.Action, actionRow.Sequence));
235 if (null != sequenceScheduledActionRow.SourceLineNumbers)
236 {
237 this.Messaging.OnMessage(WixWarnings.ActionSequenceCollision2(sequenceScheduledActionRow.SourceLineNumbers));
238 }
239 }
240 }
241
242 absoluteActionRows.Add(actionRow);
243 }
244 }
245
246 absoluteActionRows.Sort((x, y) => x.Sequence.CompareTo(y.Sequence));
247
248 // Schedule the relatively scheduled actions (by resolving the dependency trees).
249 var previousUsedSequence = 0;
250 var relativeActionRows = new List<WixActionTuple>();
251 for (int j = 0; j < absoluteActionRows.Count; j++)
252 {
253 var absoluteActionRow = absoluteActionRows[j];
254
255 // Get all the relatively scheduled action rows occuring before and after this absolutely scheduled action row.
256 var relativeActions = this.GetAllRelativeActionsForSequenceType(sequenceTable, absoluteActionRow);
257
258 // Check for relatively scheduled actions occuring before/after a special action
259 // (those actions with a negative sequence number).
260 if (absoluteActionRow.Sequence < 0 && (relativeActions.PreviousActions.Any() || relativeActions.NextActions.Any()))
261 {
262 // Create errors for all the before actions.
263 foreach (var actionRow in relativeActions.PreviousActions)
264 {
265 this.Messaging.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action));
266 }
267
268 // Create errors for all the after actions.
269 foreach (var actionRow in relativeActions.NextActions)
270 {
271 this.Messaging.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, absoluteActionRow.Action));
272 }
273
274 // If there is source line information for the absolutely scheduled action display it
275 if (absoluteActionRow.SourceLineNumbers != null)
276 {
277 this.Messaging.OnMessage(WixErrors.ActionScheduledRelativeToTerminationAction2(absoluteActionRow.SourceLineNumbers));
278 }
279
280 continue;
281 }
282
283 // Schedule the action rows before this one.
284 var unusedSequence = absoluteActionRow.Sequence - 1;
285 for (var i = relativeActions.PreviousActions.Count - 1; i >= 0; i--)
286 {
287 var relativeActionRow = relativeActions.PreviousActions[i];
288
289 // look for collisions
290 if (unusedSequence == previousUsedSequence)
291 {
292 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action));
293 if (absoluteActionRow.SourceLineNumbers != null)
294 {
295 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers));
296 }
297
298 unusedSequence++;
299 }
300
301 relativeActionRow.Sequence = unusedSequence;
302 relativeActionRows.Add(relativeActionRow);
303
304 unusedSequence--;
305 }
306
307 // Determine the next used action sequence number.
308 var nextUsedSequence = Int16.MaxValue + 1;
309 if (absoluteActionRows.Count > j + 1)
310 {
311 nextUsedSequence = absoluteActionRows[j + 1].Sequence;
312 }
313
314 // Schedule the action rows after this one.
315 unusedSequence = absoluteActionRow.Sequence + 1;
316 for (var i = 0; i < relativeActions.NextActions.Count; i++)
317 {
318 var relativeActionRow = relativeActions.NextActions[i];
319
320 if (unusedSequence == nextUsedSequence)
321 {
322 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber(relativeActionRow.SourceLineNumbers, relativeActionRow.SequenceTable.ToString(), relativeActionRow.Action, absoluteActionRow.Action));
323 if (absoluteActionRow.SourceLineNumbers != null)
324 {
325 this.Messaging.OnMessage(WixErrors.NoUniqueActionSequenceNumber2(absoluteActionRow.SourceLineNumbers));
326 }
327
328 unusedSequence--;
329 }
330
331 relativeActionRow.Sequence = unusedSequence;
332 relativeActionRows.Add(relativeActionRow);
333
334 unusedSequence++;
335 }
336
337 // keep track of this sequence number as the previous used sequence number for the next iteration
338 previousUsedSequence = absoluteActionRow.Sequence;
339 }
340
341 // add the absolutely and relatively scheduled actions to the list of scheduled actions
342 scheduledActionRows.AddRange(absoluteActionRows);
343 scheduledActionRows.AddRange(relativeActionRows);
344 }
345
346 return scheduledActionRows;
347 }
348
349 private IEnumerable<string> GetRequiredActionIds()
350 {
351 var set = new HashSet<string>();
352
353 // gather the required actions for the output type
354 if (SectionType.Product == this.Section.Type)
355 {
356 // AdminExecuteSequence table
357 set.Add("AdminExecuteSequence/CostFinalize");
358 set.Add("AdminExecuteSequence/CostInitialize");
359 set.Add("AdminExecuteSequence/FileCost");
360 set.Add("AdminExecuteSequence/InstallAdminPackage");
361 set.Add("AdminExecuteSequence/InstallFiles");
362 set.Add("AdminExecuteSequence/InstallFinalize");
363 set.Add("AdminExecuteSequence/InstallInitialize");
364 set.Add("AdminExecuteSequence/InstallValidate");
365
366 // AdminUISequence table
367 set.Add("AdminUISequence/CostFinalize");
368 set.Add("AdminUISequence/CostInitialize");
369 set.Add("AdminUISequence/ExecuteAction");
370 set.Add("AdminUISequence/FileCost");
371
372 // AdvtExecuteSequence table
373 set.Add("AdvtExecuteSequence/CostFinalize");
374 set.Add("AdvtExecuteSequence/CostInitialize");
375 set.Add("AdvtExecuteSequence/InstallFinalize");
376 set.Add("AdvtExecuteSequence/InstallValidate");
377 set.Add("AdvtExecuteSequence/PublishFeatures");
378 set.Add("AdvtExecuteSequence/PublishProduct");
379
380 // InstallExecuteSequence table
381 set.Add("InstallExecuteSequence/CostFinalize");
382 set.Add("InstallExecuteSequence/CostInitialize");
383 set.Add("InstallExecuteSequence/FileCost");
384 set.Add("InstallExecuteSequence/InstallFinalize");
385 set.Add("InstallExecuteSequence/InstallInitialize");
386 set.Add("InstallExecuteSequence/InstallValidate");
387 set.Add("InstallExecuteSequence/ProcessComponents");
388 set.Add("InstallExecuteSequence/PublishFeatures");
389 set.Add("InstallExecuteSequence/PublishProduct");
390 set.Add("InstallExecuteSequence/RegisterProduct");
391 set.Add("InstallExecuteSequence/RegisterUser");
392 set.Add("InstallExecuteSequence/UnpublishFeatures");
393 set.Add("InstallExecuteSequence/ValidateProductID");
394
395 // InstallUISequence table
396 set.Add("InstallUISequence/CostFinalize");
397 set.Add("InstallUISequence/CostInitialize");
398 set.Add("InstallUISequence/ExecuteAction");
399 set.Add("InstallUISequence/FileCost");
400 set.Add("InstallUISequence/ValidateProductID");
401 }
402
403 // Gather the required actions for each tuple type.
404 foreach (var tupleType in this.Section.Tuples.Select(t => t.Definition.Type).Distinct())
405 {
406 switch (tupleType)
407 {
408 case TupleDefinitionType.AppSearch:
409 set.Add("InstallExecuteSequence/AppSearch");
410 set.Add("InstallUISequence/AppSearch");
411 break;
412 case TupleDefinitionType.BindImage:
413 set.Add("InstallExecuteSequence/BindImage");
414 break;
415 case TupleDefinitionType.CCPSearch:
416 set.Add("InstallExecuteSequence/AppSearch");
417 set.Add("InstallExecuteSequence/CCPSearch");
418 set.Add("InstallExecuteSequence/RMCCPSearch");
419 set.Add("InstallUISequence/AppSearch");
420 set.Add("InstallUISequence/CCPSearch");
421 set.Add("InstallUISequence/RMCCPSearch");
422 break;
423 case TupleDefinitionType.Class:
424 set.Add("AdvtExecuteSequence/RegisterClassInfo");
425 set.Add("InstallExecuteSequence/RegisterClassInfo");
426 set.Add("InstallExecuteSequence/UnregisterClassInfo");
427 break;
428 case TupleDefinitionType.Complus:
429 set.Add("InstallExecuteSequence/RegisterComPlus");
430 set.Add("InstallExecuteSequence/UnregisterComPlus");
431 break;
432 case TupleDefinitionType.CreateFolder:
433 set.Add("InstallExecuteSequence/CreateFolders");
434 set.Add("InstallExecuteSequence/RemoveFolders");
435 break;
436 case TupleDefinitionType.DuplicateFile:
437 set.Add("InstallExecuteSequence/DuplicateFiles");
438 set.Add("InstallExecuteSequence/RemoveDuplicateFiles");
439 break;
440 case TupleDefinitionType.Environment:
441 set.Add("InstallExecuteSequence/WriteEnvironmentStrings");
442 set.Add("InstallExecuteSequence/RemoveEnvironmentStrings");
443 break;
444 case TupleDefinitionType.Extension:
445 set.Add("AdvtExecuteSequence/RegisterExtensionInfo");
446 set.Add("InstallExecuteSequence/RegisterExtensionInfo");
447 set.Add("InstallExecuteSequence/UnregisterExtensionInfo");
448 break;
449 case TupleDefinitionType.File:
450 set.Add("InstallExecuteSequence/InstallFiles");
451 set.Add("InstallExecuteSequence/RemoveFiles");
452 break;
453 case TupleDefinitionType.Font:
454 set.Add("InstallExecuteSequence/RegisterFonts");
455 set.Add("InstallExecuteSequence/UnregisterFonts");
456 break;
457 case TupleDefinitionType.IniFile:
458 case TupleDefinitionType.RemoveIniFile:
459 set.Add("InstallExecuteSequence/WriteIniValues");
460 set.Add("InstallExecuteSequence/RemoveIniValues");
461 break;
462 case TupleDefinitionType.IsolatedComponent:
463 set.Add("InstallExecuteSequence/IsolateComponents");
464 break;
465 case TupleDefinitionType.LaunchCondition:
466 set.Add("InstallExecuteSequence/LaunchConditions");
467 set.Add("InstallUISequence/LaunchConditions");
468 break;
469 case TupleDefinitionType.MIME:
470 set.Add("AdvtExecuteSequence/RegisterMIMEInfo");
471 set.Add("InstallExecuteSequence/RegisterMIMEInfo");
472 set.Add("InstallExecuteSequence/UnregisterMIMEInfo");
473 break;
474 case TupleDefinitionType.MoveFile:
475 set.Add("InstallExecuteSequence/MoveFiles");
476 break;
477 case TupleDefinitionType.MsiAssembly:
478 set.Add("AdvtExecuteSequence/MsiPublishAssemblies");
479 set.Add("InstallExecuteSequence/MsiPublishAssemblies");
480 set.Add("InstallExecuteSequence/MsiUnpublishAssemblies");
481 break;
482 case TupleDefinitionType.MsiServiceConfig:
483 case TupleDefinitionType.MsiServiceConfigFailureActions:
484 set.Add("InstallExecuteSequence/MsiConfigureServices");
485 break;
486 case TupleDefinitionType.ODBCDataSource:
487 case TupleDefinitionType.ODBCTranslator:
488 case TupleDefinitionType.ODBCDriver:
489 set.Add("InstallExecuteSequence/SetODBCFolders");
490 set.Add("InstallExecuteSequence/InstallODBC");
491 set.Add("InstallExecuteSequence/RemoveODBC");
492 break;
493 case TupleDefinitionType.ProgId:
494 set.Add("AdvtExecuteSequence/RegisterProgIdInfo");
495 set.Add("InstallExecuteSequence/RegisterProgIdInfo");
496 set.Add("InstallExecuteSequence/UnregisterProgIdInfo");
497 break;
498 case TupleDefinitionType.PublishComponent:
499 set.Add("AdvtExecuteSequence/PublishComponents");
500 set.Add("InstallExecuteSequence/PublishComponents");
501 set.Add("InstallExecuteSequence/UnpublishComponents");
502 break;
503 case TupleDefinitionType.Registry:
504 case TupleDefinitionType.RemoveRegistry:
505 set.Add("InstallExecuteSequence/WriteRegistryValues");
506 set.Add("InstallExecuteSequence/RemoveRegistryValues");
507 break;
508 case TupleDefinitionType.RemoveFile:
509 set.Add("InstallExecuteSequence/RemoveFiles");
510 break;
511 case TupleDefinitionType.SelfReg:
512 set.Add("InstallExecuteSequence/SelfRegModules");
513 set.Add("InstallExecuteSequence/SelfUnregModules");
514 break;
515 case TupleDefinitionType.ServiceControl:
516 set.Add("InstallExecuteSequence/StartServices");
517 set.Add("InstallExecuteSequence/StopServices");
518 set.Add("InstallExecuteSequence/DeleteServices");
519 break;
520 case TupleDefinitionType.ServiceInstall:
521 set.Add("InstallExecuteSequence/InstallServices");
522 break;
523 case TupleDefinitionType.Shortcut:
524 set.Add("AdvtExecuteSequence/CreateShortcuts");
525 set.Add("InstallExecuteSequence/CreateShortcuts");
526 set.Add("InstallExecuteSequence/RemoveShortcuts");
527 break;
528 case TupleDefinitionType.TypeLib:
529 set.Add("InstallExecuteSequence/RegisterTypeLibraries");
530 set.Add("InstallExecuteSequence/UnregisterTypeLibraries");
531 break;
532 case TupleDefinitionType.Upgrade:
533 set.Add("InstallExecuteSequence/FindRelatedProducts");
534 set.Add("InstallUISequence/FindRelatedProducts");
535
536 // Only add the MigrateFeatureStates action if MigrateFeature attribute is set on
537 // at least one UpgradeVersion element.
538 if (this.Section.Tuples.OfType<UpgradeTuple>().Any(t => (t.Attributes & MsiInterop.MsidbUpgradeAttributesMigrateFeatures) == MsiInterop.MsidbUpgradeAttributesMigrateFeatures))
539 {
540 set.Add("InstallExecuteSequence/MigrateFeatureStates");
541 set.Add("InstallUISequence/MigrateFeatureStates");
542 }
543 break;
544 }
545 }
546
547 return set;
548 }
549
550 private IEnumerable<WixActionTuple> GetActions(SequenceTable sequence, string[] actionNames)
551 {
552 foreach (var action in WindowsInstallerStandard.StandardActions())
553 {
554 if (action.SequenceTable == sequence && actionNames.Contains(action.Action))
555 {
556 yield return action;
557 }
558 }
559 }
560
561 /// <summary>
562 /// Sequence an action before or after a standard action.
563 /// </summary>
564 /// <param name="actionRow">The action row to be sequenced.</param>
565 /// <param name="requiredActionRows">Collection of actions which must be included.</param>
566 private void SequenceActionRow(WixActionTuple actionRow, Dictionary<string, WixActionTuple> requiredActionRows)
567 {
568 var after = false;
569
570 if (actionRow.After != null)
571 {
572 after = true;
573 }
574 else if (actionRow.Before == null)
575 {
576 throw new InvalidOperationException(WixStrings.EXP_FoundActionRowWithNoSequenceBeforeOrAfterColumnSet);
577 }
578
579 var parentActionName = (after ? actionRow.After : actionRow.Before);
580 var parentActionKey = actionRow.SequenceTable.ToString() + "/" + parentActionName;
581
582 if (!requiredActionRows.TryGetValue(parentActionKey, out var parentActionRow))
583 {
584 // If the missing parent action is a standard action (with a suggested sequence number), add it.
585 if (this.StandardActionsById.TryGetValue(parentActionKey, out parentActionRow))
586 {
587 // Create a clone to avoid modifying the static copy of the object.
588 // TODO: consider this: parentActionRow = parentActionRow.Clone();
589
590 requiredActionRows.Add(parentActionRow.Id.Id, parentActionRow);
591 }
592 else
593 {
594 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_FoundActionRowWinNonExistentAction, (after ? "After" : "Before"), parentActionName));
595 }
596 }
597 else if (actionRow == parentActionRow || this.ContainsChildActionRow(actionRow, parentActionRow)) // cycle detected
598 {
599 throw new WixException(WixErrors.ActionCircularDependency(actionRow.SourceLineNumbers, actionRow.SequenceTable.ToString(), actionRow.Action, parentActionRow.Action));
600 }
601
602 // Add this action to the appropriate list of dependent action rows.
603 var relativeActions = this.GetRelativeActions(parentActionRow);
604 var relatedRows = (after ? relativeActions.NextActions : relativeActions.PreviousActions);
605 relatedRows.Add(actionRow);
606 }
607
608 private bool ContainsChildActionRow(WixActionTuple childTuple, WixActionTuple parentTuple)
609 {
610 var result = false;
611
612 if (this.RelativeActionsForActions.TryGetValue(childTuple.Id.Id, out var relativeActions))
613 {
614 result = relativeActions.NextActions.Any(a => a.SequenceTable == parentTuple.SequenceTable && a.Id.Id == parentTuple.Id.Id) ||
615 relativeActions.PreviousActions.Any(a => a.SequenceTable == parentTuple.SequenceTable && a.Id.Id == parentTuple.Id.Id);
616 }
617
618 return result;
619 }
620
621 private RelativeActions GetRelativeActions(WixActionTuple action)
622 {
623 if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions))
624 {
625 relativeActions = new RelativeActions();
626 this.RelativeActionsForActions.Add(action.Id.Id, relativeActions);
627 }
628
629 return relativeActions;
630 }
631
632 private RelativeActions GetAllRelativeActionsForSequenceType(SequenceTable sequenceType, WixActionTuple action)
633 {
634 var relativeActions = new RelativeActions();
635
636 if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives))
637 {
638 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, relativeActions.PreviousActions);
639
640 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, relativeActions.NextActions);
641 }
642
643 return relativeActions;
644 }
645
646 private void RecurseRelativeActionsForSequenceType(SequenceTable sequenceType, List<WixActionTuple> actions, List<WixActionTuple> visitedActions)
647 {
648 foreach (var action in actions.Where(a => a.SequenceTable == sequenceType))
649 {
650 if (this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var actionRelatives))
651 {
652 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.PreviousActions, visitedActions);
653 }
654
655 visitedActions.Add(action);
656
657 if (actionRelatives != null)
658 {
659 this.RecurseRelativeActionsForSequenceType(sequenceType, actionRelatives.NextActions, visitedActions);
660 }
661 }
662 }
663
664 private class RelativeActions
665 {
666 public List<WixActionTuple> PreviousActions { get; } = new List<WixActionTuple>();
667
668 public List<WixActionTuple> NextActions { get; } = new List<WixActionTuple>();
669 }
670 }
671}