aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.WixBA/InstallationViewModel.cs
diff options
context:
space:
mode:
authorSean Hall <r.sean.hall@gmail.com>2021-05-06 18:55:18 -0500
committerSean Hall <r.sean.hall@gmail.com>2021-05-11 19:11:19 -0500
commit8c27fbe1bc8a6d83859aead2105d6d528c307726 (patch)
tree1e26f482e119fd9bcb348412766eaba31bf4fc84 /src/WixToolset.WixBA/InstallationViewModel.cs
parent3c88529e8e29f9763a6830f8d3ac29cd56a4cb33 (diff)
downloadwix-8c27fbe1bc8a6d83859aead2105d6d528c307726.tar.gz
wix-8c27fbe1bc8a6d83859aead2105d6d528c307726.tar.bz2
wix-8c27fbe1bc8a6d83859aead2105d6d528c307726.zip
Move WixToolset.WixBA into test/burn and use new PackageReferences.
Diffstat (limited to 'src/WixToolset.WixBA/InstallationViewModel.cs')
-rw-r--r--src/WixToolset.WixBA/InstallationViewModel.cs683
1 files changed, 0 insertions, 683 deletions
diff --git a/src/WixToolset.WixBA/InstallationViewModel.cs b/src/WixToolset.WixBA/InstallationViewModel.cs
deleted file mode 100644
index 2beebd02..00000000
--- a/src/WixToolset.WixBA/InstallationViewModel.cs
+++ /dev/null
@@ -1,683 +0,0 @@
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.WixBA
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Linq;
9 using System.Reflection;
10 using System.Windows;
11 using System.Windows.Input;
12 using IO = System.IO;
13 using WixToolset.Mba.Core;
14
15 /// <summary>
16 /// The states of detection.
17 /// </summary>
18 public enum DetectionState
19 {
20 Absent,
21 Present,
22 Newer,
23 }
24
25 /// <summary>
26 /// The states of installation.
27 /// </summary>
28 public enum InstallationState
29 {
30 Initializing,
31 Detecting,
32 Waiting,
33 Planning,
34 Applying,
35 Applied,
36 Failed,
37 }
38
39 /// <summary>
40 /// The model of the installation view in WixBA.
41 /// </summary>
42 public class InstallationViewModel : PropertyNotifyBase
43 {
44 private RootViewModel root;
45
46 private Dictionary<string, int> downloadRetries;
47 private bool downgrade;
48 private string downgradeMessage;
49
50 private ICommand licenseCommand;
51 private ICommand launchHomePageCommand;
52 private ICommand launchNewsCommand;
53 private ICommand launchVSExtensionPageCommand;
54 private ICommand installCommand;
55 private ICommand repairCommand;
56 private ICommand uninstallCommand;
57 private ICommand openLogCommand;
58 private ICommand openLogFolderCommand;
59 private ICommand tryAgainCommand;
60
61 private string message;
62 private DateTime cachePackageStart;
63 private DateTime executePackageStart;
64
65 /// <summary>
66 /// Creates a new model of the installation view.
67 /// </summary>
68 public InstallationViewModel(RootViewModel root)
69 {
70 this.root = root;
71 this.downloadRetries = new Dictionary<string, int>();
72
73 this.root.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.RootPropertyChanged);
74
75 WixBA.Model.Bootstrapper.DetectBegin += this.DetectBegin;
76 WixBA.Model.Bootstrapper.DetectRelatedBundle += this.DetectedRelatedBundle;
77 WixBA.Model.Bootstrapper.DetectComplete += this.DetectComplete;
78 WixBA.Model.Bootstrapper.PlanPackageBegin += this.PlanPackageBegin;
79 WixBA.Model.Bootstrapper.PlanComplete += this.PlanComplete;
80 WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin;
81 WixBA.Model.Bootstrapper.CacheAcquireBegin += this.CacheAcquireBegin;
82 WixBA.Model.Bootstrapper.CacheAcquireResolving += this.CacheAcquireResolving;
83 WixBA.Model.Bootstrapper.CacheAcquireComplete += this.CacheAcquireComplete;
84 WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin;
85 WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete;
86 WixBA.Model.Bootstrapper.Error += this.ExecuteError;
87 WixBA.Model.Bootstrapper.ApplyComplete += this.ApplyComplete;
88 }
89
90 void RootPropertyChanged(object sender, PropertyChangedEventArgs e)
91 {
92 if (("DetectState" == e.PropertyName) || ("InstallState" == e.PropertyName))
93 {
94 base.OnPropertyChanged("RepairEnabled");
95 base.OnPropertyChanged("InstallEnabled");
96 base.OnPropertyChanged("IsComplete");
97 base.OnPropertyChanged("IsSuccessfulCompletion");
98 base.OnPropertyChanged("IsFailedCompletion");
99 base.OnPropertyChanged("StatusText");
100 base.OnPropertyChanged("UninstallEnabled");
101 }
102 }
103
104 /// <summary>
105 /// Gets the version for the application.
106 /// </summary>
107 public string Version
108 {
109 get { return String.Concat("v", WixBA.Model.Version.ToString()); }
110 }
111
112 /// <summary>
113 /// The Publisher of this bundle.
114 /// </summary>
115 public string Publisher
116 {
117 get
118 {
119 string company = "[AssemblyCompany]";
120 return WixDistribution.ReplacePlaceholders(company, typeof(WixBA).Assembly);
121 }
122 }
123
124 /// <summary>
125 /// The Publisher of this bundle.
126 /// </summary>
127 public string SupportUrl
128 {
129 get
130 {
131 return WixDistribution.SupportUrl;
132 }
133 }
134 public string VSExtensionUrl
135 {
136 get
137 {
138 return WixDistribution.VSExtensionsLandingUrl;
139 }
140 }
141
142 public string Message
143 {
144 get
145 {
146 return this.message;
147 }
148
149 set
150 {
151 if (this.message != value)
152 {
153 this.message = value;
154 base.OnPropertyChanged("Message");
155 }
156 }
157 }
158
159 /// <summary>
160 /// Gets and sets whether the view model considers this install to be a downgrade.
161 /// </summary>
162 public bool Downgrade
163 {
164 get
165 {
166 return this.downgrade;
167 }
168
169 set
170 {
171 if (this.downgrade != value)
172 {
173 this.downgrade = value;
174 base.OnPropertyChanged("Downgrade");
175 }
176 }
177 }
178
179 public string DowngradeMessage
180 {
181 get
182 {
183 return this.downgradeMessage;
184 }
185 set
186 {
187 if (this.downgradeMessage != value)
188 {
189 this.downgradeMessage = value;
190 base.OnPropertyChanged("DowngradeMessage");
191 }
192 }
193 }
194
195 public ICommand LaunchHomePageCommand
196 {
197 get
198 {
199 if (this.launchHomePageCommand == null)
200 {
201 this.launchHomePageCommand = new RelayCommand(param => WixBA.LaunchUrl(this.SupportUrl), param => true);
202 }
203
204 return this.launchHomePageCommand;
205 }
206 }
207
208 public ICommand LaunchNewsCommand
209 {
210 get
211 {
212 if (this.launchNewsCommand == null)
213 {
214 this.launchNewsCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.NewsUrl), param => true);
215 }
216
217 return this.launchNewsCommand;
218 }
219 }
220
221 public ICommand LaunchVSExtensionPageCommand
222 {
223 get
224 {
225 if (this.launchVSExtensionPageCommand == null)
226 {
227 this.launchVSExtensionPageCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.VSExtensionsLandingUrl), param => true);
228 }
229
230 return this.launchVSExtensionPageCommand;
231 }
232 }
233
234 public ICommand LicenseCommand
235 {
236 get
237 {
238 if (this.licenseCommand == null)
239 {
240 this.licenseCommand = new RelayCommand(param => this.LaunchLicense(), param => true);
241 }
242
243 return this.licenseCommand;
244 }
245 }
246
247 public bool LicenseEnabled
248 {
249 get { return this.LicenseCommand.CanExecute(this); }
250 }
251
252 public ICommand CloseCommand
253 {
254 get { return this.root.CloseCommand; }
255 }
256
257 public bool IsComplete
258 {
259 get { return IsSuccessfulCompletion || IsFailedCompletion; }
260 }
261
262 public bool IsSuccessfulCompletion
263 {
264 get { return InstallationState.Applied == this.root.InstallState; }
265 }
266
267 public bool IsFailedCompletion
268 {
269 get { return InstallationState.Failed == this.root.InstallState; }
270 }
271
272 public ICommand InstallCommand
273 {
274 get
275 {
276 if (this.installCommand == null)
277 {
278 this.installCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Install), param => this.root.DetectState == DetectionState.Absent && this.root.InstallState == InstallationState.Waiting);
279 }
280
281 return this.installCommand;
282 }
283 }
284
285 public bool InstallEnabled
286 {
287 get { return this.InstallCommand.CanExecute(this); }
288 }
289
290 public ICommand RepairCommand
291 {
292 get
293 {
294 if (this.repairCommand == null)
295 {
296 this.repairCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Repair), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting);
297 }
298
299 return this.repairCommand;
300 }
301 }
302
303 public bool RepairEnabled
304 {
305 get { return this.RepairCommand.CanExecute(this); }
306 }
307
308 public ICommand UninstallCommand
309 {
310 get
311 {
312 if (this.uninstallCommand == null)
313 {
314 this.uninstallCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Uninstall), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting);
315 }
316
317 return this.uninstallCommand;
318 }
319 }
320
321 public bool UninstallEnabled
322 {
323 get { return this.UninstallCommand.CanExecute(this); }
324 }
325
326 public ICommand OpenLogCommand
327 {
328 get
329 {
330 if (this.openLogCommand == null)
331 {
332 this.openLogCommand = new RelayCommand(param => WixBA.OpenLog(new Uri(WixBA.Model.Engine.GetVariableString("WixBundleLog"))));
333 }
334 return this.openLogCommand;
335 }
336 }
337
338 public ICommand OpenLogFolderCommand
339 {
340 get
341 {
342 if (this.openLogFolderCommand == null)
343 {
344 string logFolder = IO.Path.GetDirectoryName(WixBA.Model.Engine.GetVariableString("WixBundleLog"));
345 this.openLogFolderCommand = new RelayCommand(param => WixBA.OpenLogFolder(logFolder));
346 }
347 return this.openLogFolderCommand;
348 }
349 }
350
351 public ICommand TryAgainCommand
352 {
353 get
354 {
355 if (this.tryAgainCommand == null)
356 {
357 this.tryAgainCommand = new RelayCommand(param =>
358 {
359 this.root.Canceled = false;
360 WixBA.Plan(WixBA.Model.PlannedAction);
361 }, param => IsFailedCompletion);
362 }
363
364 return this.tryAgainCommand;
365 }
366 }
367
368 public string StatusText
369 {
370 get
371 {
372 switch(this.root.InstallState)
373 {
374 case InstallationState.Applied:
375 return "Complete";
376 case InstallationState.Failed:
377 return this.root.Canceled ? "Cancelled" : "Failed";
378 default:
379 return "Unknown"; // this shouldn't be shown in the UI.
380 }
381 }
382 }
383
384 /// <summary>
385 /// Launches the license in the default viewer.
386 /// </summary>
387 private void LaunchLicense()
388 {
389 string folder = IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
390 WixBA.LaunchUrl(IO.Path.Combine(folder, "License.txt"));
391 }
392
393 private void DetectBegin(object sender, DetectBeginEventArgs e)
394 {
395 this.root.DetectState = e.Installed ? DetectionState.Present : DetectionState.Absent;
396 WixBA.Model.PlannedAction = LaunchAction.Unknown;
397 }
398
399 private void DetectedRelatedBundle(object sender, DetectRelatedBundleEventArgs e)
400 {
401 if (e.Operation == RelatedOperation.Downgrade)
402 {
403 this.Downgrade = true;
404 }
405
406 if (!WixBA.Model.BAManifest.Bundle.Packages.ContainsKey(e.ProductCode))
407 {
408 WixBA.Model.BAManifest.Bundle.AddRelatedBundleAsPackage(e);
409 }
410 }
411
412 private void DetectComplete(object sender, DetectCompleteEventArgs e)
413 {
414 // Parse the command line string before any planning.
415 this.ParseCommandLine();
416 this.root.InstallState = InstallationState.Waiting;
417
418 if (LaunchAction.Uninstall == WixBA.Model.Command.Action &&
419 ResumeType.Arp != WixBA.Model.Command.Resume) // MSI and WixStdBA require some kind of confirmation before proceeding so WixBA should, too.
420 {
421 WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for uninstall");
422 WixBA.Plan(LaunchAction.Uninstall);
423 }
424 else if (Hresult.Succeeded(e.Status))
425 {
426 if (this.Downgrade)
427 {
428 this.root.DetectState = DetectionState.Newer;
429 var relatedPackages = WixBA.Model.BAManifest.Bundle.Packages.Values.Where(p => p.Type == PackageType.UpgradeBundle);
430 var installedVersion = relatedPackages.Any() ? new Version(relatedPackages.Max(p => p.Version)) : null;
431 if (installedVersion != null && installedVersion < new Version(4, 1) && installedVersion.Build > 10)
432 {
433 this.DowngradeMessage = "You must uninstall WiX v" + installedVersion + " before you can install this.";
434 }
435 else
436 {
437 this.DowngradeMessage = "There is already a newer version of WiX installed on this machine.";
438 }
439 }
440
441 if (LaunchAction.Layout == WixBA.Model.Command.Action)
442 {
443 WixBA.PlanLayout();
444 }
445 else if (WixBA.Model.Command.Display != Display.Full)
446 {
447 // If we're not waiting for the user to click install, dispatch plan with the default action.
448 WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for non-interactive mode.");
449 WixBA.Plan(WixBA.Model.Command.Action);
450 }
451 }
452 else
453 {
454 this.root.InstallState = InstallationState.Failed;
455 }
456
457 // Force all commands to reevaluate CanExecute.
458 // InvalidateRequerySuggested must be run on the UI thread.
459 root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested));
460 }
461
462 private void PlanPackageBegin(object sender, PlanPackageBeginEventArgs e)
463 {
464 // If we're able to run our BA, we don't want to install the .NET Framework since the framework on the machine is already good enough.
465 if ( e.PackageId.StartsWith("NetFx4", StringComparison.OrdinalIgnoreCase))
466 {
467 e.State = RequestState.None;
468 }
469 }
470
471 private void PlanComplete(object sender, PlanCompleteEventArgs e)
472 {
473 if (Hresult.Succeeded(e.Status))
474 {
475 this.root.PreApplyState = this.root.InstallState;
476 this.root.InstallState = InstallationState.Applying;
477 WixBA.Model.Engine.Apply(this.root.ViewWindowHandle);
478 }
479 else
480 {
481 this.root.InstallState = InstallationState.Failed;
482 }
483 }
484
485 private void ApplyBegin(object sender, ApplyBeginEventArgs e)
486 {
487 this.downloadRetries.Clear();
488 }
489
490 private void CacheAcquireBegin(object sender, CacheAcquireBeginEventArgs e)
491 {
492 this.cachePackageStart = DateTime.Now;
493 }
494
495 private void CacheAcquireResolving(object sender, CacheAcquireResolvingEventArgs e)
496 {
497 if (e.Action == CacheResolveOperation.Download && !this.downloadRetries.ContainsKey(e.PackageOrContainerId))
498 {
499 this.downloadRetries.Add(e.PackageOrContainerId, 0);
500 }
501 }
502
503 private void CacheAcquireComplete(object sender, CacheAcquireCompleteEventArgs e)
504 {
505 this.AddPackageTelemetry("Cache", e.PackageOrContainerId ?? String.Empty, DateTime.Now.Subtract(this.cachePackageStart).TotalMilliseconds, e.Status);
506
507 if (e.Status < 0 && this.downloadRetries.TryGetValue(e.PackageOrContainerId, out var retries) && retries < 3)
508 {
509 this.downloadRetries[e.PackageOrContainerId] = retries + 1;
510 switch (e.Status)
511 {
512 case -2147023294: //HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT)
513 case -2147024894: //HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)
514 case -2147012889: //HRESULT_FROM_WIN32(ERROR_INTERNET_NAME_NOT_RESOLVED)
515 break;
516 default:
517 e.Action = BOOTSTRAPPER_CACHEACQUIRECOMPLETE_ACTION.Retry;
518 break;
519 }
520 }
521 }
522
523 private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e)
524 {
525 lock (this)
526 {
527 this.executePackageStart = e.ShouldExecute ? DateTime.Now : DateTime.MinValue;
528 }
529 }
530
531 private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e)
532 {
533 lock (this)
534 {
535 if (DateTime.MinValue < this.executePackageStart)
536 {
537 this.AddPackageTelemetry("Execute", e.PackageId ?? String.Empty, DateTime.Now.Subtract(this.executePackageStart).TotalMilliseconds, e.Status);
538 this.executePackageStart = DateTime.MinValue;
539 }
540 }
541 }
542
543 private void ExecuteError(object sender, ErrorEventArgs e)
544 {
545 lock (this)
546 {
547 if (!this.root.Canceled)
548 {
549 // If the error is a cancel coming from the engine during apply we want to go back to the preapply state.
550 if (InstallationState.Applying == this.root.InstallState && (int)Error.UserCancelled == e.ErrorCode)
551 {
552 this.root.InstallState = this.root.PreApplyState;
553 }
554 else
555 {
556 this.Message = e.ErrorMessage;
557
558 if (Display.Full == WixBA.Model.Command.Display)
559 {
560 // On HTTP authentication errors, have the engine try to do authentication for us.
561 if (ErrorType.HttpServerAuthentication == e.ErrorType || ErrorType.HttpProxyAuthentication == e.ErrorType)
562 {
563 e.Result = Result.TryAgain;
564 }
565 else // show an error dialog.
566 {
567 MessageBoxButton msgbox = MessageBoxButton.OK;
568 switch (e.UIHint & 0xF)
569 {
570 case 0:
571 msgbox = MessageBoxButton.OK;
572 break;
573 case 1:
574 msgbox = MessageBoxButton.OKCancel;
575 break;
576 // There is no 2! That would have been MB_ABORTRETRYIGNORE.
577 case 3:
578 msgbox = MessageBoxButton.YesNoCancel;
579 break;
580 case 4:
581 msgbox = MessageBoxButton.YesNo;
582 break;
583 // default: stay with MBOK since an exact match is not available.
584 }
585
586 MessageBoxResult result = MessageBoxResult.None;
587 WixBA.View.Dispatcher.Invoke((Action)delegate()
588 {
589 result = MessageBox.Show(WixBA.View, e.ErrorMessage, "WiX Toolset", msgbox, MessageBoxImage.Error);
590 }
591 );
592
593 // If there was a match from the UI hint to the msgbox value, use the result from the
594 // message box. Otherwise, we'll ignore it and return the default to Burn.
595 if ((e.UIHint & 0xF) == (int)msgbox)
596 {
597 e.Result = (Result)result;
598 }
599 }
600 }
601 }
602 }
603 else // canceled, so always return cancel.
604 {
605 e.Result = Result.Cancel;
606 }
607 }
608 }
609
610 private void ApplyComplete(object sender, ApplyCompleteEventArgs e)
611 {
612 WixBA.Model.Result = e.Status; // remember the final result of the apply.
613
614 // Set the state to applied or failed unless the state has already been set back to the preapply state
615 // which means we need to show the UI as it was before the apply started.
616 if (this.root.InstallState != this.root.PreApplyState)
617 {
618 this.root.InstallState = Hresult.Succeeded(e.Status) ? InstallationState.Applied : InstallationState.Failed;
619 }
620
621 // If we're not in Full UI mode, we need to alert the dispatcher to stop and close the window for passive.
622 if (Display.Full != WixBA.Model.Command.Display)
623 {
624 // If its passive, send a message to the window to close.
625 if (Display.Passive == WixBA.Model.Command.Display)
626 {
627 WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window for non-interactive install");
628 WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close));
629 }
630 else
631 {
632 WixBA.Dispatcher.InvokeShutdown();
633 }
634 return;
635 }
636 else if (Hresult.Succeeded(e.Status) && LaunchAction.UpdateReplace == WixBA.Model.PlannedAction) // if we successfully applied an update close the window since the new Bundle should be running now.
637 {
638 WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window since update successful.");
639 WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close));
640 return;
641 }
642 else if (root.AutoClose)
643 {
644 // Automatically closing since the user clicked the X button.
645 WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close));
646 return;
647 }
648
649 // Force all commands to reevaluate CanExecute.
650 // InvalidateRequerySuggested must be run on the UI thread.
651 root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested));
652 }
653
654 private void ParseCommandLine()
655 {
656 // Get array of arguments based on the system parsing algorithm.
657 string[] args = WixBA.Model.Command.CommandLineArgs;
658 for (int i = 0; i < args.Length; ++i)
659 {
660 if (args[i].StartsWith("InstallFolder=", StringComparison.InvariantCultureIgnoreCase))
661 {
662 // Allow relative directory paths. Also validates.
663 string[] param = args[i].Split(new char[] {'='}, 2);
664 this.root.InstallDirectory = IO.Path.Combine(Environment.CurrentDirectory, param[1]);
665 }
666 }
667 }
668
669 private void AddPackageTelemetry(string prefix, string id, double time, int result)
670 {
671 lock (this)
672 {
673 string key = String.Format("{0}Time_{1}", prefix, id);
674 string value = time.ToString();
675 WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value));
676
677 key = String.Format("{0}Result_{1}", prefix, id);
678 value = String.Concat("0x", result.ToString("x"));
679 WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value));
680 }
681 }
682 }
683}