1    	//  =========================================================================
2    	//  
3    	//                             INTEL CONFIDENTIAL
4    	//                            Copyright 2005 - 2015
5    	//                    Intel Corporation All Rights Reserved. 
6    	//  
7    	//  =========================================================================
8    	//  The source code contained or described herein and all documents 
9    	//  related to the source code ("Material") are owned by Intel Corporation 
10   	//  or its suppliers or licensors. Title to the Material remains with 
11   	//  Intel Corporation or its suppliers and licensors. The Material contains 
12   	//  trade secrets and proprietary and confidential information of Intel or 
13   	//  its suppliers and licensors. The Material is protected by worldwide 
14   	//  copyright and trade secret laws and treaty provisions. No part of the 
15   	//  Material may be used, copied, reproduced, modified, published, uploaded, 
16   	//  posted, transmitted, distributed, or disclosed in any way without Intel’s 
17   	//  prior express written permission.
18   	//  
19   	//  No license under any patent, copyright, trade secret or other intellectual 
20   	//  property right is granted to or conferred upon you by disclosure or 
21   	//  delivery of the Materials, either expressly, by implication, inducement, 
22   	//  estoppel or otherwise. Any license under such intellectual property rights 
23   	//  must be express and approved by Intel in writing.
24   	//  ==========================================================================
25   	
26   	using System;
27   	using System.ComponentModel;
28   	using System.Windows;
29   	using System.Windows.Threading;
30   	using Intel.Deployment.Bootstrapper.Models;
31   	using Intel.Deployment.Bootstrapper.Views;
32   	using Microsoft.Tools.WindowsInstallerXml.Bootstrapper;
33   	
34   	namespace Intel.Deployment.Bootstrapper.ViewModels
35   	{
36   	    public class MainViewModel : MainViewModelBase
37   	    {
38   	        #region Constructor
39   	        public MainViewModel()
40   	        {
41   	            this.Window = new MainView(this);
42   	            this.Dispatcher = Dispatcher.CurrentDispatcher;
43   	
44   	            this.Wizard = new Wizard();
45   	            this.ProgressMutex = new object();
46   	            this.CancelSetup = false;
47   	        }
48   	        #endregion
49   	
50   	        #region Fields
51   	        internal object ProgressMutex;
52   	        private string _BannerTitle;
53   	        private Wizard _Wizard;
54   	
55   	        /// <summary>
56   	        /// Gets or sets a value indicating whether the user is attempting
57   	        /// to cancel the setup from the bootstrapper application UI.
58   	        /// </summary>
59   	        internal volatile bool CancelSetup;
60   	
61   	        /// <summary>
62   	        /// Gets and sets whether the view model considers this install to be a downgrade.
63   	        /// </summary>
64   	        private volatile bool Downgrade;
65   	
66   	        private DetectModel _DetectModel;
67   	        private PlanModel _PlanModel;
68   	        private ApplyModel _ApplyModel;
69   	        private ExtractModel _ExtractModel;
70   	        #endregion
71   	
72   	        #region Properties
73   	        public Dispatcher Dispatcher { get; set; }
74   	
75   	        public Command Command
76   	        { get { return BootstrapperApp.Current.Command; } }
77   	        public Engine Engine
78   	        { get { return BootstrapperApp.Current.Engine; } }
79   	
80   	        public string BannerTitle
81   	        {
82   	            get { return this._BannerTitle; }
83   	            set
84   	            {
85   	                if (this._BannerTitle != value)
86   	                {
87   	                    this._BannerTitle = value;
88   	                    base.OnPropertyChanged("BannerTitle");
89   	                }
90   	            }
91   	        }
92   	
93   	        public string InstallTypeMessage
94   	        {
95   	            get
96   	            {
97   	                switch (Settings.Current.RunMode)
98   	                {
99   	                    case RunMode.Repair:
100  	                        return Resource.IntroView_RepairMessage;
101  	                    case RunMode.Uninstall:
102  	                        return Resource.IntroView_UninstallMessage;
103  	                    case RunMode.Install:
104  	                    default:
105  	                        return Resource.IntroView_InstallMessage;
106  	                }
107  	            }
108  	        }
109  	
110  	        public bool IsUninstall => Settings.Current.RunMode == RunMode.Uninstall;
111  	
112  	        public bool IsNotUninstall => !IsUninstall;
113  	
114  	        public Wizard Wizard
115  			{
116  				get { return this._Wizard; }
117  				set
118  				{
119  					if (this._Wizard != value)
120  					{
121  						this._Wizard = value;
122  						base.OnPropertyChanged("Wizard");
123  					}
124  				}
125  			}
126  	
127  			public NoInstallView NoInstallView { get; set; }
128  	
129  			public virtual DetectModel DetectModel
130  			{ get { return this._DetectModel ?? (this._DetectModel = new DetectModel()); } }
131  			public virtual PlanModel PlanModel
132  			{ get { return this._PlanModel ?? (this._PlanModel = new PlanModel()); } }
133  			public virtual ApplyModel ApplyModel
134  			{ get { return this._ApplyModel ?? (this._ApplyModel = new ApplyModel()); } }
135  			internal virtual ExtractModel ExtractModel
136  			{ get { return this._ExtractModel ?? (this._ExtractModel = new ExtractModel()); } }
137  			#endregion
138  	
139  			#region Methods
140  			public override void Run()
141  			{
142  				this.BannerTitle = this.Engine.StringVariables["WixBundleNameBanner"];
143  	
144  				InitializeModels();
145  	
146  				switch (Settings.Current.RunMode)
147  				{
148  					case RunMode.Uninstall:
149  						this.Wizard.AddView(new DetectProgressView(this));
150  						if (Settings.Current.Display == Display.Full)
151  						{
152  							this.Wizard.AddView(new IntroView(this));
153  						}
154  						this.Wizard.AddView(new ApplyProgressView(this));
155  						
156  						this.DetectState();
157  						break;
158  					case RunMode.Install:
159  				case RunMode.Modify:
160  					case RunMode.Repair:
161  						this.Wizard.AddView(new DetectProgressView(this));
162  						if (Settings.Current.Display == Display.Full)
163  						{
164  							this.Wizard.AddView(new IntroView(this));
165  							this.Wizard.AddView(new LicenseView(this));
166  	#if NDABUILD
167  	                        this.Wizard.AddView(new ReadmeViewNda(this));
168  	#else
169  	                        this.Wizard.AddView(new ReadmeViewPublic(this));
170  	#endif
171  	                    }
172  						this.Wizard.AddView(new ApplyProgressView(this));
173  	
174  						this.DetectState();
175  						break;
176  					case RunMode.Help:
177  						this.Wizard.AddView(new HelpView(this));
178  						break;
179  					case RunMode.DisplayVersion: //TODO IMPORTANT: This.
180  						break;
181  					case RunMode.NoInstall:
182  						this.InitializeNoInstall();
183  						this.NoInstallView.Show();
184  						break;
185  					case RunMode.Extract:
186  						this.Wizard.AddView(new ExtractProgressView(this));
187  						this.ExtractModel.BeginExtract(Settings.Current.TargetPath);
188  						break;
189  					case RunMode.ExtractMup:
190  						this.Wizard.AddView(new ExtractProgressView(this));
191  						this.ExtractModel.BeginExtractMup(Settings.Current.TargetPath);
192  						break;
193  	                default:
194  						break;
195  				}
196  				
197  				if (Settings.Current.Display > Display.None)
198  				{
199  					this.Wizard.NextView();
200  					this.Window.Show();
201  	
202  					if (Settings.Current.RunMode == RunMode.NoInstall)
203  					{
204  						this.NoInstallView.Focus();
205  					}
206  				}
207  	
208  				Dispatcher.Run();
209  			}
210  	
211  			public override void RunToError(int error)
212  			{
213  				this.BannerTitle = this.Engine.StringVariables["WixBundleNameBanner"];
214  	
215  	            ErrorView errView = new ErrorView(this, error);
216  	
217  				this.Wizard.Interrupt(errView);
218  	
219  				this.Window.Show();
220  	
221  				Dispatcher.Run();
222  			}
223  	
224  	        public override void RunToErrorDispatched(int error)
225  	        {
226  	            Dispatcher.Invoke(() => RunToError(error));
227  	        }
228  	
229  	        private void InitializeModels()
230  			{
231  				BootstrapperApp.Current.Error      += this.BootstrapperApplication_Error;
232  	
233  				this.DetectModel.DowngradeDetected += this.DetectModel_DowngradeDetected;
234  				this.DetectModel.DetectComplete    += this.DetectModel_DetectComplete;
235  	
236  				this.PlanModel.PlanComplete        += this.PlanModel_PlanComplete;
237  	
238  				this.ApplyModel.ApplyComplete      += this.ApplyModel_ApplyComplete;
239  	
240  				this.ExtractModel.ExtractComplete  += this.ExtractModel_ExtractComplete;
241  			}
242  	
243  			private void InitializeNoInstall()
244  			{
245  				//Create the noinstall navigation window
246  				this.NoInstallView = new NoInstallView(this);
247  	
248  				//Add each view
249  				this.Wizard.AddView(new HelpView(this));
250  				this.Wizard.AddView(new DetectProgressView(this));
251  				this.Wizard.AddView(new DowngradePromptView(this));
252  				this.Wizard.AddView(new IntroView(this));
253  				this.Wizard.AddView(new LicenseView(this));
254  				this.Wizard.AddView(new ReadmeViewNda(this));
255  	            this.Wizard.AddView(new ReadmeViewPublic(this));
256  	            this.Wizard.AddView(new ApplyProgressView(this));
257  				this.Wizard.AddView(new FinishView(this, RunMode.Install) { NoInstallViewName = "Install Completion" });
258  				this.Wizard.AddView(new RestartView(this));
259  				this.Wizard.AddView(new ExtractProgressView(this));
260  				this.Wizard.AddView(new FinishView(this, RunMode.Extract) { NoInstallViewName = "Extract Completion" });
261  				this.Wizard.AddView(new CancelSetupView(this));
262  	
263  				//Add the error view with each error
264  				this.Wizard.AddView(new ErrorView(this, 1613));
265  				this.Wizard.AddView(new ErrorView(this, Error.RebootPending));
266  				this.Wizard.AddView(new ErrorView(this, Error.InvalidOperatingSystem));
267  				this.Wizard.AddView(new ErrorView(this, Error.NoDeviceMatch));
268  				this.Wizard.AddView(new ErrorView(this, Error.Extract));
269  				this.Wizard.AddView(new ErrorView(this, Error.BadPath));
270  				this.Wizard.AddView(new ErrorView(this, Win32Error.AccessDenied));
271  				this.Wizard.AddView(new ErrorView(this, Win32Error.InvalidCommandLine));
272  				this.Wizard.AddView(new ErrorView(this, int.MaxValue)); //An unknown error.
273  			}
274  	
275  	#region Methods - Event Handlers
276  			private void DetectModel_DowngradeDetected(object sender, EventArgs e)
277  			{
278  				this.Window.Invoke(() => { this.OnDowngradeDetected(e); });
279  			}
280  			private void DetectModel_DetectComplete(object sender, SuccessEventArgs e)
281  			{
282  				this.Window.Invoke(() => { this.OnDetectComplete(e); });
283  			}
284  			private void PlanModel_PlanComplete(object sender, SuccessEventArgs e)
285  			{
286  				this.Window.BeginInvoke(() => { this.OnPlanComplete(e); });
287  			}
288  	        private void ApplyModel_ApplyComplete(object sender, ApplyCompleteEventArgs e)
289  	        {
290  	            this.Window.BeginInvoke(() => {
291  	                this.OnApplyComplete(e);
292  	                //force UI refresh to avoid freezing on the end of installation,
293  	                //it happens in some 4k screens
294  	                this.Window.WindowState = WindowState.Minimized;
295  	                this.Window.WindowState = WindowState.Normal;
296  	            });
297  	        }
298  	        private void ExtractModel_ExtractComplete(object sender, SuccessEventArgs e)
299  			{
300  				this.Window.BeginInvoke(() => { this.OnExtractComplete(e); });
301  			}
302  	#endregion
303  	
304  	#region Detect
305  			public void DetectState()
306  			{
307  				this.CancelSetup = false;
308  				this.DetectModel.DetectState();
309  			}
310  	
311  			protected virtual void OnDowngradeDetected(EventArgs e)
312  			{
313  				this.Downgrade = true;
314  			}
315  	
316  			protected virtual void OnDetectComplete(SuccessEventArgs e)
317  			{
318  				if (e.Success)
319  				{
320  					//If it's installing a downgrade that is not forced...
321  					if (Settings.Current.RunMode == RunMode.Install && this.Downgrade && !Settings.Current.ForceDowngrade)
322  					{
323  						//...then indicate a conflict.
324  						this.OnDowngradeConflictDetected();
325  					}
326  					//..otherwise there's no downgrade issue, so continue as normal.
327  					else
328  					{
329  						if (Settings.Current.Display > Display.None)
330  						{
331  							this.Wizard.NextView();
332  						}
333  	
334  						//If the UI is not interactive, begin planning.
335  						if (Settings.Current.Display < Display.Full)
336  						{
337  							switch (Settings.Current.RunMode)
338  							{
339  								case RunMode.Install:
340  								case RunMode.Uninstall:
341  								case RunMode.Repair:
342  									this.Plan(Settings.Current.RunMode);
343  									break;
344  								default:
345  									//No other RunMode should ever be able get to this point, so the below code should never run.
346  									Log.WriteException(new Exception(" Error, invalid value: Settings.Current.RunMode = " + Settings.Current.RunMode));
347  									break;
348  							}
349  						}
350  					}
351  	
352  				}
353  				else
354  				{
355  					this.OnError(e.ErrorCode);
356  				}
357  			}
358  	
359  			/// <summary>
360  			/// Occurs when installing a downgrade that is not forced.
361  			/// </summary>
362  			protected virtual void OnDowngradeConflictDetected()
363  			{
364  				//If the UI is in interactive mode...
365  				if (Settings.Current.Display == Display.Full)
366  				{
367  					//...then go to the downgrade prompt view.
368  					this.Wizard.Interrupt(new DowngradePromptView(this));
369  				}
370  				else
371  				{
372  					//...otherwise, log an exception and quit.
373  					Log.WriteException(new Exception("Downgrade detected in non-interactive mode, and is not forced.", new Win32Exception(Error.DowngradeRefused)));
374  					this.OnError(Error.DowngradeRefused);
375  				}
376  			}
377  	#endregion
378  	
379  	#region Plan
380  			private void Plan(RunMode runMode)
381  			{
382  				this.CancelSetup = false;
383  	
384  				this.PlanModel.Plan(runMode.ToLaunchAction());
385  			}
386  	
387  			protected virtual void OnPlanComplete(SuccessEventArgs e)
388  			{
389  				if (e.Success)
390  				{
391  					//TODO: Put common stuff into a common library, such as the string "IIF_MSI_SWITCHES".
392  					//			And have that library read and write the switches to and from a Dictionary<string, string>.
393  					//			Intel.Tools namespace
394  					this.Engine.StringVariables["IIF_MSI_SWITCHES"] = Settings.Current.ToMsiPropertyString();
395  					this.ApplyModel.Apply(this.Window);
396  				}
397  				else
398  				{
399  					this.OnError(e.ErrorCode);
400  				}
401  			}
402  	#endregion
403  	
404  			internal int? OverrideError = null;
405  	
406  	#region Apply
407  			/// <summary>
408  			/// 
409  			/// </summary>
410  			/// <param name="e"></param>
411  			/// <remarks>
412  			/// e.Status possible values are ERROR_SUCCESS (0), ERROR_INSTALL_USEREXIT (1602), and ERROR_INSTALL_FAILURE (1603).
413  			/// </remarks>
414  			protected virtual void OnApplyComplete(ApplyCompleteEventArgs e)
415  			{
(1) Event missing_lock: Accessing "this.OverrideError" without holding lock "MainViewModel.ProgressMutex". Elsewhere, "MainViewModel.OverrideError" is written to with "MainViewModel.ProgressMutex" held 2 out of 2 times.
Also see events: [lock_acquire][example_access][lock_acquire][example_access]
416  				int error = this.OverrideError ?? (e.Status & 0x0000FFFF);
417  	
418  				//TODO: if the user specifies /norestart, will e.Restart be affected here???
419  				BootstrapperApp.Current.Result = error;
420  	
421  				if (Settings.Current.Display == Display.Full)
422  				{
423  					if (error == Win32Error.Success)
424  					{
425  						this.Wizard.NextView(e.Restart == ApplyRestart.None ? new FinishView(this) : new RestartView(this) as View);
426  					}
427  					else
428  					{
429  						this.OnError(error);
430  					}
431  				}
432  				else
433  				{
434  					//TODO: How do I handle restarts from embedded runs???
435  					if (e.Restart != ApplyRestart.None)
436  					{
437  						if (Settings.Current.Restart == Restart.Always || Settings.Current.Restart == Restart.Automatic)
438  						{
439  							BootstrapperApp.Current.Result = Win32Error.RebootInitiated;
440  						}
441  						else
442  						{
443  							BootstrapperApp.Current.Result = Win32Error.RebootRequired;
444  						}
445  					}
446  	
447  					if (Settings.Current.Display <= Display.None)
448  					{
449  						//Stop the dispatcher, allowing code to resume from 'Dispatcher.Run();'
450  						this.Dispatcher.InvokeShutdown();
451  					}
452  					else if (Settings.Current.Display == Display.Passive)
453  					{
454  						//Close the window, stopping the dispatcher, allowing code to resume from 'Dispatcher.Run();'
455  						this.Window.Close();
456  					}
457  				}
458  			}
459  	#endregion
460  	
461  	#region Extract
462  			protected virtual void OnExtractComplete(SuccessEventArgs e)
463  			{
464  				switch (Settings.Current.Display)
465  				{
466  					case Display.None:
467  						this.Dispatcher.InvokeShutdown();
468  						break;
469  					case Display.Passive:
470  						this.Window.Close();
471  						break;
472  					case Display.Full:
473  						if (e.ErrorCode == Win32Error.Success)
474  						{
475  							this.Wizard.AddView(new FinishView(this));
476  							this.Wizard.NextView();
477  						}
478  						else
479  						{
480  							this.OnError(e.ErrorCode);
481  						}
482  						break;
483  					default:
484  						break;
485  				}
486  			}
487  	#endregion
488  			
489  	#region Error
490  			protected virtual void OnError(int errorCode)
491  			{
492  				BootstrapperApp.Current.Result = errorCode;
493  	
494  				switch (Settings.Current.Display)
495  				{
496  					case Display.None:
497  						this.Dispatcher.InvokeShutdown();
498  						break;
499  					case Display.Passive:
500  						this.Window.Close();
501  						break;
502  					case Display.Full:
503  						this.Wizard.Interrupt(new ErrorView(this, errorCode));
504  						break;
505  					default:
506  						break;
507  				}
508  			}
509  	
510  			private void BootstrapperApplication_Error(object sender, ErrorEventArgs e)
511  			{
512  	            Log.WriteLine("BootstrapperApplication_Error");
(2) Event lock_acquire: Example 1: Acquiring lock "MainViewModel.ProgressMutex".
(4) Event lock_acquire: Example 2: Acquiring lock "MainViewModel.ProgressMutex".
Also see events: [missing_lock][example_access][example_access]
513  				lock (this.ProgressMutex)
514  				{
515  	
516  					//If the user cancelled the setup...
517  					if (this.IsCancelled(e.ErrorCode))
518  					{
519  						Log.WriteLine("Cancelled");
520  						//...then return Cancel.
521  						e.Result = Result.Cancel;
(3) Event example_access: Example 1 (cont.): "MainViewModel.OverrideError" is written to with lock "MainViewModel.ProgressMutex" held.
Also see events: [missing_lock][lock_acquire][lock_acquire][example_access]
522  						this.OverrideError = BootstrapperApp.Current.Result = Win32Error.InstallUserExit;
523  					}
524  					else
525  					{
526  	                    if (Settings.Current.Display == Display.Full)
527  						{
528  							int error;
529  	
530  							//If it's an error passed back from a custom action...
531  							if (IsCustomActionError(e, out error))
532  							{
533  								//...then set the override error.
(5) Event example_access: Example 2 (cont.): "MainViewModel.OverrideError" is written to with lock "MainViewModel.ProgressMutex" held.
Also see events: [missing_lock][lock_acquire][example_access][lock_acquire]
534  								this.OverrideError = BootstrapperApp.Current.Result = error;
535  							}
536  							//Else if some input is required...
537  							else if (ModalInputView.IsRequired(e))
538  							{
539  								//...then get it.
540  	
541  								//Create a modal input view on the UI thread...
542  								ModalInputView modalInput = null;
543  	
544  								this.Window.Invoke(() =>
545  								{
546  									modalInput = new ModalInputView(this, e);
547  									this.Wizard.Interrupt(modalInput);
548  								});
549  	
550  								//...and get its result.
551  								var result = modalInput.GetResult();
552  								if (result > Result.None)
553  								{
554  									e.Result = result;
555  								}
556  	
557  								//Go back to the previous view.
558  								//Note: So far, this method only supports being called during
559  								//      the Engine.Apply() step. If it gets use elsewhere in the
560  								//      future, then we may possibly need to also implement NextView().
561  								this.Window.Invoke(() => { this.Wizard.PreviousView(); });
562  							}
563  							//else don't change the result.
564  						}
565  					}
566  				}
567  			}
568  	
569  			private bool IsCustomActionError(ErrorEventArgs e, out int error)
570  			{
571  				//TODO: Generalize the errors...Chipset error needs to go in Chipset project.
572  	
573  				//Initialize error to zero.
574  				error = 0;
575  	            //Check if it's an error passed back from a custom action.
576  	            return e.Data.Count == 2 && e.Data[0] == "IIF_CustomActionError" && int.TryParse(e.Data[1], out error);
577  			}
578  	
579  			private bool IsCancelled(int error)
580  			{
581  				return
582  	
583  					//The user cancelled from the bootstrapper UI.
584  					this.CancelSetup ||
585  	
586  					//The user cancelled from the msi.
587  					error == Win32Error.InstallUserExit ||
588  	
589  					//The user cancelled in some other way.
590  					error == Win32Error.Cancelled;
591  			}
592  	#endregion
593  	
594  	#region Do Commands
595  			internal void DoBackCommand(object param)
596  			{
597  				this.Wizard.PreviousView();
598  			}
599  	
600  			internal void DoNextCommand(object param)
601  			{
602  				this.Wizard.NextView();
603  			}
604  	
605  			internal void DoNextApplyCommand(object param)
606  			{
607  				this.Wizard.NextView();
608  				this.Plan(Settings.Current.RunMode);
609  			}
610  	
611  			internal void DoCancelSetupCommand(object param)
612  			{
613  				this.Wizard.Interrupt(new CancelSetupView(this));
614  			}
615  	
616  			internal void DoCancelSetupCloseCommand(object param)
617  			{
618  				BootstrapperApp.Current.Result = Win32Error.InstallUserExit;
619  				this.Window.Close();
620  			}
621  	
622  			internal void DoCloseCommand(object param)
623  			{
624  				this.Window.Close();
625  			}
626  	
627  			internal void DoBackCommand_NoInstall(object param)
628  			{
629  				if (this.Wizard.HasPreviousView)
630  				{
631  					this.Wizard.PreviousView();
632  				}
633  			}
634  	
635  			internal void DoNextCommand_NoInstall(object param)
636  			{
637  				if (this.Wizard.HasNextView)
638  				{
639  					this.Wizard.NextView();
640  				}
641  				else
642  				{
643  					this.NoInstallView.Close();
644  					this.Window.Close();
645  				}
646  			}
647  	
648  			internal void DoCloseCommand_NoInstall(object param)
649  			{
650  				this.NoInstallView.Close();
651  				this.Window.Close();
652  			}
653  	#endregion
654  		
655  	#endregion
656  		}
657  	}
658