diff --git a/BlazamSetup/App.xaml.cs b/BlazamSetup/App.xaml.cs index b028649..ddd9e8b 100644 --- a/BlazamSetup/App.xaml.cs +++ b/BlazamSetup/App.xaml.cs @@ -14,6 +14,7 @@ using Org.BouncyCastle.Bcpg.OpenPgp; using Serilog; using Serilog.Events; +using System.Windows.Threading; namespace BlazamSetup { @@ -44,7 +45,7 @@ protected override void OnStartup(StartupEventArgs args) .Enrich.WithMachineName() .Enrich.WithEnvironmentName() .Enrich.WithEnvironmentUserName() - + .Enrich.WithProperty("Application Name","Blazam Setup") .WriteTo.File(InstallationConfiguraion.SetupTempDirectory + @"setuplog.txt", rollingInterval: RollingInterval.Infinite, @@ -55,8 +56,9 @@ protected override void OnStartup(StartupEventArgs args) //lc.WriteTo.Console(); lc.Filter.ByExcluding(e => e.Level == LogEventLevel.Information).WriteTo.Console(); }) - .WriteTo.Seq("http://logs.blazam.org:5341", apiKey: "S3JdoIIfIKcX4L3howh1", restrictedToMinimumLevel: LogEventLevel.Warning) + .WriteTo.Seq("http://logs.blazam.org:5341", apiKey: "S3JdoIIfIKcX4L3howh1", restrictedToMinimumLevel: LogEventLevel.Information) .CreateLogger(); + SetupUnhandledExceptionHandling(); StartupArgs = args; if (!Debugger.IsAttached) @@ -74,7 +76,7 @@ protected override void OnStartup(StartupEventArgs args) } - + private void SetupAppCenter() { try @@ -150,5 +152,61 @@ public static bool IsRunningAsAdministrator() // Return TRUE if user is in role "Administrator" return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator); } + + private void SetupUnhandledExceptionHandling() + { + // Catch exceptions from all threads in the AppDomain. + AppDomain.CurrentDomain.UnhandledException += (sender, args) => + ShowUnhandledException(args.ExceptionObject as Exception, "AppDomain.CurrentDomain.UnhandledException", false); + + // Catch exceptions from each AppDomain that uses a task scheduler for async operations. + TaskScheduler.UnobservedTaskException += (sender, args) => + ShowUnhandledException(args.Exception, "TaskScheduler.UnobservedTaskException", false); + + // Catch exceptions from a single specific UI dispatcher thread. + Dispatcher.UnhandledException += (sender, args) => + { + // If we are debugging, let Visual Studio handle the exception and take us to the code that threw it. + if (!Debugger.IsAttached) + { + args.Handled = true; + ShowUnhandledException(args.Exception, "Dispatcher.UnhandledException", true); + } + }; + + // Catch exceptions from the main UI dispatcher thread. + // Typically we only need to catch this OR the Dispatcher.UnhandledException. + // Handling both can result in the exception getting handled twice. + //Application.Current.DispatcherUnhandledException += (sender, args) => + //{ + // // If we are debugging, let Visual Studio handle the exception and take us to the code that threw it. + // if (!Debugger.IsAttached) + // { + // args.Handled = true; + // ShowUnhandledException(args.Exception, "Application.Current.DispatcherUnhandledException", true); + // } + //}; + } + + void ShowUnhandledException(Exception e, string unhandledExceptionType, bool promptUserForShutdown) + { + Log.Error("Uncaught Exception: {@Error}", e); + + var messageBoxTitle = $"Fatal Error!"; + var messageBoxMessage = $"We apologize for the error. A report has been sent to the developers."; + var messageBoxButtons = MessageBoxButton.OK; + + if (promptUserForShutdown) + { + messageBoxMessage += "\n\nNormally the installer would close now. Should we close it?"; + messageBoxButtons = MessageBoxButton.YesNo; + } + + // Let the user decide if the app should die or not (if applicable). + if (MessageBox.Show(messageBoxMessage, messageBoxTitle, messageBoxButtons) == MessageBoxResult.Yes) + { + Application.Current.Shutdown(); + } + } } } diff --git a/BlazamSetup/BlazamSetup.csproj b/BlazamSetup/BlazamSetup.csproj index 0d49cf5..dd59d66 100644 --- a/BlazamSetup/BlazamSetup.csproj +++ b/BlazamSetup/BlazamSetup.csproj @@ -423,6 +423,9 @@ InstallDirectory.xaml + + InstalledActionDialog.xaml + LicenseAgreement.xaml @@ -500,6 +503,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/BlazamSetup/Data/InstallationConfiguraion.cs b/BlazamSetup/Data/InstallationConfiguraion.cs index f54e3f1..7e3fa7e 100644 --- a/BlazamSetup/Data/InstallationConfiguraion.cs +++ b/BlazamSetup/Data/InstallationConfiguraion.cs @@ -9,6 +9,7 @@ namespace BlazamSetup { public enum InstallType { IIS, Service } + public enum InstalledAction { Update, Repair,Remove} public enum DBType { Sqlite, SQL, MySQL } internal static class InstallationConfiguraion { @@ -49,5 +50,7 @@ internal static string InstallDirPath public static string ProgramDataDir => Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + Path.DirectorySeparatorChar + "Blazam"; public static string SetupTempDirectory => Path.GetTempPath() + "BlazamSetup\\"; + + public static InstalledAction InstalledAction { get; internal set; } } } diff --git a/BlazamSetup/MainWindow.xaml b/BlazamSetup/MainWindow.xaml index c29ea08..9e747aa 100644 --- a/BlazamSetup/MainWindow.xaml +++ b/BlazamSetup/MainWindow.xaml @@ -16,7 +16,7 @@ MouseDown="DockPanel_MouseDown" VerticalAlignment="Top" - LastChildFill="True" Height="30" Background="Black"> + LastChildFill="True" Height="30" Background="#202747"> @@ -60,7 +60,7 @@ - + diff --git a/BlazamSetup/MainWindow.xaml.cs b/BlazamSetup/MainWindow.xaml.cs index f703009..5ed26ca 100644 --- a/BlazamSetup/MainWindow.xaml.cs +++ b/BlazamSetup/MainWindow.xaml.cs @@ -1,6 +1,8 @@ using BlazamSetup.Services; using BlazamSetup.Steps; using BlazamSetup.Steps.Uninstall; +using Microsoft.AppCenter.Ingestion.Models.Serialization; +using Serilog; using System; using System.Collections.Generic; using System.Linq; @@ -28,26 +30,44 @@ public partial class MainWindow : Window { public MainWindow() { - InitializeComponent(); - CurrentDispatcher = Dispatcher; + try + { + InitializeComponent(); + CurrentDispatcher = Dispatcher; - InstallerFrame = Frame; - LastStepButton = BackButton; - NextStepButton = NextButton; - MainWindow.InstallerFrame.ContentRendered += InstallerFrame_ContentRendered; - if (RegistryService.InstallationExists) - InstallationConfiguraion.ProductInformation = RegistryService.GetProductInformation(); + InstallerFrame = Frame; + LastStepButton = BackButton; + NextStepButton = NextButton; + MainWindow.InstallerFrame.ContentRendered += InstallerFrame_ContentRendered; + if (RegistryService.InstallationExists) + InstallationConfiguraion.ProductInformation = RegistryService.GetProductInformation(); - if (App.StartupArgs.Args.Any(arg => arg.StartsWith("/u"))){ - MainWindow.InstallerFrame.Navigate(new WelcomeUninstall()); + if (App.StartupArgs.Args.Any(arg => arg.StartsWith("/u"))) + { + Log.Information("Uninstaller Started"); + MainWindow.InstallerFrame.Navigate(new WelcomeUninstall()); - } - else - { - MainWindow.InstallerFrame.Navigate(NavigationManager.CurrentPage); + } + else + { + + Log.Information("Installer Started"); + if (RegistryService.InstallationExists) + { + MainWindow.InstallerFrame.Navigate(new InstalledActionDialog()); - } + } + else + { + MainWindow.InstallerFrame.Navigate(NavigationManager.CurrentPage); + + } + } + }catch (Exception ex) + { + Log.Error("Uncaught Exception: {@Error}", ex); + } } diff --git a/BlazamSetup/Services/DownloadService.cs b/BlazamSetup/Services/DownloadService.cs index feeaf93..b54f931 100644 --- a/BlazamSetup/Services/DownloadService.cs +++ b/BlazamSetup/Services/DownloadService.cs @@ -1,4 +1,5 @@ using Octokit; +using Serilog; using System; using System.Collections.Generic; using System.Diagnostics; @@ -15,8 +16,8 @@ namespace BlazamSetup.Services internal static class DownloadService { - public static string SetupTempDirectory = InstallationConfiguraion.SetupTempDirectory; - public static string SourceDirectory = InstallationConfiguraion.SetupTempDirectory+"setup\\"; + public static string SetupTempDirectory = Path.GetTempPath() + "BlazamSetup\\"; + public static string SourceDirectory = Path.GetTempPath() + "BlazamSetup\\setup\\"; public static string UpdateFile = SetupTempDirectory + "blazam.zip"; private static ReleaseAsset latestRelease; @@ -27,6 +28,8 @@ internal static class DownloadService public static async Task Download() { + Log.Information("Download started"); + var githubclient = new GitHubClient(new ProductHeaderValue("BLAZAM-APP")); @@ -114,47 +117,53 @@ public static async Task Download() } } - return false; + return false; } - public static void CleanDownload() + public static void CleanDownload() { - - cancellationTokenSource.Cancel(); - Task.Run(() => { - int retries =5 ; - while (retries-->0) + + cancellationTokenSource.Cancel(); + Task.Run(() => + { + int retries = 5; + while (retries-- > 0) + { + try { - try - { - File.Delete(UpdateFile); + File.Delete(UpdateFile); Directory.Delete(SetupTempDirectory, true); - retries = 0; + retries = 0; - } - catch - { - Task.Delay(50).Wait(); - } } - }); - - + catch + { + Task.Delay(50).Wait(); + } + } + }); + + } public static void CleanSource() { cancellationTokenSource.Cancel(); - Task.Run(() => { + Task.Run(() => + { int retries = 5; while (retries-- > 0) { try { + Log.Information("Cleaning old extracted files: " + SourceDirectory); + Directory.Delete(SourceDirectory, true); retries = 0; } - catch + catch (Exception ex) { + Log.Error("Error cleaning old installation files: {@Error}", ex); + Task.Delay(50).Wait(); } } @@ -162,14 +171,30 @@ public static void CleanSource() } - internal static void UnpackDownload() + internal static async Task UnpackDownload() { - CleanSource(); - Directory.CreateDirectory(SourceDirectory); - ZipArchive download = new ZipArchive(File.OpenRead(UpdateFile)); - download.ExtractToDirectory(SourceDirectory); - download.Dispose(); - File.Delete(UpdateFile); + return await Task.Run(() => + { + try + { + Log.Information("Extracting files: " + SourceDirectory); + + CleanSource(); + Directory.CreateDirectory(SourceDirectory); + ZipArchive download = new ZipArchive(File.OpenRead(UpdateFile)); + download.ExtractToDirectory(SourceDirectory); + download.Dispose(); + File.Delete(UpdateFile); + return true; + } + catch (Exception ex) + { + Log.Error("Error unpacking download: {@Error}", ex); + + } + return false; + }); + } } } diff --git a/BlazamSetup/Services/IISManageer.cs b/BlazamSetup/Services/IISManageer.cs index 159dac0..a2512dd 100644 --- a/BlazamSetup/Services/IISManageer.cs +++ b/BlazamSetup/Services/IISManageer.cs @@ -1,12 +1,12 @@ -using Microsoft.Web.Administration; +using Microsoft.AppCenter.Ingestion.Models.Serialization; +using Microsoft.Web.Administration; using Org.BouncyCastle.Bcpg.OpenPgp; +using Serilog; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.AccessControl; -using System.Text; -using System.Threading.Tasks; namespace BlazamSetup.Services { @@ -14,22 +14,36 @@ internal static class IISManager { public static bool CreateApplication() { - using (ServerManager serverManager = new ServerManager()) + try { - if (!serverManager.Sites.Any(s => s.Name == "Blazam")) + using (ServerManager serverManager = new ServerManager()) { - Site site = serverManager.Sites.Add("Blazam", - "http", - InstallationConfiguraion.WebHostConfiguration.ListeningAddress + ":" + InstallationConfiguraion.WebHostConfiguration.HttpPort + ":", - InstallationConfiguraion.InstallDirPath + @"Blazam\\"); + Log.Information("IIS Connected"); + string httpBinding = InstallationConfiguraion.WebHostConfiguration.ListeningAddress + ":" + InstallationConfiguraion.WebHostConfiguration.HttpPort + ":"; + Site site = serverManager.Sites.FirstOrDefault(s => s.Name == "Blazam"); + if (site is null) + { + site = serverManager.Sites.Add("Blazam", + "http", + httpBinding, + InstallationConfiguraion.InstallDirPath + @"Blazam\\"); + } + + Log.Information("IIS Site {@Site}", site); + serverManager.CommitChanges(); + + FileSystemService.AddPermission( + InstallationConfiguraion.InstallDirPath + @"Blazam\\", + "IIS_IUSRS", + FileSystemRights.ReadAndExecute + ); + return true; } - FileSystemService.AddPermission( - InstallationConfiguraion.InstallDirPath + @"Blazam\\", - "IIS_IUSRS", - FileSystemRights.ReadAndExecute - ); - return true; + } + catch ( Exception ex ) + { + Log.Error("Error while creating IIS website. {@Error}", ex); } return false; } diff --git a/BlazamSetup/Services/InstallationService.cs b/BlazamSetup/Services/InstallationService.cs index fbf75d1..5714911 100644 --- a/BlazamSetup/Services/InstallationService.cs +++ b/BlazamSetup/Services/InstallationService.cs @@ -1,4 +1,6 @@ -using System; +using Serilog; +using SQLitePCL; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -14,21 +16,34 @@ internal static class InstallationService internal static AppEvent OnProgress { get; set; } internal static AppEvent OnStepTitleChanged { get; set; } internal static AppEvent OnInstallationFinished { get; set; } - internal static CancellationTokenSource CancellationTokenSource { get; set; }= new CancellationTokenSource(); + internal static CancellationTokenSource CancellationTokenSource { get; set; } = new CancellationTokenSource(); - internal static async Task StartInstallationAsync() { + internal static async Task StartInstallationAsync() + { + Log.Information("Installattion Started {@InstallationType} {@InstallDirPath} {@DatabaseConfiguration}", InstallationConfiguraion.InstallationType, InstallationConfiguraion.InstallDirPath, InstallationConfiguraion.DatabaseConfiguration); + + if (!await PreInstallation()) Rollback(); + if (CancellationTokenSource.IsCancellationRequested) return; + if (!await CopySourceFiles(InstallationConfiguraion.InstallDirPath + "\\Blazam\\")) Rollback(); + if (CancellationTokenSource.IsCancellationRequested) return; await Task.Run(() => { - PreInstallation(); - CopySourceFiles(InstallationConfiguraion.InstallDirPath + "\\Blazam\\"); - CreateProgramDataDirectory(); + if (CancellationTokenSource.IsCancellationRequested) return; + + if (!CreateProgramDataDirectory()) Rollback(); + if (CancellationTokenSource.IsCancellationRequested) return; + + if (InstallationConfiguraion.InstallationType == InstallType.Service && !ServiceManager.IsInstalled) { OnStepTitleChanged?.Invoke("Install Services"); OnProgress?.Invoke(0); - ServiceManager.Install(); + if (!ServiceManager.Install()) Rollback(); + if (CancellationTokenSource.IsCancellationRequested) return; + + OnProgress?.Invoke(100); } else @@ -36,37 +51,56 @@ await Task.Run(() => OnStepTitleChanged?.Invoke("Configuring IIS"); OnProgress?.Invoke(0); - IISManager.CreateApplication(); + if (!IISManager.CreateApplication()) Rollback(); + if (CancellationTokenSource.IsCancellationRequested) return; + OnProgress?.Invoke(100); } OnStepTitleChanged?.Invoke("Finishing Installation"); OnProgress?.Invoke(0); + if (CancellationTokenSource.IsCancellationRequested) return; + //Post install steps AppSettingsService.Copy(); AppSettingsService.Configure(); - RegistryService.CreateUninstallKey(); RegistryService.SetProductInformation(InstallationConfiguraion.ProductInformation); OnProgress?.Invoke(100); OnStepTitleChanged?.Invoke("Installation Finished"); + Log.Information("Installation Finished Succeessfully"); + MainWindow.DisableBack(); MainWindow.EnableNext(); }); } - private static void CreateProgramDataDirectory() + private static void Rollback() { - string identity = "IIS_IUSRS"; - if (InstallationConfiguraion.InstallationType == InstallType.Service) - identity = "NT Authority/NetworkService"; - - Directory.CreateDirectory(InstallationConfiguraion.ProgramDataDir); - FileSystemService.AddPermission( - InstallationConfiguraion.ProgramDataDir, - identity, - System.Security.AccessControl.FileSystemRights.Write | System.Security.AccessControl.FileSystemRights.Modify | System.Security.AccessControl.FileSystemRights.ReadAndExecute - ); + Cancel(); + } + + private static bool CreateProgramDataDirectory() + { + try + { + string identity = "IIS_IUSRS"; + if (InstallationConfiguraion.InstallationType == InstallType.Service) + identity = "NT Authority/NetworkService"; + + Directory.CreateDirectory(InstallationConfiguraion.ProgramDataDir); + FileSystemService.AddPermission( + InstallationConfiguraion.ProgramDataDir, + identity, + System.Security.AccessControl.FileSystemRights.Write | System.Security.AccessControl.FileSystemRights.Modify | System.Security.AccessControl.FileSystemRights.ReadAndExecute + ); + return true; + }catch (Exception ex) + { + Log.Error("Error creating program data directory: {@Error}", ex); + + } + return false; } @@ -75,68 +109,83 @@ private static void CreateProgramDataDirectory() /// /// /// - public static bool CopySourceFiles(string targetDirectory) + public static async Task CopySourceFiles(string targetDirectory) { - try + return await Task.Run(() => { - OnStepTitleChanged?.Invoke("Copy Files"); - bool copyingDownTree = false; - if (targetDirectory.Contains(DownloadService.SourceDirectory)) + try { - copyingDownTree = true; - } - var totalFiles = FileSystemService.GetFileCount(DownloadService.SourceDirectory); - var fileIndex = 0; - - if (Directory.Exists(DownloadService.SetupTempDirectory)) - { - var directories = Directory.GetDirectories(DownloadService.SourceDirectory, "*", SearchOption.AllDirectories).AsEnumerable(); - - if (copyingDownTree) - directories = directories.Where(d => !d.Contains(targetDirectory)); + OnStepTitleChanged?.Invoke("Copy Files"); + Log.Information("File copy started"); - //Now Create all of the directories - foreach (string dirPath in directories) + bool copyingDownTree = false; + if (targetDirectory.Contains(DownloadService.SourceDirectory)) { - Directory.CreateDirectory(dirPath.Replace(DownloadService.SourceDirectory, targetDirectory)); + copyingDownTree = true; } - var files = Directory.GetFiles(DownloadService.SourceDirectory, "*.*", SearchOption.AllDirectories).AsEnumerable(); + var totalFiles = FileSystemService.GetFileCount(DownloadService.SourceDirectory); + var fileIndex = 0; - if (copyingDownTree) - files = files.Where(f => !f.Contains(targetDirectory)); - //Copy all the files & Replaces any files with the same name - foreach (string newPath in files) + if (Directory.Exists(DownloadService.SetupTempDirectory)) { - File.Copy(newPath, newPath.Replace(DownloadService.SourceDirectory, targetDirectory), true); - fileIndex++; - OnProgress?.Invoke((fileIndex / totalFiles) * 100); + var directories = Directory.GetDirectories(DownloadService.SourceDirectory, "*", SearchOption.AllDirectories).AsEnumerable(); + + if (copyingDownTree) + directories = directories.Where(d => !d.Contains(targetDirectory)); + + //Now Create all of the directories + foreach (string dirPath in directories) + { + Log.Information("Creating directory: "+dirPath); + + Directory.CreateDirectory(dirPath.Replace(DownloadService.SourceDirectory, targetDirectory)); + } + var files = Directory.GetFiles(DownloadService.SourceDirectory, "*.*", SearchOption.AllDirectories).AsEnumerable(); + + if (copyingDownTree) + files = files.Where(f => !f.Contains(targetDirectory)); + //Copy all the files & Replaces any files with the same name + foreach (string path in files) + { + var newPath = path.Replace(DownloadService.SourceDirectory, targetDirectory); + Log.Information("Copying file: " + newPath); + + File.Copy(path, newPath, true); + fileIndex++; + OnProgress?.Invoke((fileIndex / totalFiles) * 100); + } + CopySetup(targetDirectory); + return true; + } - CopySetup(targetDirectory); - return true; + } + catch (Exception ex) + { + Log.Error("Error Copying files {@Error}",ex); + Console.WriteLine(ex.Message); } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - return false; + return false; + }); + } private static void CopySetup(string targetDirectory) { var setupPath = Assembly.GetExecutingAssembly().Location; var destPath = targetDirectory + "setup.exe"; + Log.Information("Copying file: " + destPath); + File.Copy(setupPath, destPath, true); } - private static void PreInstallation() + private static async Task PreInstallation() { OnStepTitleChanged?.Invoke("Extract Files"); - DownloadService.UnpackDownload(); - return; + return await DownloadService.UnpackDownload(); + } @@ -144,6 +193,8 @@ internal static void Cancel() { if (!CancellationTokenSource.IsCancellationRequested) { + Log.Information("Cancelling"); + CancellationTokenSource.Cancel(); } } diff --git a/BlazamSetup/Services/RegistryService.cs b/BlazamSetup/Services/RegistryService.cs index 7d52808..5fedb1e 100644 --- a/BlazamSetup/Services/RegistryService.cs +++ b/BlazamSetup/Services/RegistryService.cs @@ -49,6 +49,8 @@ internal static bool SetProductInformation(ProductInformation productInformation { try { + RegistryService.CreateUninstallKey(); + var key = OpenKey(true); foreach (var property in typeof(ProductInformation).GetProperties()) { diff --git a/BlazamSetup/Steps/InstalledActionDialog.xaml b/BlazamSetup/Steps/InstalledActionDialog.xaml new file mode 100644 index 0000000..58c8123 --- /dev/null +++ b/BlazamSetup/Steps/InstalledActionDialog.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + diff --git a/BlazamSetup/Steps/InstalledActionDialog.xaml.cs b/BlazamSetup/Steps/InstalledActionDialog.xaml.cs new file mode 100644 index 0000000..87a9070 --- /dev/null +++ b/BlazamSetup/Steps/InstalledActionDialog.xaml.cs @@ -0,0 +1,57 @@ +using BlazamSetup.Steps.Uninstall; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace BlazamSetup.Steps +{ + /// + /// Interaction logic for InstalledActionDialog.xaml + /// + public partial class InstalledActionDialog : UserControl,IInstallationStep + { + + public InstalledActionDialog() + { + InitializeComponent(); + } + + private void Update_Checked(object sender, RoutedEventArgs e) + { + InstallationConfiguraion.InstalledAction = InstalledAction.Update; + } + + private void Repair_Checked(object sender, RoutedEventArgs e) + { + InstallationConfiguraion.InstalledAction = InstalledAction.Repair; + + } + + private void Remove_Checked(object sender, RoutedEventArgs e) + { + + InstallationConfiguraion.InstalledAction = InstalledAction.Remove; + } + + IInstallationStep IInstallationStep.NextStep() + { + switch(InstallationConfiguraion.InstalledAction) + { + case InstalledAction.Remove: + default: + return new WelcomeUninstall(); + } + } + } +}