From 8c27fbe1bc8a6d83859aead2105d6d528c307726 Mon Sep 17 00:00:00 2001 From: Sean Hall Date: Thu, 6 May 2021 18:55:18 -0500 Subject: Move WixToolset.WixBA into test/burn and use new PackageReferences. --- src/WixToolset.WixBA/BrowserProperties.cs | 40 -- src/WixToolset.WixBA/Hresult.cs | 22 - src/WixToolset.WixBA/InstallationViewModel.cs | 683 --------------------- src/WixToolset.WixBA/Model.cs | 132 ---- src/WixToolset.WixBA/NewsItem.cs | 18 - src/WixToolset.WixBA/ProgressViewModel.cs | 245 -------- src/WixToolset.WixBA/PropertyNotifyBase.cs | 59 -- src/WixToolset.WixBA/RelayCommand.cs | 45 -- .../Resources/logo-black-hollow.png | Bin 47472 -> 0 bytes .../Resources/logo-white-hollow.png | Bin 60557 -> 0 bytes src/WixToolset.WixBA/RootView.xaml | 420 ------------- src/WixToolset.WixBA/RootView.xaml.cs | 51 -- src/WixToolset.WixBA/RootViewModel.cs | 202 ------ src/WixToolset.WixBA/Styles.xaml | 194 ------ src/WixToolset.WixBA/UpdateViewModel.cs | 207 ------- src/WixToolset.WixBA/WindowProperties.cs | 65 -- src/WixToolset.WixBA/WixBA.BootstrapperCore.config | 16 - src/WixToolset.WixBA/WixBA.cs | 229 ------- src/WixToolset.WixBA/WixBAFactory.cs | 17 - src/WixToolset.WixBA/WixDistribution.cs | 118 ---- src/WixToolset.WixBA/WixToolset.WixBA.csproj | 46 -- .../burn/WixToolset.WixBA/BrowserProperties.cs | 40 ++ src/test/burn/WixToolset.WixBA/Hresult.cs | 22 + .../burn/WixToolset.WixBA/InstallationViewModel.cs | 683 +++++++++++++++++++++ src/test/burn/WixToolset.WixBA/Model.cs | 132 ++++ src/test/burn/WixToolset.WixBA/NewsItem.cs | 18 + .../burn/WixToolset.WixBA/ProgressViewModel.cs | 245 ++++++++ .../burn/WixToolset.WixBA/PropertyNotifyBase.cs | 59 ++ src/test/burn/WixToolset.WixBA/RelayCommand.cs | 45 ++ .../Resources/logo-black-hollow.png | Bin 0 -> 47472 bytes .../Resources/logo-white-hollow.png | Bin 0 -> 60557 bytes src/test/burn/WixToolset.WixBA/RootView.xaml | 420 +++++++++++++ src/test/burn/WixToolset.WixBA/RootView.xaml.cs | 51 ++ src/test/burn/WixToolset.WixBA/RootViewModel.cs | 202 ++++++ src/test/burn/WixToolset.WixBA/Styles.xaml | 194 ++++++ src/test/burn/WixToolset.WixBA/UpdateViewModel.cs | 207 +++++++ src/test/burn/WixToolset.WixBA/WindowProperties.cs | 65 ++ .../WixToolset.WixBA/WixBA.BootstrapperCore.config | 16 + src/test/burn/WixToolset.WixBA/WixBA.cs | 229 +++++++ src/test/burn/WixToolset.WixBA/WixBAFactory.cs | 17 + src/test/burn/WixToolset.WixBA/WixDistribution.cs | 118 ++++ .../burn/WixToolset.WixBA/WixToolset.WixBA.csproj | 50 ++ 42 files changed, 2813 insertions(+), 2809 deletions(-) delete mode 100644 src/WixToolset.WixBA/BrowserProperties.cs delete mode 100644 src/WixToolset.WixBA/Hresult.cs delete mode 100644 src/WixToolset.WixBA/InstallationViewModel.cs delete mode 100644 src/WixToolset.WixBA/Model.cs delete mode 100644 src/WixToolset.WixBA/NewsItem.cs delete mode 100644 src/WixToolset.WixBA/ProgressViewModel.cs delete mode 100644 src/WixToolset.WixBA/PropertyNotifyBase.cs delete mode 100644 src/WixToolset.WixBA/RelayCommand.cs delete mode 100644 src/WixToolset.WixBA/Resources/logo-black-hollow.png delete mode 100644 src/WixToolset.WixBA/Resources/logo-white-hollow.png delete mode 100644 src/WixToolset.WixBA/RootView.xaml delete mode 100644 src/WixToolset.WixBA/RootView.xaml.cs delete mode 100644 src/WixToolset.WixBA/RootViewModel.cs delete mode 100644 src/WixToolset.WixBA/Styles.xaml delete mode 100644 src/WixToolset.WixBA/UpdateViewModel.cs delete mode 100644 src/WixToolset.WixBA/WindowProperties.cs delete mode 100644 src/WixToolset.WixBA/WixBA.BootstrapperCore.config delete mode 100644 src/WixToolset.WixBA/WixBA.cs delete mode 100644 src/WixToolset.WixBA/WixBAFactory.cs delete mode 100644 src/WixToolset.WixBA/WixDistribution.cs delete mode 100644 src/WixToolset.WixBA/WixToolset.WixBA.csproj create mode 100644 src/test/burn/WixToolset.WixBA/BrowserProperties.cs create mode 100644 src/test/burn/WixToolset.WixBA/Hresult.cs create mode 100644 src/test/burn/WixToolset.WixBA/InstallationViewModel.cs create mode 100644 src/test/burn/WixToolset.WixBA/Model.cs create mode 100644 src/test/burn/WixToolset.WixBA/NewsItem.cs create mode 100644 src/test/burn/WixToolset.WixBA/ProgressViewModel.cs create mode 100644 src/test/burn/WixToolset.WixBA/PropertyNotifyBase.cs create mode 100644 src/test/burn/WixToolset.WixBA/RelayCommand.cs create mode 100644 src/test/burn/WixToolset.WixBA/Resources/logo-black-hollow.png create mode 100644 src/test/burn/WixToolset.WixBA/Resources/logo-white-hollow.png create mode 100644 src/test/burn/WixToolset.WixBA/RootView.xaml create mode 100644 src/test/burn/WixToolset.WixBA/RootView.xaml.cs create mode 100644 src/test/burn/WixToolset.WixBA/RootViewModel.cs create mode 100644 src/test/burn/WixToolset.WixBA/Styles.xaml create mode 100644 src/test/burn/WixToolset.WixBA/UpdateViewModel.cs create mode 100644 src/test/burn/WixToolset.WixBA/WindowProperties.cs create mode 100644 src/test/burn/WixToolset.WixBA/WixBA.BootstrapperCore.config create mode 100644 src/test/burn/WixToolset.WixBA/WixBA.cs create mode 100644 src/test/burn/WixToolset.WixBA/WixBAFactory.cs create mode 100644 src/test/burn/WixToolset.WixBA/WixDistribution.cs create mode 100644 src/test/burn/WixToolset.WixBA/WixToolset.WixBA.csproj diff --git a/src/WixToolset.WixBA/BrowserProperties.cs b/src/WixToolset.WixBA/BrowserProperties.cs deleted file mode 100644 index c8fb6177..00000000 --- a/src/WixToolset.WixBA/BrowserProperties.cs +++ /dev/null @@ -1,40 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System.Windows; - using System.Windows.Controls; - - /// - /// Dependency Properties to support using a WebBrowser object. - /// - class BrowserProperties - { - /// - /// Dependency Propery used to pass an HTML string to the webBrowser object. - /// - public static readonly DependencyProperty HtmlDocProperty = - DependencyProperty.RegisterAttached("HtmlDoc", typeof(string), typeof(BrowserProperties), new PropertyMetadata(OnHtmlDocChanged)); - - public static string GetHtmlDoc(DependencyObject dependencyObject) - { - return (string)dependencyObject.GetValue(HtmlDocProperty); - } - - public static void SetHtmlDoc(DependencyObject dependencyObject, string htmldoc) - { - dependencyObject.SetValue(HtmlDocProperty, htmldoc); - } - - /// - /// Event handler that passes the HtmlDoc Dependency Property to MavigateToString method. - /// - /// - /// - private static void OnHtmlDocChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var webBrowser = (WebBrowser)d; - webBrowser.NavigateToString((string)e.NewValue); - } - } -} diff --git a/src/WixToolset.WixBA/Hresult.cs b/src/WixToolset.WixBA/Hresult.cs deleted file mode 100644 index a5e552ac..00000000 --- a/src/WixToolset.WixBA/Hresult.cs +++ /dev/null @@ -1,22 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - - /// - /// Utility class to work with HRESULTs - /// - internal class Hresult - { - /// - /// Determines if an HRESULT was a success code or not. - /// - /// HRESULT to verify. - /// True if the status is a success code. - public static bool Succeeded(int status) - { - return status >= 0; - } - } -} 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 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; - using System.Reflection; - using System.Windows; - using System.Windows.Input; - using IO = System.IO; - using WixToolset.Mba.Core; - - /// - /// The states of detection. - /// - public enum DetectionState - { - Absent, - Present, - Newer, - } - - /// - /// The states of installation. - /// - public enum InstallationState - { - Initializing, - Detecting, - Waiting, - Planning, - Applying, - Applied, - Failed, - } - - /// - /// The model of the installation view in WixBA. - /// - public class InstallationViewModel : PropertyNotifyBase - { - private RootViewModel root; - - private Dictionary downloadRetries; - private bool downgrade; - private string downgradeMessage; - - private ICommand licenseCommand; - private ICommand launchHomePageCommand; - private ICommand launchNewsCommand; - private ICommand launchVSExtensionPageCommand; - private ICommand installCommand; - private ICommand repairCommand; - private ICommand uninstallCommand; - private ICommand openLogCommand; - private ICommand openLogFolderCommand; - private ICommand tryAgainCommand; - - private string message; - private DateTime cachePackageStart; - private DateTime executePackageStart; - - /// - /// Creates a new model of the installation view. - /// - public InstallationViewModel(RootViewModel root) - { - this.root = root; - this.downloadRetries = new Dictionary(); - - this.root.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.RootPropertyChanged); - - WixBA.Model.Bootstrapper.DetectBegin += this.DetectBegin; - WixBA.Model.Bootstrapper.DetectRelatedBundle += this.DetectedRelatedBundle; - WixBA.Model.Bootstrapper.DetectComplete += this.DetectComplete; - WixBA.Model.Bootstrapper.PlanPackageBegin += this.PlanPackageBegin; - WixBA.Model.Bootstrapper.PlanComplete += this.PlanComplete; - WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; - WixBA.Model.Bootstrapper.CacheAcquireBegin += this.CacheAcquireBegin; - WixBA.Model.Bootstrapper.CacheAcquireResolving += this.CacheAcquireResolving; - WixBA.Model.Bootstrapper.CacheAcquireComplete += this.CacheAcquireComplete; - WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; - WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; - WixBA.Model.Bootstrapper.Error += this.ExecuteError; - WixBA.Model.Bootstrapper.ApplyComplete += this.ApplyComplete; - } - - void RootPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (("DetectState" == e.PropertyName) || ("InstallState" == e.PropertyName)) - { - base.OnPropertyChanged("RepairEnabled"); - base.OnPropertyChanged("InstallEnabled"); - base.OnPropertyChanged("IsComplete"); - base.OnPropertyChanged("IsSuccessfulCompletion"); - base.OnPropertyChanged("IsFailedCompletion"); - base.OnPropertyChanged("StatusText"); - base.OnPropertyChanged("UninstallEnabled"); - } - } - - /// - /// Gets the version for the application. - /// - public string Version - { - get { return String.Concat("v", WixBA.Model.Version.ToString()); } - } - - /// - /// The Publisher of this bundle. - /// - public string Publisher - { - get - { - string company = "[AssemblyCompany]"; - return WixDistribution.ReplacePlaceholders(company, typeof(WixBA).Assembly); - } - } - - /// - /// The Publisher of this bundle. - /// - public string SupportUrl - { - get - { - return WixDistribution.SupportUrl; - } - } - public string VSExtensionUrl - { - get - { - return WixDistribution.VSExtensionsLandingUrl; - } - } - - public string Message - { - get - { - return this.message; - } - - set - { - if (this.message != value) - { - this.message = value; - base.OnPropertyChanged("Message"); - } - } - } - - /// - /// Gets and sets whether the view model considers this install to be a downgrade. - /// - public bool Downgrade - { - get - { - return this.downgrade; - } - - set - { - if (this.downgrade != value) - { - this.downgrade = value; - base.OnPropertyChanged("Downgrade"); - } - } - } - - public string DowngradeMessage - { - get - { - return this.downgradeMessage; - } - set - { - if (this.downgradeMessage != value) - { - this.downgradeMessage = value; - base.OnPropertyChanged("DowngradeMessage"); - } - } - } - - public ICommand LaunchHomePageCommand - { - get - { - if (this.launchHomePageCommand == null) - { - this.launchHomePageCommand = new RelayCommand(param => WixBA.LaunchUrl(this.SupportUrl), param => true); - } - - return this.launchHomePageCommand; - } - } - - public ICommand LaunchNewsCommand - { - get - { - if (this.launchNewsCommand == null) - { - this.launchNewsCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.NewsUrl), param => true); - } - - return this.launchNewsCommand; - } - } - - public ICommand LaunchVSExtensionPageCommand - { - get - { - if (this.launchVSExtensionPageCommand == null) - { - this.launchVSExtensionPageCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.VSExtensionsLandingUrl), param => true); - } - - return this.launchVSExtensionPageCommand; - } - } - - public ICommand LicenseCommand - { - get - { - if (this.licenseCommand == null) - { - this.licenseCommand = new RelayCommand(param => this.LaunchLicense(), param => true); - } - - return this.licenseCommand; - } - } - - public bool LicenseEnabled - { - get { return this.LicenseCommand.CanExecute(this); } - } - - public ICommand CloseCommand - { - get { return this.root.CloseCommand; } - } - - public bool IsComplete - { - get { return IsSuccessfulCompletion || IsFailedCompletion; } - } - - public bool IsSuccessfulCompletion - { - get { return InstallationState.Applied == this.root.InstallState; } - } - - public bool IsFailedCompletion - { - get { return InstallationState.Failed == this.root.InstallState; } - } - - public ICommand InstallCommand - { - get - { - if (this.installCommand == null) - { - this.installCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Install), param => this.root.DetectState == DetectionState.Absent && this.root.InstallState == InstallationState.Waiting); - } - - return this.installCommand; - } - } - - public bool InstallEnabled - { - get { return this.InstallCommand.CanExecute(this); } - } - - public ICommand RepairCommand - { - get - { - if (this.repairCommand == null) - { - this.repairCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Repair), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); - } - - return this.repairCommand; - } - } - - public bool RepairEnabled - { - get { return this.RepairCommand.CanExecute(this); } - } - - public ICommand UninstallCommand - { - get - { - if (this.uninstallCommand == null) - { - this.uninstallCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Uninstall), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); - } - - return this.uninstallCommand; - } - } - - public bool UninstallEnabled - { - get { return this.UninstallCommand.CanExecute(this); } - } - - public ICommand OpenLogCommand - { - get - { - if (this.openLogCommand == null) - { - this.openLogCommand = new RelayCommand(param => WixBA.OpenLog(new Uri(WixBA.Model.Engine.GetVariableString("WixBundleLog")))); - } - return this.openLogCommand; - } - } - - public ICommand OpenLogFolderCommand - { - get - { - if (this.openLogFolderCommand == null) - { - string logFolder = IO.Path.GetDirectoryName(WixBA.Model.Engine.GetVariableString("WixBundleLog")); - this.openLogFolderCommand = new RelayCommand(param => WixBA.OpenLogFolder(logFolder)); - } - return this.openLogFolderCommand; - } - } - - public ICommand TryAgainCommand - { - get - { - if (this.tryAgainCommand == null) - { - this.tryAgainCommand = new RelayCommand(param => - { - this.root.Canceled = false; - WixBA.Plan(WixBA.Model.PlannedAction); - }, param => IsFailedCompletion); - } - - return this.tryAgainCommand; - } - } - - public string StatusText - { - get - { - switch(this.root.InstallState) - { - case InstallationState.Applied: - return "Complete"; - case InstallationState.Failed: - return this.root.Canceled ? "Cancelled" : "Failed"; - default: - return "Unknown"; // this shouldn't be shown in the UI. - } - } - } - - /// - /// Launches the license in the default viewer. - /// - private void LaunchLicense() - { - string folder = IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - WixBA.LaunchUrl(IO.Path.Combine(folder, "License.txt")); - } - - private void DetectBegin(object sender, DetectBeginEventArgs e) - { - this.root.DetectState = e.Installed ? DetectionState.Present : DetectionState.Absent; - WixBA.Model.PlannedAction = LaunchAction.Unknown; - } - - private void DetectedRelatedBundle(object sender, DetectRelatedBundleEventArgs e) - { - if (e.Operation == RelatedOperation.Downgrade) - { - this.Downgrade = true; - } - - if (!WixBA.Model.BAManifest.Bundle.Packages.ContainsKey(e.ProductCode)) - { - WixBA.Model.BAManifest.Bundle.AddRelatedBundleAsPackage(e); - } - } - - private void DetectComplete(object sender, DetectCompleteEventArgs e) - { - // Parse the command line string before any planning. - this.ParseCommandLine(); - this.root.InstallState = InstallationState.Waiting; - - if (LaunchAction.Uninstall == WixBA.Model.Command.Action && - ResumeType.Arp != WixBA.Model.Command.Resume) // MSI and WixStdBA require some kind of confirmation before proceeding so WixBA should, too. - { - WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for uninstall"); - WixBA.Plan(LaunchAction.Uninstall); - } - else if (Hresult.Succeeded(e.Status)) - { - if (this.Downgrade) - { - this.root.DetectState = DetectionState.Newer; - var relatedPackages = WixBA.Model.BAManifest.Bundle.Packages.Values.Where(p => p.Type == PackageType.UpgradeBundle); - var installedVersion = relatedPackages.Any() ? new Version(relatedPackages.Max(p => p.Version)) : null; - if (installedVersion != null && installedVersion < new Version(4, 1) && installedVersion.Build > 10) - { - this.DowngradeMessage = "You must uninstall WiX v" + installedVersion + " before you can install this."; - } - else - { - this.DowngradeMessage = "There is already a newer version of WiX installed on this machine."; - } - } - - if (LaunchAction.Layout == WixBA.Model.Command.Action) - { - WixBA.PlanLayout(); - } - else if (WixBA.Model.Command.Display != Display.Full) - { - // If we're not waiting for the user to click install, dispatch plan with the default action. - WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for non-interactive mode."); - WixBA.Plan(WixBA.Model.Command.Action); - } - } - else - { - this.root.InstallState = InstallationState.Failed; - } - - // Force all commands to reevaluate CanExecute. - // InvalidateRequerySuggested must be run on the UI thread. - root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); - } - - private void PlanPackageBegin(object sender, PlanPackageBeginEventArgs e) - { - // 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. - if ( e.PackageId.StartsWith("NetFx4", StringComparison.OrdinalIgnoreCase)) - { - e.State = RequestState.None; - } - } - - private void PlanComplete(object sender, PlanCompleteEventArgs e) - { - if (Hresult.Succeeded(e.Status)) - { - this.root.PreApplyState = this.root.InstallState; - this.root.InstallState = InstallationState.Applying; - WixBA.Model.Engine.Apply(this.root.ViewWindowHandle); - } - else - { - this.root.InstallState = InstallationState.Failed; - } - } - - private void ApplyBegin(object sender, ApplyBeginEventArgs e) - { - this.downloadRetries.Clear(); - } - - private void CacheAcquireBegin(object sender, CacheAcquireBeginEventArgs e) - { - this.cachePackageStart = DateTime.Now; - } - - private void CacheAcquireResolving(object sender, CacheAcquireResolvingEventArgs e) - { - if (e.Action == CacheResolveOperation.Download && !this.downloadRetries.ContainsKey(e.PackageOrContainerId)) - { - this.downloadRetries.Add(e.PackageOrContainerId, 0); - } - } - - private void CacheAcquireComplete(object sender, CacheAcquireCompleteEventArgs e) - { - this.AddPackageTelemetry("Cache", e.PackageOrContainerId ?? String.Empty, DateTime.Now.Subtract(this.cachePackageStart).TotalMilliseconds, e.Status); - - if (e.Status < 0 && this.downloadRetries.TryGetValue(e.PackageOrContainerId, out var retries) && retries < 3) - { - this.downloadRetries[e.PackageOrContainerId] = retries + 1; - switch (e.Status) - { - case -2147023294: //HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) - case -2147024894: //HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) - case -2147012889: //HRESULT_FROM_WIN32(ERROR_INTERNET_NAME_NOT_RESOLVED) - break; - default: - e.Action = BOOTSTRAPPER_CACHEACQUIRECOMPLETE_ACTION.Retry; - break; - } - } - } - - private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) - { - lock (this) - { - this.executePackageStart = e.ShouldExecute ? DateTime.Now : DateTime.MinValue; - } - } - - private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) - { - lock (this) - { - if (DateTime.MinValue < this.executePackageStart) - { - this.AddPackageTelemetry("Execute", e.PackageId ?? String.Empty, DateTime.Now.Subtract(this.executePackageStart).TotalMilliseconds, e.Status); - this.executePackageStart = DateTime.MinValue; - } - } - } - - private void ExecuteError(object sender, ErrorEventArgs e) - { - lock (this) - { - if (!this.root.Canceled) - { - // If the error is a cancel coming from the engine during apply we want to go back to the preapply state. - if (InstallationState.Applying == this.root.InstallState && (int)Error.UserCancelled == e.ErrorCode) - { - this.root.InstallState = this.root.PreApplyState; - } - else - { - this.Message = e.ErrorMessage; - - if (Display.Full == WixBA.Model.Command.Display) - { - // On HTTP authentication errors, have the engine try to do authentication for us. - if (ErrorType.HttpServerAuthentication == e.ErrorType || ErrorType.HttpProxyAuthentication == e.ErrorType) - { - e.Result = Result.TryAgain; - } - else // show an error dialog. - { - MessageBoxButton msgbox = MessageBoxButton.OK; - switch (e.UIHint & 0xF) - { - case 0: - msgbox = MessageBoxButton.OK; - break; - case 1: - msgbox = MessageBoxButton.OKCancel; - break; - // There is no 2! That would have been MB_ABORTRETRYIGNORE. - case 3: - msgbox = MessageBoxButton.YesNoCancel; - break; - case 4: - msgbox = MessageBoxButton.YesNo; - break; - // default: stay with MBOK since an exact match is not available. - } - - MessageBoxResult result = MessageBoxResult.None; - WixBA.View.Dispatcher.Invoke((Action)delegate() - { - result = MessageBox.Show(WixBA.View, e.ErrorMessage, "WiX Toolset", msgbox, MessageBoxImage.Error); - } - ); - - // If there was a match from the UI hint to the msgbox value, use the result from the - // message box. Otherwise, we'll ignore it and return the default to Burn. - if ((e.UIHint & 0xF) == (int)msgbox) - { - e.Result = (Result)result; - } - } - } - } - } - else // canceled, so always return cancel. - { - e.Result = Result.Cancel; - } - } - } - - private void ApplyComplete(object sender, ApplyCompleteEventArgs e) - { - WixBA.Model.Result = e.Status; // remember the final result of the apply. - - // Set the state to applied or failed unless the state has already been set back to the preapply state - // which means we need to show the UI as it was before the apply started. - if (this.root.InstallState != this.root.PreApplyState) - { - this.root.InstallState = Hresult.Succeeded(e.Status) ? InstallationState.Applied : InstallationState.Failed; - } - - // If we're not in Full UI mode, we need to alert the dispatcher to stop and close the window for passive. - if (Display.Full != WixBA.Model.Command.Display) - { - // If its passive, send a message to the window to close. - if (Display.Passive == WixBA.Model.Command.Display) - { - WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window for non-interactive install"); - WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); - } - else - { - WixBA.Dispatcher.InvokeShutdown(); - } - return; - } - 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. - { - WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window since update successful."); - WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); - return; - } - else if (root.AutoClose) - { - // Automatically closing since the user clicked the X button. - WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); - return; - } - - // Force all commands to reevaluate CanExecute. - // InvalidateRequerySuggested must be run on the UI thread. - root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); - } - - private void ParseCommandLine() - { - // Get array of arguments based on the system parsing algorithm. - string[] args = WixBA.Model.Command.CommandLineArgs; - for (int i = 0; i < args.Length; ++i) - { - if (args[i].StartsWith("InstallFolder=", StringComparison.InvariantCultureIgnoreCase)) - { - // Allow relative directory paths. Also validates. - string[] param = args[i].Split(new char[] {'='}, 2); - this.root.InstallDirectory = IO.Path.Combine(Environment.CurrentDirectory, param[1]); - } - } - } - - private void AddPackageTelemetry(string prefix, string id, double time, int result) - { - lock (this) - { - string key = String.Format("{0}Time_{1}", prefix, id); - string value = time.ToString(); - WixBA.Model.Telemetry.Add(new KeyValuePair(key, value)); - - key = String.Format("{0}Result_{1}", prefix, id); - value = String.Concat("0x", result.ToString("x")); - WixBA.Model.Telemetry.Add(new KeyValuePair(key, value)); - } - } - } -} diff --git a/src/WixToolset.WixBA/Model.cs b/src/WixToolset.WixBA/Model.cs deleted file mode 100644 index a557fa4e..00000000 --- a/src/WixToolset.WixBA/Model.cs +++ /dev/null @@ -1,132 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.Collections.Generic; - using System.Net; - using WixToolset.Mba.Core; - - /// - /// The model. - /// - public class Model - { - private const string BurnBundleInstallDirectoryVariable = "InstallFolder"; - private const string BurnBundleLayoutDirectoryVariable = "WixBundleLayoutDirectory"; - private const string BurnBundleVersionVariable = "WixBundleVersion"; - - /// - /// Creates a new model for the BA. - /// - /// The BA. - public Model(WixBA bootstrapper) - { - this.BAManifest = bootstrapper.BAManifest; - this.Bootstrapper = bootstrapper; - this.Command = bootstrapper.Command; - this.Engine = bootstrapper.Engine; - this.Telemetry = new List>(); - this.Version = this.Engine.GetVariableVersion(BurnBundleVersionVariable); - } - - public IBootstrapperApplicationData BAManifest { get; } - - /// - /// Gets the bootstrapper. - /// - public IDefaultBootstrapperApplication Bootstrapper { get; } - - /// - /// Gets the bootstrapper command-line. - /// - public IBootstrapperCommand Command { get; } - - /// - /// Gets the bootstrapper engine. - /// - public IEngine Engine { get; } - - /// - /// Gets the key/value pairs used in telemetry. - /// - public List> Telemetry { get; private set; } - - /// - /// Get or set the final result of the installation. - /// - public int Result { get; set; } - - /// - /// Get the version of the install. - /// - public string Version { get; private set; } - - /// - /// Get or set the path where the bundle is installed. - /// - public string InstallDirectory - { - get - { - if (!this.Engine.ContainsVariable(BurnBundleInstallDirectoryVariable)) - { - return null; - } - - return this.Engine.GetVariableString(BurnBundleInstallDirectoryVariable); - } - - set - { - this.Engine.SetVariableString(BurnBundleInstallDirectoryVariable, value, false); - } - } - - /// - /// Get or set the path for the layout to be created. - /// - public string LayoutDirectory - { - get - { - if (!this.Engine.ContainsVariable(BurnBundleLayoutDirectoryVariable)) - { - return null; - } - - return this.Engine.GetVariableString(BurnBundleLayoutDirectoryVariable); - } - - set - { - this.Engine.SetVariableString(BurnBundleLayoutDirectoryVariable, value, false); - } - } - - public LaunchAction PlannedAction { get; set; } - - /// - /// Creates a correctly configured HTTP web request. - /// - /// URI to connect to. - /// Correctly configured HTTP web request. - public HttpWebRequest CreateWebRequest(string uri) - { - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); - request.UserAgent = String.Concat("WixInstall", this.Version.ToString()); - - return request; - } - - /// - /// Gets the display name for a package if possible. - /// - /// Identity of the package to find the display name. - /// Display name of the package if found or the package id if not. - public string GetPackageName(string packageId) - { - return this.BAManifest.Bundle.Packages.TryGetValue(packageId, out var package) ? package.DisplayName : packageId; - } - } -} diff --git a/src/WixToolset.WixBA/NewsItem.cs b/src/WixToolset.WixBA/NewsItem.cs deleted file mode 100644 index f8bf7aed..00000000 --- a/src/WixToolset.WixBA/NewsItem.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - - /// - /// The model for an individual news item. - /// - public class NewsItem - { - public string Author { get; set; } - public string Title { get; set; } - public string Url { get; set; } - public string Snippet { get; set; } - public DateTime Updated { get; set; } - } -} diff --git a/src/WixToolset.WixBA/ProgressViewModel.cs b/src/WixToolset.WixBA/ProgressViewModel.cs deleted file mode 100644 index 6f7bb028..00000000 --- a/src/WixToolset.WixBA/ProgressViewModel.cs +++ /dev/null @@ -1,245 +0,0 @@ -// 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. - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Text.RegularExpressions; -using WixToolset.Mba.Core; - -namespace WixToolset.WixBA -{ - public class ProgressViewModel : PropertyNotifyBase - { - private static readonly Regex TrimActionTimeFromMessage = new Regex(@"^\w+\s+\d+:\d+:\d+:\s+", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.Singleline); - - private RootViewModel root; - private Dictionary executingPackageOrderIndex; - - private int progressPhases; - private int progress; - private int cacheProgress; - private int executeProgress; - private string package; - private string message; - - public ProgressViewModel(RootViewModel root) - { - this.root = root; - this.executingPackageOrderIndex = new Dictionary(); - - this.root.PropertyChanged += this.RootPropertyChanged; - - WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; - WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; - WixBA.Model.Bootstrapper.ExecuteProgress += this.ApplyExecuteProgress; - WixBA.Model.Bootstrapper.PauseAutomaticUpdatesBegin += this.PauseAutomaticUpdatesBegin; - WixBA.Model.Bootstrapper.SystemRestorePointBegin += this.SystemRestorePointBegin; - WixBA.Model.Bootstrapper.PlanBegin += this.PlanBegin; - WixBA.Model.Bootstrapper.PlannedPackage += this.PlannedPackage; - WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; - WixBA.Model.Bootstrapper.Progress += this.ApplyProgress; - WixBA.Model.Bootstrapper.CacheAcquireProgress += this.CacheAcquireProgress; - WixBA.Model.Bootstrapper.CacheContainerOrPayloadVerifyProgress += CacheContainerOrPayloadVerifyProgress; - WixBA.Model.Bootstrapper.CachePayloadExtractProgress += CachePayloadExtractProgress; - WixBA.Model.Bootstrapper.CacheVerifyProgress += CacheVerifyProgress; - WixBA.Model.Bootstrapper.CacheComplete += this.CacheComplete; - } - - public bool ProgressEnabled - { - get { return this.root.InstallState == InstallationState.Applying; } - } - - public int Progress - { - get - { - return this.progress; - } - - set - { - if (this.progress != value) - { - this.progress = value; - base.OnPropertyChanged("Progress"); - } - } - } - - public string Package - { - get - { - return this.package; - } - - set - { - if (this.package != value) - { - this.package = value; - base.OnPropertyChanged("Package"); - } - } - } - - public string Message - { - get - { - return this.message; - } - - set - { - if (this.message != value) - { - this.message = value; - base.OnPropertyChanged("Message"); - } - } - } - - void RootPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if ("InstallState" == e.PropertyName) - { - base.OnPropertyChanged("ProgressEnabled"); - } - } - - private void PlanBegin(object sender, PlanBeginEventArgs e) - { - lock (this) - { - this.executingPackageOrderIndex.Clear(); - } - } - - private void PlannedPackage(object sender, PlannedPackageEventArgs e) - { - if (ActionState.None != e.Execute) - { - lock (this) - { - Debug.Assert(!this.executingPackageOrderIndex.ContainsKey(e.PackageId)); - this.executingPackageOrderIndex.Add(e.PackageId, this.executingPackageOrderIndex.Count); - } - } - } - - private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) - { - lock (this) - { - this.Package = WixBA.Model.GetPackageName(e.PackageId); - this.Message = String.Format("Processing: {0}", this.Package); - e.Cancel = this.root.Canceled; - } - } - - private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) - { - lock (this) - { // avoid a stale display - this.Message = String.Empty; - } - } - - private void PauseAutomaticUpdatesBegin(object sender, PauseAutomaticUpdatesBeginEventArgs e) - { - lock (this) - { - this.Message = "Pausing Windows automatic updates"; - } - } - - private void SystemRestorePointBegin(object sender, SystemRestorePointBeginEventArgs e) - { - lock (this) - { - this.Message = "Creating system restore point"; - } - } - - private void ApplyBegin(object sender, ApplyBeginEventArgs e) - { - this.progressPhases = e.PhaseCount; - } - - private void ApplyProgress(object sender, ProgressEventArgs e) - { - lock (this) - { - e.Cancel = this.root.Canceled; - } - } - - private void CacheAcquireProgress(object sender, CacheAcquireProgressEventArgs e) - { - lock (this) - { - this.cacheProgress = e.OverallPercentage; - this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; - e.Cancel = this.root.Canceled; - } - } - - private void CacheContainerOrPayloadVerifyProgress(object sender, CacheContainerOrPayloadVerifyProgressEventArgs e) - { - lock (this) - { - this.cacheProgress = e.OverallPercentage; - this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; - e.Cancel = this.root.Canceled; - } - } - - private void CachePayloadExtractProgress(object sender, CachePayloadExtractProgressEventArgs e) - { - lock (this) - { - this.cacheProgress = e.OverallPercentage; - this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; - e.Cancel = this.root.Canceled; - } - } - - private void CacheVerifyProgress(object sender, CacheVerifyProgressEventArgs e) - { - lock (this) - { - this.cacheProgress = e.OverallPercentage; - this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; - e.Cancel = this.root.Canceled; - } - } - - private void CacheComplete(object sender, CacheCompleteEventArgs e) - { - lock (this) - { - this.cacheProgress = 100; - this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; - } - } - - private void ApplyExecuteProgress(object sender, ExecuteProgressEventArgs e) - { - lock (this) - { - this.executeProgress = e.OverallPercentage; - this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; - - if (WixBA.Model.Command.Display == Display.Embedded) - { - WixBA.Model.Engine.SendEmbeddedProgress(e.ProgressPercentage, this.Progress); - } - - e.Cancel = this.root.Canceled; - } - } - } -} diff --git a/src/WixToolset.WixBA/PropertyNotifyBase.cs b/src/WixToolset.WixBA/PropertyNotifyBase.cs deleted file mode 100644 index f8264614..00000000 --- a/src/WixToolset.WixBA/PropertyNotifyBase.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.ComponentModel; - using System.Diagnostics; - - /// - /// It provides support for property change notifications. - /// - public abstract class PropertyNotifyBase : INotifyPropertyChanged - { - /// - /// Initializes a new instance of the class. - /// - protected PropertyNotifyBase() - { - } - - /// - /// Raised when a property on this object has a new value. - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Warns the developer if this object does not have a public property with the - /// specified name. This method does not exist in a Release build. - /// - /// Property name to verify. - [Conditional("DEBUG")] - [DebuggerStepThrough] - public void VerifyPropertyName(string propertyName) - { - // Verify that the property name matches a real, public, instance property - // on this object. - if (null == TypeDescriptor.GetProperties(this)[propertyName]) - { - Debug.Fail(String.Concat("Invalid property name: ", propertyName)); - } - } - - /// - /// Raises this object's PropertyChanged event. - /// - /// The property that has a new value. - protected virtual void OnPropertyChanged(string propertyName) - { - this.VerifyPropertyName(propertyName); - - PropertyChangedEventHandler handler = this.PropertyChanged; - if (null != handler) - { - PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); - handler(this, e); - } - } - } -} diff --git a/src/WixToolset.WixBA/RelayCommand.cs b/src/WixToolset.WixBA/RelayCommand.cs deleted file mode 100644 index d3ab2d7a..00000000 --- a/src/WixToolset.WixBA/RelayCommand.cs +++ /dev/null @@ -1,45 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.Diagnostics; - using System.Windows.Input; - - /// - /// Base class that implements ICommand interface via delegates. - /// - public class RelayCommand : ICommand - { - private readonly Action execute; - private readonly Predicate canExecute; - - public RelayCommand(Action execute) - : this(execute, null) - { - } - - public RelayCommand(Action execute, Predicate canExecute) - { - this.execute = execute; - this.canExecute = canExecute; - } - - public event EventHandler CanExecuteChanged - { - add { CommandManager.RequerySuggested += value; } - remove { CommandManager.RequerySuggested -= value; } - } - - [DebuggerStepThrough] - public bool CanExecute(object parameter) - { - return this.canExecute == null ? true : this.canExecute(parameter); - } - - public void Execute(object parameter) - { - this.execute(parameter); - } - } -} diff --git a/src/WixToolset.WixBA/Resources/logo-black-hollow.png b/src/WixToolset.WixBA/Resources/logo-black-hollow.png deleted file mode 100644 index 9d0290bd..00000000 Binary files a/src/WixToolset.WixBA/Resources/logo-black-hollow.png and /dev/null differ diff --git a/src/WixToolset.WixBA/Resources/logo-white-hollow.png b/src/WixToolset.WixBA/Resources/logo-white-hollow.png deleted file mode 100644 index 242c7350..00000000 Binary files a/src/WixToolset.WixBA/Resources/logo-white-hollow.png and /dev/null differ diff --git a/src/WixToolset.WixBA/RootView.xaml b/src/WixToolset.WixBA/RootView.xaml deleted file mode 100644 index b7d535d1..00000000 --- a/src/WixToolset.WixBA/RootView.xaml +++ /dev/null @@ -1,420 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.WixBA/RootView.xaml.cs b/src/WixToolset.WixBA/RootView.xaml.cs deleted file mode 100644 index e1ee19db..00000000 --- a/src/WixToolset.WixBA/RootView.xaml.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System.ComponentModel; - using System.Windows; - using System.Windows.Interop; - - /// - /// Interaction logic for View.xaml - /// - public partial class RootView : Window - { - /// - /// Creates the view populated with it's model. - /// - /// Model for the view. - public RootView(RootViewModel viewModel) - { - this.DataContext = viewModel; - - this.Loaded += (sender, e) => WixBA.Model.Engine.CloseSplashScreen(); - this.Closed += (sender, e) => this.Dispatcher.InvokeShutdown(); // shutdown dispatcher when the window is closed. - - this.InitializeComponent(); - - viewModel.Dispatcher = this.Dispatcher; - viewModel.ViewWindowHandle = new WindowInteropHelper(this).EnsureHandle(); - } - - /// - /// Event is fired when the window is closing. - /// - /// - /// - private void Window_Closing(object sender, CancelEventArgs e) - { - RootViewModel rvm = this.DataContext as RootViewModel; - if ((null != rvm) && (InstallationState.Applying == rvm.InstallState)) - { - rvm.CancelButton_Click(); - if (rvm.Canceled) - { - // Defer closing until the engine has canceled processing. - e.Cancel = true; - rvm.AutoClose = true; - } - } - } - } -} diff --git a/src/WixToolset.WixBA/RootViewModel.cs b/src/WixToolset.WixBA/RootViewModel.cs deleted file mode 100644 index 8cff7274..00000000 --- a/src/WixToolset.WixBA/RootViewModel.cs +++ /dev/null @@ -1,202 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.Windows; - using System.Windows.Input; - using System.Windows.Threading; - using WixToolset.Mba.Core; - - /// - /// The errors returned from the engine - /// - public enum Error - { - UserCancelled = 1223, - } - - /// - /// The model of the root view in WixBA. - /// - public class RootViewModel : PropertyNotifyBase - { - private ICommand cancelCommand; - private ICommand closeCommand; - - private bool canceled; - private InstallationState installState; - private DetectionState detectState; - - /// - /// Creates a new model of the root view. - /// - public RootViewModel() - { - this.InstallationViewModel = new InstallationViewModel(this); - this.ProgressViewModel = new ProgressViewModel(this); - this.UpdateViewModel = new UpdateViewModel(this); - } - - public InstallationViewModel InstallationViewModel { get; private set; } - public ProgressViewModel ProgressViewModel { get; private set; } - public UpdateViewModel UpdateViewModel { get; private set; } - public Dispatcher Dispatcher { get; set; } - public IntPtr ViewWindowHandle { get; set; } - public bool AutoClose { get; set; } - - public ICommand CloseCommand - { - get - { - if (this.closeCommand == null) - { - this.closeCommand = new RelayCommand(param => WixBA.View.Close()); - } - - return this.closeCommand; - } - } - - public ICommand CancelCommand - { - get - { - if (this.cancelCommand == null) - { - this.cancelCommand = new RelayCommand(param => - { - this.CancelButton_Click(); - }, - param => !this.Canceled); - } - - return this.cancelCommand; - } - } - - public bool CancelAvailable - { - get { return InstallationState.Applying == this.InstallState; } - } - - public bool Canceled - { - get - { - return this.canceled; - } - - set - { - if (this.canceled != value) - { - this.canceled = value; - base.OnPropertyChanged("Canceled"); - } - } - } - - /// - /// Gets and sets the detect state of the view's model. - /// - public DetectionState DetectState - { - get - { - return this.detectState; - } - - set - { - if (this.detectState != value) - { - this.detectState = value; - - // Notify all the properties derived from the state that the state changed. - base.OnPropertyChanged("DetectState"); - } - } - } - - /// - /// Gets and sets the installation state of the view's model. - /// - public InstallationState InstallState - { - get - { - return this.installState; - } - - set - { - if (this.installState != value) - { - this.installState = value; - - // Notify all the properties derived from the state that the state changed. - base.OnPropertyChanged("InstallState"); - base.OnPropertyChanged("CancelAvailable"); - } - } - } - - /// - /// Gets and sets the state of the view's model before apply begins in order to return to that state if cancel or rollback occurs. - /// - public InstallationState PreApplyState { get; set; } - - /// - /// Gets and sets the path where the bundle is currently installed or will be installed. - /// - public string InstallDirectory - { - get - { - return WixBA.Model.InstallDirectory; - } - - set - { - if (WixBA.Model.InstallDirectory != value) - { - WixBA.Model.InstallDirectory = value; - base.OnPropertyChanged("InstallDirectory"); - } - } - } - - /// - /// The Title of this bundle. - /// - public string Title - { - get - { - return WixDistribution.ShortProduct; - } - } - - /// - /// Prompts the user to make sure they want to cancel. - /// This needs to run on the UI thread, use Dispatcher.Invoke to call this from a background thread. - /// - public void CancelButton_Click() - { - if (this.Canceled) - { - return; - } - - if (Display.Full == WixBA.Model.Command.Display) - { - this.Canceled = (MessageBoxResult.Yes == MessageBox.Show(WixBA.View, "Are you sure you want to cancel?", "WiX Toolset", MessageBoxButton.YesNo, MessageBoxImage.Error)); - } - else - { - this.Canceled = true; - } - } - } -} diff --git a/src/WixToolset.WixBA/Styles.xaml b/src/WixToolset.WixBA/Styles.xaml deleted file mode 100644 index fa0afc4f..00000000 --- a/src/WixToolset.WixBA/Styles.xaml +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - Segoe UI, Arial - 45 - 12 - 14 - - - - - - - #FF1EF1E8 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/WixToolset.WixBA/UpdateViewModel.cs b/src/WixToolset.WixBA/UpdateViewModel.cs deleted file mode 100644 index 80d894cb..00000000 --- a/src/WixToolset.WixBA/UpdateViewModel.cs +++ /dev/null @@ -1,207 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.ComponentModel; - using System.Windows.Input; - using WixToolset.Mba.Core; - - /// - /// The states of the update view model. - /// - public enum UpdateState - { - Unknown, - Initializing, - Checking, - Current, - Available, - Failed, - } - - /// - /// The model of the update view. - /// - public class UpdateViewModel : PropertyNotifyBase - { - private RootViewModel root; - private UpdateState state; - private ICommand updateCommand; - private string updateVersion; - private string updateChanges; - - - public UpdateViewModel(RootViewModel root) - { - this.root = root; - WixBA.Model.Bootstrapper.DetectUpdateBegin += this.DetectUpdateBegin; - WixBA.Model.Bootstrapper.DetectUpdate += this.DetectUpdate; - WixBA.Model.Bootstrapper.DetectUpdateComplete += this.DetectUpdateComplete; - - this.root.PropertyChanged += new PropertyChangedEventHandler(this.RootPropertyChanged); - - this.State = UpdateState.Initializing; - } - - void RootPropertyChanged(object sender, PropertyChangedEventArgs e) - { - if ("InstallState" == e.PropertyName) - { - base.OnPropertyChanged("CanUpdate"); - } - } - - public bool CheckingEnabled - { - get { return this.State == UpdateState.Initializing || this.State == UpdateState.Checking; } - } - - public bool CanUpdate - { - get - { - switch(this.root.InstallState) - { - case InstallationState.Waiting: - case InstallationState.Applied: - case InstallationState.Failed: - return this.IsUpdateAvailable; - default: - return false; - } - } - } - - public ICommand UpdateCommand - { - get - { - if (this.updateCommand == null) - { - this.updateCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.UpdateReplace), param => this.CanUpdate); - } - - return this.updateCommand; - } - } - - public bool IsUpdateAvailable - { - get { return this.State == UpdateState.Available; } - } - - /// - /// Gets and sets the state of the update view model. - /// - public UpdateState State - { - get - { - return this.state; - } - - set - { - if (this.state != value) - { - this.state = value; - base.OnPropertyChanged("State"); - base.OnPropertyChanged("CanUpdate"); - base.OnPropertyChanged("CheckingEnabled"); - base.OnPropertyChanged("IsUpdateAvailable"); - } - } - } - /// - /// The version of an available update. - /// - public string UpdateVersion - { - get - { - return updateVersion; - } - set - { - if (this.updateVersion != value) - { - this.updateVersion = value; - base.OnPropertyChanged("UpdateVersion"); - } - } - } - - /// - /// The changes in the available update. - /// - public string UpdateChanges - { - get - { - return updateChanges; - } - set - { - if (this.updateChanges != value) - { - this.updateChanges = value; - base.OnPropertyChanged("UpdateChanges"); - } - } - } - - private void DetectUpdateBegin(object sender, DetectUpdateBeginEventArgs e) - { - // Don't check for updates if: - // the first check failed (no retry) - // if we are being run as an uninstall - // if we are not under a full UI. - if ((UpdateState.Failed != this.State) && (LaunchAction.Uninstall != WixBA.Model.Command.Action) && (Display.Full == WixBA.Model.Command.Display)) - { - this.State = UpdateState.Checking; - e.Skip = false; - } - } - - private void DetectUpdate(object sender, DetectUpdateEventArgs e) - { - // The list of updates is sorted in descending version, so the first callback should be the largest update available. - // This update should be either larger than ours (so we are out of date), the same as ours (so we are current) - // or smaller than ours (we have a private build). If we really wanted to, we could leave the e.StopProcessingUpdates alone and - // enumerate all of the updates. - WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, WixBA.Model.Version)); - if (WixBA.Model.Engine.CompareVersions(e.Version, WixBA.Model.Version) > 0) - { - WixBA.Model.Engine.SetUpdate(null, e.UpdateLocation, e.Size, UpdateHashType.None, null); - this.UpdateVersion = String.Concat("v", e.Version.ToString()); - string changesFormat = @"{0}"; - this.UpdateChanges = String.Format(changesFormat, e.Content); - this.State = UpdateState.Available; - } - else - { - this.State = UpdateState.Current; - } - e.StopProcessingUpdates = true; - } - - private void DetectUpdateComplete(object sender, DetectUpdateCompleteEventArgs e) - { - // Failed to process an update, allow the existing bundle to still install. - if ((UpdateState.Failed != this.State) && !Hresult.Succeeded(e.Status)) - { - this.State = UpdateState.Failed; - WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status)); - e.IgnoreError = true; - } - // If we are uninstalling, we don't want to check or show an update - // If we are checking, then the feed didn't find any valid enclosures - // If we are initializing, we're either uninstalling or not a full UI - else if ((LaunchAction.Uninstall == WixBA.Model.Command.Action) || (UpdateState.Initializing == this.State) || (UpdateState.Checking == this.State)) - { - this.State = UpdateState.Unknown; - } - } - } -} diff --git a/src/WixToolset.WixBA/WindowProperties.cs b/src/WixToolset.WixBA/WindowProperties.cs deleted file mode 100644 index 6d1e273c..00000000 --- a/src/WixToolset.WixBA/WindowProperties.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.Windows; - using System.Windows.Media; - - /// - /// Dependency Properties associated with the main Window object. - /// - public class WindowProperties : DependencyObject - { - /// - /// Dependency Property to hold the result of detecting the relative luminosity (or brightness) of a Windows background. - /// - public static readonly DependencyProperty IsLightBackgroundProperty = DependencyProperty.Register( - "IsLightBackground", typeof(bool), typeof(WindowProperties), new PropertyMetadata( false )); - - private static Lazy _instance = new Lazy(() => - { - WindowProperties wp = new WindowProperties(); - wp.CheckBackgroundBrightness(); - return wp; - }); - - public static WindowProperties Instance - { - get - { - return _instance.Value; - } - } - - - public bool IsLightBackground - { - get { return (bool)GetValue(IsLightBackgroundProperty); } - private set { SetValue(IsLightBackgroundProperty, value); } - } - - /// - /// Use the Luminosity parameter of the background color to detect light vs dark theme settings. - /// - /// - /// This approach detects both the common High Contrast themes (White vs Black) and custom themes which may have relatively lighter backgrounds. - /// - public void CheckBackgroundBrightness() - { - SolidColorBrush windowbrush = System.Windows.SystemColors.WindowBrush; - System.Drawing.Color dcolor = System.Drawing.Color.FromArgb(windowbrush.Color.A, windowbrush.Color.R, windowbrush.Color.G, windowbrush.Color.B); - - var brightness = dcolor.GetBrightness(); - // Test for 'Lightness' at an arbitrary point, approaching 1.0 (White). - if (0.7 < brightness) - { - this.IsLightBackground = true; - } - else - { - this.IsLightBackground = false; - } - } - } -} diff --git a/src/WixToolset.WixBA/WixBA.BootstrapperCore.config b/src/WixToolset.WixBA/WixBA.BootstrapperCore.config deleted file mode 100644 index da8f8028..00000000 --- a/src/WixToolset.WixBA/WixBA.BootstrapperCore.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - -
- - - - - - - - - diff --git a/src/WixToolset.WixBA/WixBA.cs b/src/WixToolset.WixBA/WixBA.cs deleted file mode 100644 index 2d680c7e..00000000 --- a/src/WixToolset.WixBA/WixBA.cs +++ /dev/null @@ -1,229 +0,0 @@ -// 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. - -namespace WixToolset.WixBA -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Net; - using System.Text; - using WixToolset.Mba.Core; - - using Threading = System.Windows.Threading; - using WinForms = System.Windows.Forms; - - /// - /// The WiX toolset bootstrapper application. - /// - public class WixBA : BootstrapperApplication - { - public WixBA(IEngine engine, IBootstrapperCommand command) - : base(engine) - { - this.Command = command; - - this.BAManifest = new BootstrapperApplicationData(); - } - - internal IBootstrapperApplicationData BAManifest { get; } - - internal IBootstrapperCommand Command { get; } - - internal IEngine Engine => this.engine; - - /// - /// Gets the global model. - /// - static public Model Model { get; private set; } - - /// - /// Gets the global view. - /// - static public RootView View { get; private set; } - // TODO: We should refactor things so we dont have a global View. - - /// - /// Gets the global dispatcher. - /// - static public Threading.Dispatcher Dispatcher { get; private set; } - - /// - /// Launches the default web browser to the provided URI. - /// - /// URI to open the web browser. - public static void LaunchUrl(string uri) - { - WixBA.UseShellExecute(uri); - } - - /// - /// Open a log file. - /// - /// URI to a log file. - internal static void OpenLog(Uri uri) - { - WixBA.UseShellExecute(uri.ToString()); - } - - /// - /// Open a log folder. - /// - /// path to a log folder. - internal static void OpenLogFolder(string logFolder) - { - WixBA.UseShellExecute(logFolder); - } - - /// - /// Open a log folder. - /// - /// path to a log folder. - private static void UseShellExecute(string path) - { - // Switch the wait cursor since shellexec can take a second or so. - System.Windows.Input.Cursor cursor = WixBA.View.Cursor; - WixBA.View.Cursor = System.Windows.Input.Cursors.Wait; - Process process = null; - try - { - process = new Process(); - process.StartInfo.FileName = path; - process.StartInfo.UseShellExecute = true; - process.StartInfo.Verb = "open"; - - process.Start(); - } - finally - { - if (null != process) - { - process.Dispose(); - } - // back to the original cursor. - WixBA.View.Cursor = cursor; - } - } - - /// - /// Starts planning the appropriate action. - /// - /// Action to plan. - public static void Plan(LaunchAction action) - { - WixBA.Model.PlannedAction = action; - WixBA.Model.Engine.Plan(WixBA.Model.PlannedAction); - } - - public static void PlanLayout() - { - // Either default or set the layout directory - if (String.IsNullOrEmpty(WixBA.Model.Command.LayoutDirectory)) - { - WixBA.Model.LayoutDirectory = Directory.GetCurrentDirectory(); - - // Ask the user for layout folder if one wasn't provided and we're in full UI mode - if (WixBA.Model.Command.Display == Display.Full) - { - WixBA.Dispatcher.Invoke((Action)delegate() - { - WinForms.FolderBrowserDialog browserDialog = new WinForms.FolderBrowserDialog(); - browserDialog.RootFolder = Environment.SpecialFolder.MyComputer; - - // Default to the current directory. - browserDialog.SelectedPath = WixBA.Model.LayoutDirectory; - WinForms.DialogResult result = browserDialog.ShowDialog(); - - if (WinForms.DialogResult.OK == result) - { - WixBA.Model.LayoutDirectory = browserDialog.SelectedPath; - WixBA.Plan(WixBA.Model.Command.Action); - } - else - { - WixBA.View.Close(); - } - } - ); - } - } - else - { - WixBA.Model.LayoutDirectory = WixBA.Model.Command.LayoutDirectory; - WixBA.Plan(WixBA.Model.Command.Action); - } - } - - /// - /// Thread entry point for WiX Toolset Bootstrapper Application. - /// - protected override void Run() - { - this.Engine.Log(LogLevel.Verbose, "Running the WiX BA."); - WixBA.Model = new Model(this); - WixBA.Dispatcher = Threading.Dispatcher.CurrentDispatcher; - RootViewModel viewModel = new RootViewModel(); - - // Kick off detect which will populate the view models. - this.Engine.Detect(); - - // Create a Window to show UI. - if (WixBA.Model.Command.Display == Display.Passive || - WixBA.Model.Command.Display == Display.Full) - { - this.Engine.Log(LogLevel.Verbose, "Creating a UI."); - WixBA.View = new RootView(viewModel); - WixBA.View.Show(); - } - - Threading.Dispatcher.Run(); - - this.PostTelemetry(); - this.Engine.Quit(WixBA.Model.Result); - } - - private void PostTelemetry() - { - string result = String.Concat("0x", WixBA.Model.Result.ToString("x")); - - StringBuilder telemetryData = new StringBuilder(); - foreach (KeyValuePair kvp in WixBA.Model.Telemetry) - { - telemetryData.AppendFormat("{0}={1}+", kvp.Key, kvp.Value); - } - telemetryData.AppendFormat("Result={0}", result); - - byte[] data = Encoding.UTF8.GetBytes(telemetryData.ToString()); - - try - { - HttpWebRequest post = WixBA.Model.CreateWebRequest(String.Format(WixDistribution.TelemetryUrlFormat, WixBA.Model.Version.ToString(), result)); - post.Method = "POST"; - post.ContentType = "application/x-www-form-urlencoded"; - post.ContentLength = data.Length; - - using (Stream postStream = post.GetRequestStream()) - { - postStream.Write(data, 0, data.Length); - } - - HttpWebResponse response = (HttpWebResponse)post.GetResponse(); - } - catch (ArgumentException) - { - } - catch (FormatException) - { - } - catch (OverflowException) - { - } - catch (ProtocolViolationException) - { - } - catch (WebException) - { - } - } - } -} diff --git a/src/WixToolset.WixBA/WixBAFactory.cs b/src/WixToolset.WixBA/WixBAFactory.cs deleted file mode 100644 index 67fcc4b5..00000000 --- a/src/WixToolset.WixBA/WixBAFactory.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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. - -// Identifies the class that derives from IBootstrapperApplicationFactory and is the BAFactory class that gets -// instantiated by the interop layer -[assembly: WixToolset.Mba.Core.BootstrapperApplicationFactory(typeof(WixToolset.WixBA.WixBAFactory))] -namespace WixToolset.WixBA -{ - using WixToolset.Mba.Core; - - public class WixBAFactory : BaseBootstrapperApplicationFactory - { - protected override IBootstrapperApplication Create(IEngine engine, IBootstrapperCommand command) - { - return new WixBA(engine, command); - } - } -} diff --git a/src/WixToolset.WixBA/WixDistribution.cs b/src/WixToolset.WixBA/WixDistribution.cs deleted file mode 100644 index 0d20e585..00000000 --- a/src/WixToolset.WixBA/WixDistribution.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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. - -using System; -using System.Diagnostics; -using System.Reflection; -using System.Resources; - -[assembly: AssemblyCompany(".NET Foundation")] -[assembly: AssemblyCopyright("Copyright (c) .NET Foundation and contributors. All rights reserved.")] -[assembly: AssemblyProduct("WiX Toolset")] - -#if DEBUG - [assembly: AssemblyConfiguration("DEBUG")] -#else - [assembly: AssemblyConfiguration("")] -#endif -[assembly: NeutralResourcesLanguage("en-US")] - -namespace WixToolset -{ - /// - /// Distribution specific strings. - /// - internal static class WixDistribution - { - /// - /// News URL for the distribution. - /// - public static string NewsUrl = "http://wixtoolset.org/news/"; - - /// - /// Short product name for the distribution. - /// - public static string ShortProduct = "WiX Toolset"; - - /// - /// Support URL for the distribution. - /// - public static string SupportUrl = "http://wixtoolset.org/"; - - /// - /// Telemetry URL format for the distribution. - /// - public static string TelemetryUrlFormat = "http://wixtoolset.org/integrationtelemetry/v{0}/?r={1}"; - - /// - /// VS Extensions Landing page Url for the distribution. - /// - public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/"; - - public static string ReplacePlaceholders(string original, Assembly assembly) - { - if (null != assembly) - { - FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); - - original = original.Replace("[FileComments]", fileVersion.Comments); - original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright); - original = original.Replace("[FileProductName]", fileVersion.ProductName); - original = original.Replace("[FileVersion]", fileVersion.FileVersion); - - if (original.Contains("[FileVersionMajorMinor]")) - { - Version version = new Version(fileVersion.FileVersion); - original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor)); - } - - AssemblyCompanyAttribute company; - if (WixDistribution.TryGetAttribute(assembly, out company)) - { - original = original.Replace("[AssemblyCompany]", company.Company); - } - - AssemblyCopyrightAttribute copyright; - if (WixDistribution.TryGetAttribute(assembly, out copyright)) - { - original = original.Replace("[AssemblyCopyright]", copyright.Copyright); - } - - AssemblyDescriptionAttribute description; - if (WixDistribution.TryGetAttribute(assembly, out description)) - { - original = original.Replace("[AssemblyDescription]", description.Description); - } - - AssemblyProductAttribute product; - if (WixDistribution.TryGetAttribute(assembly, out product)) - { - original = original.Replace("[AssemblyProduct]", product.Product); - } - - AssemblyTitleAttribute title; - if (WixDistribution.TryGetAttribute(assembly, out title)) - { - original = original.Replace("[AssemblyTitle]", title.Title); - } - } - - original = original.Replace("[NewsUrl]", WixDistribution.NewsUrl); - original = original.Replace("[ShortProduct]", WixDistribution.ShortProduct); - original = original.Replace("[SupportUrl]", WixDistribution.SupportUrl); - return original; - } - - private static bool TryGetAttribute(Assembly assembly, out T attribute) where T : Attribute - { - attribute = null; - - object[] customAttributes = assembly.GetCustomAttributes(typeof(T), false); - if (null != customAttributes && 0 < customAttributes.Length) - { - attribute = customAttributes[0] as T; - } - - return null != attribute; - } - } -} diff --git a/src/WixToolset.WixBA/WixToolset.WixBA.csproj b/src/WixToolset.WixBA/WixToolset.WixBA.csproj deleted file mode 100644 index 8f834cd8..00000000 --- a/src/WixToolset.WixBA/WixToolset.WixBA.csproj +++ /dev/null @@ -1,46 +0,0 @@ - - - - - net45 - WixToolset.WixBA - WixToolset.WixBA - embedded - win-x86 - WixToolset.WixBA - WiX Bootstrapper Application - false - false - false - false - - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - PreserveNewest - Designer - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/burn/WixToolset.WixBA/BrowserProperties.cs b/src/test/burn/WixToolset.WixBA/BrowserProperties.cs new file mode 100644 index 00000000..c8fb6177 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/BrowserProperties.cs @@ -0,0 +1,40 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System.Windows; + using System.Windows.Controls; + + /// + /// Dependency Properties to support using a WebBrowser object. + /// + class BrowserProperties + { + /// + /// Dependency Propery used to pass an HTML string to the webBrowser object. + /// + public static readonly DependencyProperty HtmlDocProperty = + DependencyProperty.RegisterAttached("HtmlDoc", typeof(string), typeof(BrowserProperties), new PropertyMetadata(OnHtmlDocChanged)); + + public static string GetHtmlDoc(DependencyObject dependencyObject) + { + return (string)dependencyObject.GetValue(HtmlDocProperty); + } + + public static void SetHtmlDoc(DependencyObject dependencyObject, string htmldoc) + { + dependencyObject.SetValue(HtmlDocProperty, htmldoc); + } + + /// + /// Event handler that passes the HtmlDoc Dependency Property to MavigateToString method. + /// + /// + /// + private static void OnHtmlDocChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var webBrowser = (WebBrowser)d; + webBrowser.NavigateToString((string)e.NewValue); + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/Hresult.cs b/src/test/burn/WixToolset.WixBA/Hresult.cs new file mode 100644 index 00000000..a5e552ac --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/Hresult.cs @@ -0,0 +1,22 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + + /// + /// Utility class to work with HRESULTs + /// + internal class Hresult + { + /// + /// Determines if an HRESULT was a success code or not. + /// + /// HRESULT to verify. + /// True if the status is a success code. + public static bool Succeeded(int status) + { + return status >= 0; + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/InstallationViewModel.cs b/src/test/burn/WixToolset.WixBA/InstallationViewModel.cs new file mode 100644 index 00000000..2beebd02 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/InstallationViewModel.cs @@ -0,0 +1,683 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq; + using System.Reflection; + using System.Windows; + using System.Windows.Input; + using IO = System.IO; + using WixToolset.Mba.Core; + + /// + /// The states of detection. + /// + public enum DetectionState + { + Absent, + Present, + Newer, + } + + /// + /// The states of installation. + /// + public enum InstallationState + { + Initializing, + Detecting, + Waiting, + Planning, + Applying, + Applied, + Failed, + } + + /// + /// The model of the installation view in WixBA. + /// + public class InstallationViewModel : PropertyNotifyBase + { + private RootViewModel root; + + private Dictionary downloadRetries; + private bool downgrade; + private string downgradeMessage; + + private ICommand licenseCommand; + private ICommand launchHomePageCommand; + private ICommand launchNewsCommand; + private ICommand launchVSExtensionPageCommand; + private ICommand installCommand; + private ICommand repairCommand; + private ICommand uninstallCommand; + private ICommand openLogCommand; + private ICommand openLogFolderCommand; + private ICommand tryAgainCommand; + + private string message; + private DateTime cachePackageStart; + private DateTime executePackageStart; + + /// + /// Creates a new model of the installation view. + /// + public InstallationViewModel(RootViewModel root) + { + this.root = root; + this.downloadRetries = new Dictionary(); + + this.root.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.RootPropertyChanged); + + WixBA.Model.Bootstrapper.DetectBegin += this.DetectBegin; + WixBA.Model.Bootstrapper.DetectRelatedBundle += this.DetectedRelatedBundle; + WixBA.Model.Bootstrapper.DetectComplete += this.DetectComplete; + WixBA.Model.Bootstrapper.PlanPackageBegin += this.PlanPackageBegin; + WixBA.Model.Bootstrapper.PlanComplete += this.PlanComplete; + WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; + WixBA.Model.Bootstrapper.CacheAcquireBegin += this.CacheAcquireBegin; + WixBA.Model.Bootstrapper.CacheAcquireResolving += this.CacheAcquireResolving; + WixBA.Model.Bootstrapper.CacheAcquireComplete += this.CacheAcquireComplete; + WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; + WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; + WixBA.Model.Bootstrapper.Error += this.ExecuteError; + WixBA.Model.Bootstrapper.ApplyComplete += this.ApplyComplete; + } + + void RootPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (("DetectState" == e.PropertyName) || ("InstallState" == e.PropertyName)) + { + base.OnPropertyChanged("RepairEnabled"); + base.OnPropertyChanged("InstallEnabled"); + base.OnPropertyChanged("IsComplete"); + base.OnPropertyChanged("IsSuccessfulCompletion"); + base.OnPropertyChanged("IsFailedCompletion"); + base.OnPropertyChanged("StatusText"); + base.OnPropertyChanged("UninstallEnabled"); + } + } + + /// + /// Gets the version for the application. + /// + public string Version + { + get { return String.Concat("v", WixBA.Model.Version.ToString()); } + } + + /// + /// The Publisher of this bundle. + /// + public string Publisher + { + get + { + string company = "[AssemblyCompany]"; + return WixDistribution.ReplacePlaceholders(company, typeof(WixBA).Assembly); + } + } + + /// + /// The Publisher of this bundle. + /// + public string SupportUrl + { + get + { + return WixDistribution.SupportUrl; + } + } + public string VSExtensionUrl + { + get + { + return WixDistribution.VSExtensionsLandingUrl; + } + } + + public string Message + { + get + { + return this.message; + } + + set + { + if (this.message != value) + { + this.message = value; + base.OnPropertyChanged("Message"); + } + } + } + + /// + /// Gets and sets whether the view model considers this install to be a downgrade. + /// + public bool Downgrade + { + get + { + return this.downgrade; + } + + set + { + if (this.downgrade != value) + { + this.downgrade = value; + base.OnPropertyChanged("Downgrade"); + } + } + } + + public string DowngradeMessage + { + get + { + return this.downgradeMessage; + } + set + { + if (this.downgradeMessage != value) + { + this.downgradeMessage = value; + base.OnPropertyChanged("DowngradeMessage"); + } + } + } + + public ICommand LaunchHomePageCommand + { + get + { + if (this.launchHomePageCommand == null) + { + this.launchHomePageCommand = new RelayCommand(param => WixBA.LaunchUrl(this.SupportUrl), param => true); + } + + return this.launchHomePageCommand; + } + } + + public ICommand LaunchNewsCommand + { + get + { + if (this.launchNewsCommand == null) + { + this.launchNewsCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.NewsUrl), param => true); + } + + return this.launchNewsCommand; + } + } + + public ICommand LaunchVSExtensionPageCommand + { + get + { + if (this.launchVSExtensionPageCommand == null) + { + this.launchVSExtensionPageCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.VSExtensionsLandingUrl), param => true); + } + + return this.launchVSExtensionPageCommand; + } + } + + public ICommand LicenseCommand + { + get + { + if (this.licenseCommand == null) + { + this.licenseCommand = new RelayCommand(param => this.LaunchLicense(), param => true); + } + + return this.licenseCommand; + } + } + + public bool LicenseEnabled + { + get { return this.LicenseCommand.CanExecute(this); } + } + + public ICommand CloseCommand + { + get { return this.root.CloseCommand; } + } + + public bool IsComplete + { + get { return IsSuccessfulCompletion || IsFailedCompletion; } + } + + public bool IsSuccessfulCompletion + { + get { return InstallationState.Applied == this.root.InstallState; } + } + + public bool IsFailedCompletion + { + get { return InstallationState.Failed == this.root.InstallState; } + } + + public ICommand InstallCommand + { + get + { + if (this.installCommand == null) + { + this.installCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Install), param => this.root.DetectState == DetectionState.Absent && this.root.InstallState == InstallationState.Waiting); + } + + return this.installCommand; + } + } + + public bool InstallEnabled + { + get { return this.InstallCommand.CanExecute(this); } + } + + public ICommand RepairCommand + { + get + { + if (this.repairCommand == null) + { + this.repairCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Repair), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); + } + + return this.repairCommand; + } + } + + public bool RepairEnabled + { + get { return this.RepairCommand.CanExecute(this); } + } + + public ICommand UninstallCommand + { + get + { + if (this.uninstallCommand == null) + { + this.uninstallCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Uninstall), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); + } + + return this.uninstallCommand; + } + } + + public bool UninstallEnabled + { + get { return this.UninstallCommand.CanExecute(this); } + } + + public ICommand OpenLogCommand + { + get + { + if (this.openLogCommand == null) + { + this.openLogCommand = new RelayCommand(param => WixBA.OpenLog(new Uri(WixBA.Model.Engine.GetVariableString("WixBundleLog")))); + } + return this.openLogCommand; + } + } + + public ICommand OpenLogFolderCommand + { + get + { + if (this.openLogFolderCommand == null) + { + string logFolder = IO.Path.GetDirectoryName(WixBA.Model.Engine.GetVariableString("WixBundleLog")); + this.openLogFolderCommand = new RelayCommand(param => WixBA.OpenLogFolder(logFolder)); + } + return this.openLogFolderCommand; + } + } + + public ICommand TryAgainCommand + { + get + { + if (this.tryAgainCommand == null) + { + this.tryAgainCommand = new RelayCommand(param => + { + this.root.Canceled = false; + WixBA.Plan(WixBA.Model.PlannedAction); + }, param => IsFailedCompletion); + } + + return this.tryAgainCommand; + } + } + + public string StatusText + { + get + { + switch(this.root.InstallState) + { + case InstallationState.Applied: + return "Complete"; + case InstallationState.Failed: + return this.root.Canceled ? "Cancelled" : "Failed"; + default: + return "Unknown"; // this shouldn't be shown in the UI. + } + } + } + + /// + /// Launches the license in the default viewer. + /// + private void LaunchLicense() + { + string folder = IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + WixBA.LaunchUrl(IO.Path.Combine(folder, "License.txt")); + } + + private void DetectBegin(object sender, DetectBeginEventArgs e) + { + this.root.DetectState = e.Installed ? DetectionState.Present : DetectionState.Absent; + WixBA.Model.PlannedAction = LaunchAction.Unknown; + } + + private void DetectedRelatedBundle(object sender, DetectRelatedBundleEventArgs e) + { + if (e.Operation == RelatedOperation.Downgrade) + { + this.Downgrade = true; + } + + if (!WixBA.Model.BAManifest.Bundle.Packages.ContainsKey(e.ProductCode)) + { + WixBA.Model.BAManifest.Bundle.AddRelatedBundleAsPackage(e); + } + } + + private void DetectComplete(object sender, DetectCompleteEventArgs e) + { + // Parse the command line string before any planning. + this.ParseCommandLine(); + this.root.InstallState = InstallationState.Waiting; + + if (LaunchAction.Uninstall == WixBA.Model.Command.Action && + ResumeType.Arp != WixBA.Model.Command.Resume) // MSI and WixStdBA require some kind of confirmation before proceeding so WixBA should, too. + { + WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for uninstall"); + WixBA.Plan(LaunchAction.Uninstall); + } + else if (Hresult.Succeeded(e.Status)) + { + if (this.Downgrade) + { + this.root.DetectState = DetectionState.Newer; + var relatedPackages = WixBA.Model.BAManifest.Bundle.Packages.Values.Where(p => p.Type == PackageType.UpgradeBundle); + var installedVersion = relatedPackages.Any() ? new Version(relatedPackages.Max(p => p.Version)) : null; + if (installedVersion != null && installedVersion < new Version(4, 1) && installedVersion.Build > 10) + { + this.DowngradeMessage = "You must uninstall WiX v" + installedVersion + " before you can install this."; + } + else + { + this.DowngradeMessage = "There is already a newer version of WiX installed on this machine."; + } + } + + if (LaunchAction.Layout == WixBA.Model.Command.Action) + { + WixBA.PlanLayout(); + } + else if (WixBA.Model.Command.Display != Display.Full) + { + // If we're not waiting for the user to click install, dispatch plan with the default action. + WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for non-interactive mode."); + WixBA.Plan(WixBA.Model.Command.Action); + } + } + else + { + this.root.InstallState = InstallationState.Failed; + } + + // Force all commands to reevaluate CanExecute. + // InvalidateRequerySuggested must be run on the UI thread. + root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); + } + + private void PlanPackageBegin(object sender, PlanPackageBeginEventArgs e) + { + // 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. + if ( e.PackageId.StartsWith("NetFx4", StringComparison.OrdinalIgnoreCase)) + { + e.State = RequestState.None; + } + } + + private void PlanComplete(object sender, PlanCompleteEventArgs e) + { + if (Hresult.Succeeded(e.Status)) + { + this.root.PreApplyState = this.root.InstallState; + this.root.InstallState = InstallationState.Applying; + WixBA.Model.Engine.Apply(this.root.ViewWindowHandle); + } + else + { + this.root.InstallState = InstallationState.Failed; + } + } + + private void ApplyBegin(object sender, ApplyBeginEventArgs e) + { + this.downloadRetries.Clear(); + } + + private void CacheAcquireBegin(object sender, CacheAcquireBeginEventArgs e) + { + this.cachePackageStart = DateTime.Now; + } + + private void CacheAcquireResolving(object sender, CacheAcquireResolvingEventArgs e) + { + if (e.Action == CacheResolveOperation.Download && !this.downloadRetries.ContainsKey(e.PackageOrContainerId)) + { + this.downloadRetries.Add(e.PackageOrContainerId, 0); + } + } + + private void CacheAcquireComplete(object sender, CacheAcquireCompleteEventArgs e) + { + this.AddPackageTelemetry("Cache", e.PackageOrContainerId ?? String.Empty, DateTime.Now.Subtract(this.cachePackageStart).TotalMilliseconds, e.Status); + + if (e.Status < 0 && this.downloadRetries.TryGetValue(e.PackageOrContainerId, out var retries) && retries < 3) + { + this.downloadRetries[e.PackageOrContainerId] = retries + 1; + switch (e.Status) + { + case -2147023294: //HRESULT_FROM_WIN32(ERROR_INSTALL_USEREXIT) + case -2147024894: //HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) + case -2147012889: //HRESULT_FROM_WIN32(ERROR_INTERNET_NAME_NOT_RESOLVED) + break; + default: + e.Action = BOOTSTRAPPER_CACHEACQUIRECOMPLETE_ACTION.Retry; + break; + } + } + } + + private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) + { + lock (this) + { + this.executePackageStart = e.ShouldExecute ? DateTime.Now : DateTime.MinValue; + } + } + + private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) + { + lock (this) + { + if (DateTime.MinValue < this.executePackageStart) + { + this.AddPackageTelemetry("Execute", e.PackageId ?? String.Empty, DateTime.Now.Subtract(this.executePackageStart).TotalMilliseconds, e.Status); + this.executePackageStart = DateTime.MinValue; + } + } + } + + private void ExecuteError(object sender, ErrorEventArgs e) + { + lock (this) + { + if (!this.root.Canceled) + { + // If the error is a cancel coming from the engine during apply we want to go back to the preapply state. + if (InstallationState.Applying == this.root.InstallState && (int)Error.UserCancelled == e.ErrorCode) + { + this.root.InstallState = this.root.PreApplyState; + } + else + { + this.Message = e.ErrorMessage; + + if (Display.Full == WixBA.Model.Command.Display) + { + // On HTTP authentication errors, have the engine try to do authentication for us. + if (ErrorType.HttpServerAuthentication == e.ErrorType || ErrorType.HttpProxyAuthentication == e.ErrorType) + { + e.Result = Result.TryAgain; + } + else // show an error dialog. + { + MessageBoxButton msgbox = MessageBoxButton.OK; + switch (e.UIHint & 0xF) + { + case 0: + msgbox = MessageBoxButton.OK; + break; + case 1: + msgbox = MessageBoxButton.OKCancel; + break; + // There is no 2! That would have been MB_ABORTRETRYIGNORE. + case 3: + msgbox = MessageBoxButton.YesNoCancel; + break; + case 4: + msgbox = MessageBoxButton.YesNo; + break; + // default: stay with MBOK since an exact match is not available. + } + + MessageBoxResult result = MessageBoxResult.None; + WixBA.View.Dispatcher.Invoke((Action)delegate() + { + result = MessageBox.Show(WixBA.View, e.ErrorMessage, "WiX Toolset", msgbox, MessageBoxImage.Error); + } + ); + + // If there was a match from the UI hint to the msgbox value, use the result from the + // message box. Otherwise, we'll ignore it and return the default to Burn. + if ((e.UIHint & 0xF) == (int)msgbox) + { + e.Result = (Result)result; + } + } + } + } + } + else // canceled, so always return cancel. + { + e.Result = Result.Cancel; + } + } + } + + private void ApplyComplete(object sender, ApplyCompleteEventArgs e) + { + WixBA.Model.Result = e.Status; // remember the final result of the apply. + + // Set the state to applied or failed unless the state has already been set back to the preapply state + // which means we need to show the UI as it was before the apply started. + if (this.root.InstallState != this.root.PreApplyState) + { + this.root.InstallState = Hresult.Succeeded(e.Status) ? InstallationState.Applied : InstallationState.Failed; + } + + // If we're not in Full UI mode, we need to alert the dispatcher to stop and close the window for passive. + if (Display.Full != WixBA.Model.Command.Display) + { + // If its passive, send a message to the window to close. + if (Display.Passive == WixBA.Model.Command.Display) + { + WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window for non-interactive install"); + WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); + } + else + { + WixBA.Dispatcher.InvokeShutdown(); + } + return; + } + 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. + { + WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window since update successful."); + WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); + return; + } + else if (root.AutoClose) + { + // Automatically closing since the user clicked the X button. + WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); + return; + } + + // Force all commands to reevaluate CanExecute. + // InvalidateRequerySuggested must be run on the UI thread. + root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); + } + + private void ParseCommandLine() + { + // Get array of arguments based on the system parsing algorithm. + string[] args = WixBA.Model.Command.CommandLineArgs; + for (int i = 0; i < args.Length; ++i) + { + if (args[i].StartsWith("InstallFolder=", StringComparison.InvariantCultureIgnoreCase)) + { + // Allow relative directory paths. Also validates. + string[] param = args[i].Split(new char[] {'='}, 2); + this.root.InstallDirectory = IO.Path.Combine(Environment.CurrentDirectory, param[1]); + } + } + } + + private void AddPackageTelemetry(string prefix, string id, double time, int result) + { + lock (this) + { + string key = String.Format("{0}Time_{1}", prefix, id); + string value = time.ToString(); + WixBA.Model.Telemetry.Add(new KeyValuePair(key, value)); + + key = String.Format("{0}Result_{1}", prefix, id); + value = String.Concat("0x", result.ToString("x")); + WixBA.Model.Telemetry.Add(new KeyValuePair(key, value)); + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/Model.cs b/src/test/burn/WixToolset.WixBA/Model.cs new file mode 100644 index 00000000..a557fa4e --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/Model.cs @@ -0,0 +1,132 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.Collections.Generic; + using System.Net; + using WixToolset.Mba.Core; + + /// + /// The model. + /// + public class Model + { + private const string BurnBundleInstallDirectoryVariable = "InstallFolder"; + private const string BurnBundleLayoutDirectoryVariable = "WixBundleLayoutDirectory"; + private const string BurnBundleVersionVariable = "WixBundleVersion"; + + /// + /// Creates a new model for the BA. + /// + /// The BA. + public Model(WixBA bootstrapper) + { + this.BAManifest = bootstrapper.BAManifest; + this.Bootstrapper = bootstrapper; + this.Command = bootstrapper.Command; + this.Engine = bootstrapper.Engine; + this.Telemetry = new List>(); + this.Version = this.Engine.GetVariableVersion(BurnBundleVersionVariable); + } + + public IBootstrapperApplicationData BAManifest { get; } + + /// + /// Gets the bootstrapper. + /// + public IDefaultBootstrapperApplication Bootstrapper { get; } + + /// + /// Gets the bootstrapper command-line. + /// + public IBootstrapperCommand Command { get; } + + /// + /// Gets the bootstrapper engine. + /// + public IEngine Engine { get; } + + /// + /// Gets the key/value pairs used in telemetry. + /// + public List> Telemetry { get; private set; } + + /// + /// Get or set the final result of the installation. + /// + public int Result { get; set; } + + /// + /// Get the version of the install. + /// + public string Version { get; private set; } + + /// + /// Get or set the path where the bundle is installed. + /// + public string InstallDirectory + { + get + { + if (!this.Engine.ContainsVariable(BurnBundleInstallDirectoryVariable)) + { + return null; + } + + return this.Engine.GetVariableString(BurnBundleInstallDirectoryVariable); + } + + set + { + this.Engine.SetVariableString(BurnBundleInstallDirectoryVariable, value, false); + } + } + + /// + /// Get or set the path for the layout to be created. + /// + public string LayoutDirectory + { + get + { + if (!this.Engine.ContainsVariable(BurnBundleLayoutDirectoryVariable)) + { + return null; + } + + return this.Engine.GetVariableString(BurnBundleLayoutDirectoryVariable); + } + + set + { + this.Engine.SetVariableString(BurnBundleLayoutDirectoryVariable, value, false); + } + } + + public LaunchAction PlannedAction { get; set; } + + /// + /// Creates a correctly configured HTTP web request. + /// + /// URI to connect to. + /// Correctly configured HTTP web request. + public HttpWebRequest CreateWebRequest(string uri) + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); + request.UserAgent = String.Concat("WixInstall", this.Version.ToString()); + + return request; + } + + /// + /// Gets the display name for a package if possible. + /// + /// Identity of the package to find the display name. + /// Display name of the package if found or the package id if not. + public string GetPackageName(string packageId) + { + return this.BAManifest.Bundle.Packages.TryGetValue(packageId, out var package) ? package.DisplayName : packageId; + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/NewsItem.cs b/src/test/burn/WixToolset.WixBA/NewsItem.cs new file mode 100644 index 00000000..f8bf7aed --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/NewsItem.cs @@ -0,0 +1,18 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + + /// + /// The model for an individual news item. + /// + public class NewsItem + { + public string Author { get; set; } + public string Title { get; set; } + public string Url { get; set; } + public string Snippet { get; set; } + public DateTime Updated { get; set; } + } +} diff --git a/src/test/burn/WixToolset.WixBA/ProgressViewModel.cs b/src/test/burn/WixToolset.WixBA/ProgressViewModel.cs new file mode 100644 index 00000000..6f7bb028 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/ProgressViewModel.cs @@ -0,0 +1,245 @@ +// 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. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Text.RegularExpressions; +using WixToolset.Mba.Core; + +namespace WixToolset.WixBA +{ + public class ProgressViewModel : PropertyNotifyBase + { + private static readonly Regex TrimActionTimeFromMessage = new Regex(@"^\w+\s+\d+:\d+:\d+:\s+", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.Singleline); + + private RootViewModel root; + private Dictionary executingPackageOrderIndex; + + private int progressPhases; + private int progress; + private int cacheProgress; + private int executeProgress; + private string package; + private string message; + + public ProgressViewModel(RootViewModel root) + { + this.root = root; + this.executingPackageOrderIndex = new Dictionary(); + + this.root.PropertyChanged += this.RootPropertyChanged; + + WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; + WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; + WixBA.Model.Bootstrapper.ExecuteProgress += this.ApplyExecuteProgress; + WixBA.Model.Bootstrapper.PauseAutomaticUpdatesBegin += this.PauseAutomaticUpdatesBegin; + WixBA.Model.Bootstrapper.SystemRestorePointBegin += this.SystemRestorePointBegin; + WixBA.Model.Bootstrapper.PlanBegin += this.PlanBegin; + WixBA.Model.Bootstrapper.PlannedPackage += this.PlannedPackage; + WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; + WixBA.Model.Bootstrapper.Progress += this.ApplyProgress; + WixBA.Model.Bootstrapper.CacheAcquireProgress += this.CacheAcquireProgress; + WixBA.Model.Bootstrapper.CacheContainerOrPayloadVerifyProgress += CacheContainerOrPayloadVerifyProgress; + WixBA.Model.Bootstrapper.CachePayloadExtractProgress += CachePayloadExtractProgress; + WixBA.Model.Bootstrapper.CacheVerifyProgress += CacheVerifyProgress; + WixBA.Model.Bootstrapper.CacheComplete += this.CacheComplete; + } + + public bool ProgressEnabled + { + get { return this.root.InstallState == InstallationState.Applying; } + } + + public int Progress + { + get + { + return this.progress; + } + + set + { + if (this.progress != value) + { + this.progress = value; + base.OnPropertyChanged("Progress"); + } + } + } + + public string Package + { + get + { + return this.package; + } + + set + { + if (this.package != value) + { + this.package = value; + base.OnPropertyChanged("Package"); + } + } + } + + public string Message + { + get + { + return this.message; + } + + set + { + if (this.message != value) + { + this.message = value; + base.OnPropertyChanged("Message"); + } + } + } + + void RootPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if ("InstallState" == e.PropertyName) + { + base.OnPropertyChanged("ProgressEnabled"); + } + } + + private void PlanBegin(object sender, PlanBeginEventArgs e) + { + lock (this) + { + this.executingPackageOrderIndex.Clear(); + } + } + + private void PlannedPackage(object sender, PlannedPackageEventArgs e) + { + if (ActionState.None != e.Execute) + { + lock (this) + { + Debug.Assert(!this.executingPackageOrderIndex.ContainsKey(e.PackageId)); + this.executingPackageOrderIndex.Add(e.PackageId, this.executingPackageOrderIndex.Count); + } + } + } + + private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) + { + lock (this) + { + this.Package = WixBA.Model.GetPackageName(e.PackageId); + this.Message = String.Format("Processing: {0}", this.Package); + e.Cancel = this.root.Canceled; + } + } + + private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) + { + lock (this) + { // avoid a stale display + this.Message = String.Empty; + } + } + + private void PauseAutomaticUpdatesBegin(object sender, PauseAutomaticUpdatesBeginEventArgs e) + { + lock (this) + { + this.Message = "Pausing Windows automatic updates"; + } + } + + private void SystemRestorePointBegin(object sender, SystemRestorePointBeginEventArgs e) + { + lock (this) + { + this.Message = "Creating system restore point"; + } + } + + private void ApplyBegin(object sender, ApplyBeginEventArgs e) + { + this.progressPhases = e.PhaseCount; + } + + private void ApplyProgress(object sender, ProgressEventArgs e) + { + lock (this) + { + e.Cancel = this.root.Canceled; + } + } + + private void CacheAcquireProgress(object sender, CacheAcquireProgressEventArgs e) + { + lock (this) + { + this.cacheProgress = e.OverallPercentage; + this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; + e.Cancel = this.root.Canceled; + } + } + + private void CacheContainerOrPayloadVerifyProgress(object sender, CacheContainerOrPayloadVerifyProgressEventArgs e) + { + lock (this) + { + this.cacheProgress = e.OverallPercentage; + this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; + e.Cancel = this.root.Canceled; + } + } + + private void CachePayloadExtractProgress(object sender, CachePayloadExtractProgressEventArgs e) + { + lock (this) + { + this.cacheProgress = e.OverallPercentage; + this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; + e.Cancel = this.root.Canceled; + } + } + + private void CacheVerifyProgress(object sender, CacheVerifyProgressEventArgs e) + { + lock (this) + { + this.cacheProgress = e.OverallPercentage; + this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; + e.Cancel = this.root.Canceled; + } + } + + private void CacheComplete(object sender, CacheCompleteEventArgs e) + { + lock (this) + { + this.cacheProgress = 100; + this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; + } + } + + private void ApplyExecuteProgress(object sender, ExecuteProgressEventArgs e) + { + lock (this) + { + this.executeProgress = e.OverallPercentage; + this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; + + if (WixBA.Model.Command.Display == Display.Embedded) + { + WixBA.Model.Engine.SendEmbeddedProgress(e.ProgressPercentage, this.Progress); + } + + e.Cancel = this.root.Canceled; + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/PropertyNotifyBase.cs b/src/test/burn/WixToolset.WixBA/PropertyNotifyBase.cs new file mode 100644 index 00000000..f8264614 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/PropertyNotifyBase.cs @@ -0,0 +1,59 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.ComponentModel; + using System.Diagnostics; + + /// + /// It provides support for property change notifications. + /// + public abstract class PropertyNotifyBase : INotifyPropertyChanged + { + /// + /// Initializes a new instance of the class. + /// + protected PropertyNotifyBase() + { + } + + /// + /// Raised when a property on this object has a new value. + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Warns the developer if this object does not have a public property with the + /// specified name. This method does not exist in a Release build. + /// + /// Property name to verify. + [Conditional("DEBUG")] + [DebuggerStepThrough] + public void VerifyPropertyName(string propertyName) + { + // Verify that the property name matches a real, public, instance property + // on this object. + if (null == TypeDescriptor.GetProperties(this)[propertyName]) + { + Debug.Fail(String.Concat("Invalid property name: ", propertyName)); + } + } + + /// + /// Raises this object's PropertyChanged event. + /// + /// The property that has a new value. + protected virtual void OnPropertyChanged(string propertyName) + { + this.VerifyPropertyName(propertyName); + + PropertyChangedEventHandler handler = this.PropertyChanged; + if (null != handler) + { + PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); + handler(this, e); + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/RelayCommand.cs b/src/test/burn/WixToolset.WixBA/RelayCommand.cs new file mode 100644 index 00000000..d3ab2d7a --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/RelayCommand.cs @@ -0,0 +1,45 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.Diagnostics; + using System.Windows.Input; + + /// + /// Base class that implements ICommand interface via delegates. + /// + public class RelayCommand : ICommand + { + private readonly Action execute; + private readonly Predicate canExecute; + + public RelayCommand(Action execute) + : this(execute, null) + { + } + + public RelayCommand(Action execute, Predicate canExecute) + { + this.execute = execute; + this.canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged + { + add { CommandManager.RequerySuggested += value; } + remove { CommandManager.RequerySuggested -= value; } + } + + [DebuggerStepThrough] + public bool CanExecute(object parameter) + { + return this.canExecute == null ? true : this.canExecute(parameter); + } + + public void Execute(object parameter) + { + this.execute(parameter); + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/Resources/logo-black-hollow.png b/src/test/burn/WixToolset.WixBA/Resources/logo-black-hollow.png new file mode 100644 index 00000000..9d0290bd Binary files /dev/null and b/src/test/burn/WixToolset.WixBA/Resources/logo-black-hollow.png differ diff --git a/src/test/burn/WixToolset.WixBA/Resources/logo-white-hollow.png b/src/test/burn/WixToolset.WixBA/Resources/logo-white-hollow.png new file mode 100644 index 00000000..242c7350 Binary files /dev/null and b/src/test/burn/WixToolset.WixBA/Resources/logo-white-hollow.png differ diff --git a/src/test/burn/WixToolset.WixBA/RootView.xaml b/src/test/burn/WixToolset.WixBA/RootView.xaml new file mode 100644 index 00000000..b7d535d1 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/RootView.xaml @@ -0,0 +1,420 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/WixToolset.WixBA/RootView.xaml.cs b/src/test/burn/WixToolset.WixBA/RootView.xaml.cs new file mode 100644 index 00000000..e1ee19db --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/RootView.xaml.cs @@ -0,0 +1,51 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System.ComponentModel; + using System.Windows; + using System.Windows.Interop; + + /// + /// Interaction logic for View.xaml + /// + public partial class RootView : Window + { + /// + /// Creates the view populated with it's model. + /// + /// Model for the view. + public RootView(RootViewModel viewModel) + { + this.DataContext = viewModel; + + this.Loaded += (sender, e) => WixBA.Model.Engine.CloseSplashScreen(); + this.Closed += (sender, e) => this.Dispatcher.InvokeShutdown(); // shutdown dispatcher when the window is closed. + + this.InitializeComponent(); + + viewModel.Dispatcher = this.Dispatcher; + viewModel.ViewWindowHandle = new WindowInteropHelper(this).EnsureHandle(); + } + + /// + /// Event is fired when the window is closing. + /// + /// + /// + private void Window_Closing(object sender, CancelEventArgs e) + { + RootViewModel rvm = this.DataContext as RootViewModel; + if ((null != rvm) && (InstallationState.Applying == rvm.InstallState)) + { + rvm.CancelButton_Click(); + if (rvm.Canceled) + { + // Defer closing until the engine has canceled processing. + e.Cancel = true; + rvm.AutoClose = true; + } + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/RootViewModel.cs b/src/test/burn/WixToolset.WixBA/RootViewModel.cs new file mode 100644 index 00000000..8cff7274 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/RootViewModel.cs @@ -0,0 +1,202 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.Windows; + using System.Windows.Input; + using System.Windows.Threading; + using WixToolset.Mba.Core; + + /// + /// The errors returned from the engine + /// + public enum Error + { + UserCancelled = 1223, + } + + /// + /// The model of the root view in WixBA. + /// + public class RootViewModel : PropertyNotifyBase + { + private ICommand cancelCommand; + private ICommand closeCommand; + + private bool canceled; + private InstallationState installState; + private DetectionState detectState; + + /// + /// Creates a new model of the root view. + /// + public RootViewModel() + { + this.InstallationViewModel = new InstallationViewModel(this); + this.ProgressViewModel = new ProgressViewModel(this); + this.UpdateViewModel = new UpdateViewModel(this); + } + + public InstallationViewModel InstallationViewModel { get; private set; } + public ProgressViewModel ProgressViewModel { get; private set; } + public UpdateViewModel UpdateViewModel { get; private set; } + public Dispatcher Dispatcher { get; set; } + public IntPtr ViewWindowHandle { get; set; } + public bool AutoClose { get; set; } + + public ICommand CloseCommand + { + get + { + if (this.closeCommand == null) + { + this.closeCommand = new RelayCommand(param => WixBA.View.Close()); + } + + return this.closeCommand; + } + } + + public ICommand CancelCommand + { + get + { + if (this.cancelCommand == null) + { + this.cancelCommand = new RelayCommand(param => + { + this.CancelButton_Click(); + }, + param => !this.Canceled); + } + + return this.cancelCommand; + } + } + + public bool CancelAvailable + { + get { return InstallationState.Applying == this.InstallState; } + } + + public bool Canceled + { + get + { + return this.canceled; + } + + set + { + if (this.canceled != value) + { + this.canceled = value; + base.OnPropertyChanged("Canceled"); + } + } + } + + /// + /// Gets and sets the detect state of the view's model. + /// + public DetectionState DetectState + { + get + { + return this.detectState; + } + + set + { + if (this.detectState != value) + { + this.detectState = value; + + // Notify all the properties derived from the state that the state changed. + base.OnPropertyChanged("DetectState"); + } + } + } + + /// + /// Gets and sets the installation state of the view's model. + /// + public InstallationState InstallState + { + get + { + return this.installState; + } + + set + { + if (this.installState != value) + { + this.installState = value; + + // Notify all the properties derived from the state that the state changed. + base.OnPropertyChanged("InstallState"); + base.OnPropertyChanged("CancelAvailable"); + } + } + } + + /// + /// Gets and sets the state of the view's model before apply begins in order to return to that state if cancel or rollback occurs. + /// + public InstallationState PreApplyState { get; set; } + + /// + /// Gets and sets the path where the bundle is currently installed or will be installed. + /// + public string InstallDirectory + { + get + { + return WixBA.Model.InstallDirectory; + } + + set + { + if (WixBA.Model.InstallDirectory != value) + { + WixBA.Model.InstallDirectory = value; + base.OnPropertyChanged("InstallDirectory"); + } + } + } + + /// + /// The Title of this bundle. + /// + public string Title + { + get + { + return WixDistribution.ShortProduct; + } + } + + /// + /// Prompts the user to make sure they want to cancel. + /// This needs to run on the UI thread, use Dispatcher.Invoke to call this from a background thread. + /// + public void CancelButton_Click() + { + if (this.Canceled) + { + return; + } + + if (Display.Full == WixBA.Model.Command.Display) + { + this.Canceled = (MessageBoxResult.Yes == MessageBox.Show(WixBA.View, "Are you sure you want to cancel?", "WiX Toolset", MessageBoxButton.YesNo, MessageBoxImage.Error)); + } + else + { + this.Canceled = true; + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/Styles.xaml b/src/test/burn/WixToolset.WixBA/Styles.xaml new file mode 100644 index 00000000..fa0afc4f --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/Styles.xaml @@ -0,0 +1,194 @@ + + + + + + + + + + Segoe UI, Arial + 45 + 12 + 14 + + + + + + + #FF1EF1E8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/test/burn/WixToolset.WixBA/UpdateViewModel.cs b/src/test/burn/WixToolset.WixBA/UpdateViewModel.cs new file mode 100644 index 00000000..80d894cb --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/UpdateViewModel.cs @@ -0,0 +1,207 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.ComponentModel; + using System.Windows.Input; + using WixToolset.Mba.Core; + + /// + /// The states of the update view model. + /// + public enum UpdateState + { + Unknown, + Initializing, + Checking, + Current, + Available, + Failed, + } + + /// + /// The model of the update view. + /// + public class UpdateViewModel : PropertyNotifyBase + { + private RootViewModel root; + private UpdateState state; + private ICommand updateCommand; + private string updateVersion; + private string updateChanges; + + + public UpdateViewModel(RootViewModel root) + { + this.root = root; + WixBA.Model.Bootstrapper.DetectUpdateBegin += this.DetectUpdateBegin; + WixBA.Model.Bootstrapper.DetectUpdate += this.DetectUpdate; + WixBA.Model.Bootstrapper.DetectUpdateComplete += this.DetectUpdateComplete; + + this.root.PropertyChanged += new PropertyChangedEventHandler(this.RootPropertyChanged); + + this.State = UpdateState.Initializing; + } + + void RootPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if ("InstallState" == e.PropertyName) + { + base.OnPropertyChanged("CanUpdate"); + } + } + + public bool CheckingEnabled + { + get { return this.State == UpdateState.Initializing || this.State == UpdateState.Checking; } + } + + public bool CanUpdate + { + get + { + switch(this.root.InstallState) + { + case InstallationState.Waiting: + case InstallationState.Applied: + case InstallationState.Failed: + return this.IsUpdateAvailable; + default: + return false; + } + } + } + + public ICommand UpdateCommand + { + get + { + if (this.updateCommand == null) + { + this.updateCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.UpdateReplace), param => this.CanUpdate); + } + + return this.updateCommand; + } + } + + public bool IsUpdateAvailable + { + get { return this.State == UpdateState.Available; } + } + + /// + /// Gets and sets the state of the update view model. + /// + public UpdateState State + { + get + { + return this.state; + } + + set + { + if (this.state != value) + { + this.state = value; + base.OnPropertyChanged("State"); + base.OnPropertyChanged("CanUpdate"); + base.OnPropertyChanged("CheckingEnabled"); + base.OnPropertyChanged("IsUpdateAvailable"); + } + } + } + /// + /// The version of an available update. + /// + public string UpdateVersion + { + get + { + return updateVersion; + } + set + { + if (this.updateVersion != value) + { + this.updateVersion = value; + base.OnPropertyChanged("UpdateVersion"); + } + } + } + + /// + /// The changes in the available update. + /// + public string UpdateChanges + { + get + { + return updateChanges; + } + set + { + if (this.updateChanges != value) + { + this.updateChanges = value; + base.OnPropertyChanged("UpdateChanges"); + } + } + } + + private void DetectUpdateBegin(object sender, DetectUpdateBeginEventArgs e) + { + // Don't check for updates if: + // the first check failed (no retry) + // if we are being run as an uninstall + // if we are not under a full UI. + if ((UpdateState.Failed != this.State) && (LaunchAction.Uninstall != WixBA.Model.Command.Action) && (Display.Full == WixBA.Model.Command.Display)) + { + this.State = UpdateState.Checking; + e.Skip = false; + } + } + + private void DetectUpdate(object sender, DetectUpdateEventArgs e) + { + // The list of updates is sorted in descending version, so the first callback should be the largest update available. + // This update should be either larger than ours (so we are out of date), the same as ours (so we are current) + // or smaller than ours (we have a private build). If we really wanted to, we could leave the e.StopProcessingUpdates alone and + // enumerate all of the updates. + WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, WixBA.Model.Version)); + if (WixBA.Model.Engine.CompareVersions(e.Version, WixBA.Model.Version) > 0) + { + WixBA.Model.Engine.SetUpdate(null, e.UpdateLocation, e.Size, UpdateHashType.None, null); + this.UpdateVersion = String.Concat("v", e.Version.ToString()); + string changesFormat = @"{0}"; + this.UpdateChanges = String.Format(changesFormat, e.Content); + this.State = UpdateState.Available; + } + else + { + this.State = UpdateState.Current; + } + e.StopProcessingUpdates = true; + } + + private void DetectUpdateComplete(object sender, DetectUpdateCompleteEventArgs e) + { + // Failed to process an update, allow the existing bundle to still install. + if ((UpdateState.Failed != this.State) && !Hresult.Succeeded(e.Status)) + { + this.State = UpdateState.Failed; + WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status)); + e.IgnoreError = true; + } + // If we are uninstalling, we don't want to check or show an update + // If we are checking, then the feed didn't find any valid enclosures + // If we are initializing, we're either uninstalling or not a full UI + else if ((LaunchAction.Uninstall == WixBA.Model.Command.Action) || (UpdateState.Initializing == this.State) || (UpdateState.Checking == this.State)) + { + this.State = UpdateState.Unknown; + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/WindowProperties.cs b/src/test/burn/WixToolset.WixBA/WindowProperties.cs new file mode 100644 index 00000000..6d1e273c --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/WindowProperties.cs @@ -0,0 +1,65 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.Windows; + using System.Windows.Media; + + /// + /// Dependency Properties associated with the main Window object. + /// + public class WindowProperties : DependencyObject + { + /// + /// Dependency Property to hold the result of detecting the relative luminosity (or brightness) of a Windows background. + /// + public static readonly DependencyProperty IsLightBackgroundProperty = DependencyProperty.Register( + "IsLightBackground", typeof(bool), typeof(WindowProperties), new PropertyMetadata( false )); + + private static Lazy _instance = new Lazy(() => + { + WindowProperties wp = new WindowProperties(); + wp.CheckBackgroundBrightness(); + return wp; + }); + + public static WindowProperties Instance + { + get + { + return _instance.Value; + } + } + + + public bool IsLightBackground + { + get { return (bool)GetValue(IsLightBackgroundProperty); } + private set { SetValue(IsLightBackgroundProperty, value); } + } + + /// + /// Use the Luminosity parameter of the background color to detect light vs dark theme settings. + /// + /// + /// This approach detects both the common High Contrast themes (White vs Black) and custom themes which may have relatively lighter backgrounds. + /// + public void CheckBackgroundBrightness() + { + SolidColorBrush windowbrush = System.Windows.SystemColors.WindowBrush; + System.Drawing.Color dcolor = System.Drawing.Color.FromArgb(windowbrush.Color.A, windowbrush.Color.R, windowbrush.Color.G, windowbrush.Color.B); + + var brightness = dcolor.GetBrightness(); + // Test for 'Lightness' at an arbitrary point, approaching 1.0 (White). + if (0.7 < brightness) + { + this.IsLightBackground = true; + } + else + { + this.IsLightBackground = false; + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/WixBA.BootstrapperCore.config b/src/test/burn/WixToolset.WixBA/WixBA.BootstrapperCore.config new file mode 100644 index 00000000..da8f8028 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/WixBA.BootstrapperCore.config @@ -0,0 +1,16 @@ + + + + + + +
+ + + + + + + + + diff --git a/src/test/burn/WixToolset.WixBA/WixBA.cs b/src/test/burn/WixToolset.WixBA/WixBA.cs new file mode 100644 index 00000000..2d680c7e --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/WixBA.cs @@ -0,0 +1,229 @@ +// 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. + +namespace WixToolset.WixBA +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Net; + using System.Text; + using WixToolset.Mba.Core; + + using Threading = System.Windows.Threading; + using WinForms = System.Windows.Forms; + + /// + /// The WiX toolset bootstrapper application. + /// + public class WixBA : BootstrapperApplication + { + public WixBA(IEngine engine, IBootstrapperCommand command) + : base(engine) + { + this.Command = command; + + this.BAManifest = new BootstrapperApplicationData(); + } + + internal IBootstrapperApplicationData BAManifest { get; } + + internal IBootstrapperCommand Command { get; } + + internal IEngine Engine => this.engine; + + /// + /// Gets the global model. + /// + static public Model Model { get; private set; } + + /// + /// Gets the global view. + /// + static public RootView View { get; private set; } + // TODO: We should refactor things so we dont have a global View. + + /// + /// Gets the global dispatcher. + /// + static public Threading.Dispatcher Dispatcher { get; private set; } + + /// + /// Launches the default web browser to the provided URI. + /// + /// URI to open the web browser. + public static void LaunchUrl(string uri) + { + WixBA.UseShellExecute(uri); + } + + /// + /// Open a log file. + /// + /// URI to a log file. + internal static void OpenLog(Uri uri) + { + WixBA.UseShellExecute(uri.ToString()); + } + + /// + /// Open a log folder. + /// + /// path to a log folder. + internal static void OpenLogFolder(string logFolder) + { + WixBA.UseShellExecute(logFolder); + } + + /// + /// Open a log folder. + /// + /// path to a log folder. + private static void UseShellExecute(string path) + { + // Switch the wait cursor since shellexec can take a second or so. + System.Windows.Input.Cursor cursor = WixBA.View.Cursor; + WixBA.View.Cursor = System.Windows.Input.Cursors.Wait; + Process process = null; + try + { + process = new Process(); + process.StartInfo.FileName = path; + process.StartInfo.UseShellExecute = true; + process.StartInfo.Verb = "open"; + + process.Start(); + } + finally + { + if (null != process) + { + process.Dispose(); + } + // back to the original cursor. + WixBA.View.Cursor = cursor; + } + } + + /// + /// Starts planning the appropriate action. + /// + /// Action to plan. + public static void Plan(LaunchAction action) + { + WixBA.Model.PlannedAction = action; + WixBA.Model.Engine.Plan(WixBA.Model.PlannedAction); + } + + public static void PlanLayout() + { + // Either default or set the layout directory + if (String.IsNullOrEmpty(WixBA.Model.Command.LayoutDirectory)) + { + WixBA.Model.LayoutDirectory = Directory.GetCurrentDirectory(); + + // Ask the user for layout folder if one wasn't provided and we're in full UI mode + if (WixBA.Model.Command.Display == Display.Full) + { + WixBA.Dispatcher.Invoke((Action)delegate() + { + WinForms.FolderBrowserDialog browserDialog = new WinForms.FolderBrowserDialog(); + browserDialog.RootFolder = Environment.SpecialFolder.MyComputer; + + // Default to the current directory. + browserDialog.SelectedPath = WixBA.Model.LayoutDirectory; + WinForms.DialogResult result = browserDialog.ShowDialog(); + + if (WinForms.DialogResult.OK == result) + { + WixBA.Model.LayoutDirectory = browserDialog.SelectedPath; + WixBA.Plan(WixBA.Model.Command.Action); + } + else + { + WixBA.View.Close(); + } + } + ); + } + } + else + { + WixBA.Model.LayoutDirectory = WixBA.Model.Command.LayoutDirectory; + WixBA.Plan(WixBA.Model.Command.Action); + } + } + + /// + /// Thread entry point for WiX Toolset Bootstrapper Application. + /// + protected override void Run() + { + this.Engine.Log(LogLevel.Verbose, "Running the WiX BA."); + WixBA.Model = new Model(this); + WixBA.Dispatcher = Threading.Dispatcher.CurrentDispatcher; + RootViewModel viewModel = new RootViewModel(); + + // Kick off detect which will populate the view models. + this.Engine.Detect(); + + // Create a Window to show UI. + if (WixBA.Model.Command.Display == Display.Passive || + WixBA.Model.Command.Display == Display.Full) + { + this.Engine.Log(LogLevel.Verbose, "Creating a UI."); + WixBA.View = new RootView(viewModel); + WixBA.View.Show(); + } + + Threading.Dispatcher.Run(); + + this.PostTelemetry(); + this.Engine.Quit(WixBA.Model.Result); + } + + private void PostTelemetry() + { + string result = String.Concat("0x", WixBA.Model.Result.ToString("x")); + + StringBuilder telemetryData = new StringBuilder(); + foreach (KeyValuePair kvp in WixBA.Model.Telemetry) + { + telemetryData.AppendFormat("{0}={1}+", kvp.Key, kvp.Value); + } + telemetryData.AppendFormat("Result={0}", result); + + byte[] data = Encoding.UTF8.GetBytes(telemetryData.ToString()); + + try + { + HttpWebRequest post = WixBA.Model.CreateWebRequest(String.Format(WixDistribution.TelemetryUrlFormat, WixBA.Model.Version.ToString(), result)); + post.Method = "POST"; + post.ContentType = "application/x-www-form-urlencoded"; + post.ContentLength = data.Length; + + using (Stream postStream = post.GetRequestStream()) + { + postStream.Write(data, 0, data.Length); + } + + HttpWebResponse response = (HttpWebResponse)post.GetResponse(); + } + catch (ArgumentException) + { + } + catch (FormatException) + { + } + catch (OverflowException) + { + } + catch (ProtocolViolationException) + { + } + catch (WebException) + { + } + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/WixBAFactory.cs b/src/test/burn/WixToolset.WixBA/WixBAFactory.cs new file mode 100644 index 00000000..67fcc4b5 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/WixBAFactory.cs @@ -0,0 +1,17 @@ +// 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. + +// Identifies the class that derives from IBootstrapperApplicationFactory and is the BAFactory class that gets +// instantiated by the interop layer +[assembly: WixToolset.Mba.Core.BootstrapperApplicationFactory(typeof(WixToolset.WixBA.WixBAFactory))] +namespace WixToolset.WixBA +{ + using WixToolset.Mba.Core; + + public class WixBAFactory : BaseBootstrapperApplicationFactory + { + protected override IBootstrapperApplication Create(IEngine engine, IBootstrapperCommand command) + { + return new WixBA(engine, command); + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/WixDistribution.cs b/src/test/burn/WixToolset.WixBA/WixDistribution.cs new file mode 100644 index 00000000..0d20e585 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/WixDistribution.cs @@ -0,0 +1,118 @@ +// 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. + +using System; +using System.Diagnostics; +using System.Reflection; +using System.Resources; + +[assembly: AssemblyCompany(".NET Foundation")] +[assembly: AssemblyCopyright("Copyright (c) .NET Foundation and contributors. All rights reserved.")] +[assembly: AssemblyProduct("WiX Toolset")] + +#if DEBUG + [assembly: AssemblyConfiguration("DEBUG")] +#else + [assembly: AssemblyConfiguration("")] +#endif +[assembly: NeutralResourcesLanguage("en-US")] + +namespace WixToolset +{ + /// + /// Distribution specific strings. + /// + internal static class WixDistribution + { + /// + /// News URL for the distribution. + /// + public static string NewsUrl = "http://wixtoolset.org/news/"; + + /// + /// Short product name for the distribution. + /// + public static string ShortProduct = "WiX Toolset"; + + /// + /// Support URL for the distribution. + /// + public static string SupportUrl = "http://wixtoolset.org/"; + + /// + /// Telemetry URL format for the distribution. + /// + public static string TelemetryUrlFormat = "http://wixtoolset.org/integrationtelemetry/v{0}/?r={1}"; + + /// + /// VS Extensions Landing page Url for the distribution. + /// + public static string VSExtensionsLandingUrl = "http://wixtoolset.org/releases/"; + + public static string ReplacePlaceholders(string original, Assembly assembly) + { + if (null != assembly) + { + FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(assembly.Location); + + original = original.Replace("[FileComments]", fileVersion.Comments); + original = original.Replace("[FileCopyright]", fileVersion.LegalCopyright); + original = original.Replace("[FileProductName]", fileVersion.ProductName); + original = original.Replace("[FileVersion]", fileVersion.FileVersion); + + if (original.Contains("[FileVersionMajorMinor]")) + { + Version version = new Version(fileVersion.FileVersion); + original = original.Replace("[FileVersionMajorMinor]", String.Concat(version.Major, ".", version.Minor)); + } + + AssemblyCompanyAttribute company; + if (WixDistribution.TryGetAttribute(assembly, out company)) + { + original = original.Replace("[AssemblyCompany]", company.Company); + } + + AssemblyCopyrightAttribute copyright; + if (WixDistribution.TryGetAttribute(assembly, out copyright)) + { + original = original.Replace("[AssemblyCopyright]", copyright.Copyright); + } + + AssemblyDescriptionAttribute description; + if (WixDistribution.TryGetAttribute(assembly, out description)) + { + original = original.Replace("[AssemblyDescription]", description.Description); + } + + AssemblyProductAttribute product; + if (WixDistribution.TryGetAttribute(assembly, out product)) + { + original = original.Replace("[AssemblyProduct]", product.Product); + } + + AssemblyTitleAttribute title; + if (WixDistribution.TryGetAttribute(assembly, out title)) + { + original = original.Replace("[AssemblyTitle]", title.Title); + } + } + + original = original.Replace("[NewsUrl]", WixDistribution.NewsUrl); + original = original.Replace("[ShortProduct]", WixDistribution.ShortProduct); + original = original.Replace("[SupportUrl]", WixDistribution.SupportUrl); + return original; + } + + private static bool TryGetAttribute(Assembly assembly, out T attribute) where T : Attribute + { + attribute = null; + + object[] customAttributes = assembly.GetCustomAttributes(typeof(T), false); + if (null != customAttributes && 0 < customAttributes.Length) + { + attribute = customAttributes[0] as T; + } + + return null != attribute; + } + } +} diff --git a/src/test/burn/WixToolset.WixBA/WixToolset.WixBA.csproj b/src/test/burn/WixToolset.WixBA/WixToolset.WixBA.csproj new file mode 100644 index 00000000..72ef5795 --- /dev/null +++ b/src/test/burn/WixToolset.WixBA/WixToolset.WixBA.csproj @@ -0,0 +1,50 @@ + + + + + net45 + WixToolset.WixBA + WixToolset.WixBA + embedded + win-x86 + WixToolset.WixBA + WiX Bootstrapper Application + false + false + false + false + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + PreserveNewest + Designer + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3-55-g6feb