diff --git a/InstallerGui/App.xaml b/InstallerGui/App.xaml
deleted file mode 100644
index f28634d7..00000000
--- a/InstallerGui/App.xaml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/InstallerGui/App.xaml.cs b/InstallerGui/App.xaml.cs
deleted file mode 100644
index 6ccc1a5a..00000000
--- a/InstallerGui/App.xaml.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2026 Erik Darling, Darling Data LLC
- *
- * This file is part of the SQL Server Performance Monitor.
- *
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
-
-using System;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Threading;
-using PerformanceMonitorInstallerGui.Utilities;
-
-namespace PerformanceMonitorInstallerGui
-{
- public partial class App : Application
- {
- protected override void OnStartup(StartupEventArgs e)
- {
- base.OnStartup(e);
-
- Logger.LogToFile("App.OnStartup", "Application starting...");
-
- /*
- Register global exception handlers
- */
- AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
- DispatcherUnhandledException += OnDispatcherUnhandledException;
- TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
- }
-
- private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
- {
- var exception = e.ExceptionObject as Exception;
- if (exception != null)
- {
- Logger.LogToFile("OnUnhandledException", exception);
- }
-
- if (e.IsTerminating)
- {
- MessageBox.Show(
- $"A fatal error occurred and the application must close.\n\n" +
- $"Error: {exception?.Message}\n\n" +
- $"Inner: {exception?.InnerException?.Message}\n\n" +
- $"Log file: {Logger.LogFilePath}",
- "Fatal Error",
- MessageBoxButton.OK,
- MessageBoxImage.Error);
- }
- }
-
- private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
- {
- Logger.LogToFile("OnDispatcherUnhandledException", e.Exception);
-
- e.Handled = true;
-
- MessageBox.Show(
- $"An error occurred: {e.Exception.Message}\n\n" +
- $"Inner: {e.Exception.InnerException?.Message}\n\n" +
- $"Log file: {Logger.LogFilePath}",
- "Error",
- MessageBoxButton.OK,
- MessageBoxImage.Error);
- }
-
- private void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
- {
- if (e.Exception != null)
- {
- Logger.LogToFile("OnUnobservedTaskException", e.Exception);
- }
-
- e.SetObserved();
-
- Dispatcher.Invoke(() =>
- {
- MessageBox.Show(
- $"A background task error occurred: {e.Exception?.InnerException?.Message ?? e.Exception?.Message}\n\n" +
- $"Log file: {Logger.LogFilePath}",
- "Background Error",
- MessageBoxButton.OK,
- MessageBoxImage.Warning);
- });
- }
- }
-}
diff --git a/InstallerGui/EDD.ico b/InstallerGui/EDD.ico
deleted file mode 100644
index a7f07aa3..00000000
Binary files a/InstallerGui/EDD.ico and /dev/null differ
diff --git a/InstallerGui/InstallerGui.csproj b/InstallerGui/InstallerGui.csproj
deleted file mode 100644
index 6e6a58ef..00000000
--- a/InstallerGui/InstallerGui.csproj
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
- WinExe
- net8.0-windows
- enable
- true
- PerformanceMonitorInstallerGui
- PerformanceMonitorInstallerGui
- SQL Server Performance Monitor Installer
- 2.4.1
- 2.4.1.0
- 2.4.1.0
- 2.4.1
- Darling Data, LLC
- Copyright © 2026 Darling Data, LLC
- EDD.ico
- win-x64
- true
- true
- true
- false
- true
- true
- latest-recommended
- CA1849;CA2007;CA1508;CA1031;CA1001;CA1822;CA1305;CA2100;CA1002;CA1845;CA1861;CA2234;CA1062;CA1823
-
-
-
-
-
-
-
-
-
-
-
-
-
- PreserveNewest
-
-
-
-
diff --git a/InstallerGui/MainWindow.xaml b/InstallerGui/MainWindow.xaml
deleted file mode 100644
index 1e428e38..00000000
--- a/InstallerGui/MainWindow.xaml
+++ /dev/null
@@ -1,282 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/InstallerGui/MainWindow.xaml.cs b/InstallerGui/MainWindow.xaml.cs
deleted file mode 100644
index 1f532c89..00000000
--- a/InstallerGui/MainWindow.xaml.cs
+++ /dev/null
@@ -1,896 +0,0 @@
-/*
- * Copyright (c) 2026 Erik Darling, Darling Data LLC
- *
- * This file is part of the SQL Server Performance Monitor.
- *
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Documents;
-using System.Windows.Media;
-using Installer.Core;
-using Installer.Core.Models;
-using PerformanceMonitorInstallerGui.Utilities;
-
-namespace PerformanceMonitorInstallerGui
-{
- public partial class MainWindow : Window
- {
- private readonly DependencyInstaller _dependencyInstaller;
- private CancellationTokenSource? _cancellationTokenSource;
- private string? _connectionString;
- private ScriptProvider? _scriptProvider;
- private ServerInfo? _serverInfo;
- private InstallationResult? _installationResult;
- private string? _installedVersion;
-
- /*
- UI brushes for log output
- */
- private static readonly SolidColorBrush SuccessBrush = new(Color.FromRgb(0x4C, 0xAF, 0x50)); // Green
- private static readonly SolidColorBrush ErrorBrush = new(Color.FromRgb(0xF4, 0x43, 0x36)); // Red
- private static readonly SolidColorBrush WarningBrush = new(Color.FromRgb(0xFF, 0xC1, 0x07)); // Yellow
-
- /*
- Cached version strings
- Display version includes git hash suffix for UI/logs
- Assembly version is clean (e.g. "2.0.0.0") for upgrade version comparison
- */
- private static readonly string AppVersion =
- Assembly.GetExecutingAssembly()
- .GetCustomAttribute()?.InformationalVersion
- ?? Assembly.GetExecutingAssembly().GetName().Version?.ToString()
- ?? "Unknown";
-
- private static readonly string AppAssemblyVersion =
- Assembly.GetExecutingAssembly().GetName().Version?.ToString()
- ?? "Unknown";
-
- private static readonly char[] NewLineChars = { '\r', '\n' };
-
- public MainWindow()
- {
- try
- {
- InitializeComponent();
- string communityDir = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "community");
- _dependencyInstaller = new DependencyInstaller(communityDir);
-
- /*Set window title with version*/
- Title = $"Performance Monitor Installer v{AppVersion}";
-
- /*Find installation files after window loads*/
- Loaded += MainWindow_Loaded;
- }
- catch (Exception ex)
- {
- Logger.LogToFile("MainWindow Constructor", ex);
- MessageBox.Show(
- $"Constructor error:\n\n{ex.GetType().Name}: {ex.Message}\n\n" +
- $"Inner: {ex.InnerException?.Message}\n\n" +
- $"Log file: {Logger.LogFilePath}",
- "Constructor Error",
- MessageBoxButton.OK,
- MessageBoxImage.Error);
- throw;
- }
- }
-
- private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
- {
- try
- {
- FindInstallationFiles();
- await CheckForInstallerUpdateAsync();
- }
- catch (Exception ex)
- {
- Logger.LogToFile("MainWindow_Loaded", ex);
- MessageBox.Show(this,
- $"Loaded error:\n\n{ex.GetType().Name}: {ex.Message}\n\n" +
- $"Inner: {ex.InnerException?.Message}\n\n" +
- $"Log file: {Logger.LogFilePath}",
- "Loaded Error",
- MessageBoxButton.OK,
- MessageBoxImage.Error);
- }
- }
-
- ///
- /// Find SQL installation files on startup
- ///
- private void FindInstallationFiles()
- {
- _scriptProvider = ScriptProvider.AutoDiscover();
- var scriptFiles = _scriptProvider.GetInstallFiles();
-
- if (scriptFiles.Count > 0)
- {
- LogMessage($"Found {scriptFiles.Count} SQL installation files", "Info");
- }
- else
- {
- LogMessage("WARNING: No SQL installation files found.", "Warning");
- LogMessage("Make sure the installer is in the Monitor directory or a subdirectory.", "Warning");
- _scriptProvider = null;
- InstallButton.IsEnabled = false;
-
- MessageBox.Show(this,
- "No SQL installation files found.\n\nMake sure the installer is located in the Monitor directory or a subdirectory.",
- "Files Not Found",
- MessageBoxButton.OK,
- MessageBoxImage.Warning);
- }
- }
-
- ///
- /// Handle authentication type change
- ///
- private void AuthType_Changed(object sender, RoutedEventArgs e)
- {
- /*Controls may not exist yet during InitializeComponent*/
- if (UsernameTextBox == null || PasswordBox == null)
- return;
-
- bool useSqlAuth = SqlAuthRadio.IsChecked == true;
- bool useEntraAuth = EntraAuthRadio.IsChecked == true;
-
- UsernameTextBox.IsEnabled = useSqlAuth || useEntraAuth;
- PasswordBox.IsEnabled = useSqlAuth;
- UsernameLabel.Text = useEntraAuth ? "Email:" : "Username:";
- UsernameLabel.Opacity = (useSqlAuth || useEntraAuth) ? 1.0 : 0.5;
- PasswordLabel.Opacity = useSqlAuth ? 1.0 : 0.5;
-
- InvalidateConnection();
- }
-
- ///
- /// Invalidate cached connection when any connection parameter changes
- ///
- private void ConnectionParameter_Changed(object sender, EventArgs e)
- {
- InvalidateConnection();
- }
-
- ///
- /// Clear cached connection state so the user must re-test before installing
- ///
- private void InvalidateConnection()
- {
- /*Controls may not exist yet during InitializeComponent*/
- if (InstallButton == null)
- return;
-
- _connectionString = null;
- _serverInfo = null;
- _installedVersion = null;
- InstallButton.IsEnabled = false;
- UninstallButton.IsEnabled = false;
- }
-
- ///
- /// Test connection button click
- ///
- private async void TestConnection_Click(object sender, RoutedEventArgs e)
- {
- Logger.LogToFile("TestConnection_Click", "Method started");
-
- string server = ServerTextBox.Text.Trim();
- if (string.IsNullOrEmpty(server))
- {
- MessageBox.Show(this, "Please enter a server name.", "Validation Error",
- MessageBoxButton.OK, MessageBoxImage.Warning);
- return;
- }
-
- bool useWindowsAuth = WindowsAuthRadio.IsChecked == true;
- bool useEntraAuth = EntraAuthRadio.IsChecked == true;
- string? username = (useWindowsAuth) ? null : UsernameTextBox.Text.Trim();
- string? password = (useWindowsAuth || useEntraAuth) ? null : PasswordBox.Password;
-
- if (useEntraAuth)
- {
- if (string.IsNullOrEmpty(username))
- {
- MessageBox.Show(this, "Please enter an email address for Entra ID authentication.",
- "Validation Error", MessageBoxButton.OK, MessageBoxImage.Warning);
- return;
- }
- }
- else if (!useWindowsAuth)
- {
- if (string.IsNullOrEmpty(username))
- {
- MessageBox.Show(this, "Please enter a username for SQL Server Authentication.",
- "Validation Error", MessageBoxButton.OK, MessageBoxImage.Warning);
- return;
- }
- if (string.IsNullOrEmpty(password))
- {
- MessageBox.Show(this, "Please enter a password for SQL Server Authentication.",
- "Validation Error", MessageBoxButton.OK, MessageBoxImage.Warning);
- return;
- }
- }
-
- TestConnectionButton.IsEnabled = false;
- TestConnectionButton.Content = "Testing...";
-
- try
- {
- Logger.LogToFile("TestConnection_Click", "Entering try block");
-
- string encryption = (EncryptionComboBox.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString() ?? "Mandatory";
- bool trustCertificate = TrustCertificateCheckBox.IsChecked ?? false;
-
- Logger.LogToFile("TestConnection_Click", $"Encryption: {encryption}, TrustCert: {trustCertificate}");
-
- _connectionString = InstallationService.BuildConnectionString(server, useWindowsAuth, username, password, encryption, trustCertificate, useEntraAuth);
-
- Logger.LogToFile("TestConnection_Click", "Connection string built, clearing log");
-
- ClearLog();
- LogMessage("Testing connection...", "Info");
-
- Logger.LogToFile("TestConnection_Click", "Calling TestConnectionAsync");
-
- _serverInfo = await InstallationService.TestConnectionAsync(_connectionString);
-
- Logger.LogToFile("TestConnection_Click", $"TestConnectionAsync returned, IsConnected: {_serverInfo?.IsConnected}");
-
- if (_serverInfo == null)
- {
- LogMessage("Connection test returned no result", "Error");
- InstallButton.IsEnabled = false;
- return;
- }
-
- if (_serverInfo.IsConnected)
- {
- LogMessage("Connection successful!", "Success");
- LogMessage($"Server: {_serverInfo.ServerName}", "Info");
- LogMessage($"Edition: {_serverInfo.SqlServerEdition}", "Info");
-
- /*Show first line of version*/
- string[] versionLines = _serverInfo.SqlServerVersion.Split(NewLineChars,
- StringSplitOptions.RemoveEmptyEntries);
- if (versionLines.Length > 0)
- {
- LogMessage($"Version: {versionLines[0]}", "Info");
- }
-
- /*Check minimum SQL Server version (2016+ required for on-prem)*/
- if (!_serverInfo.IsSupportedVersion)
- {
- LogMessage($"{_serverInfo.ProductMajorVersionName} is not supported. SQL Server 2016 or later is required.", "Error");
- InstallButton.IsEnabled = false;
- MessageBox.Show(this,
- $"{_serverInfo.ProductMajorVersionName} is not supported.\n\n" +
- $"Performance Monitor requires SQL Server 2016 (13.x) or later.\n" +
- $"Server: {_serverInfo.ServerName}",
- "Unsupported SQL Server Version",
- MessageBoxButton.OK,
- MessageBoxImage.Error);
- return;
- }
-
- /*Check for installed version*/
- _installedVersion = await InstallationService.GetInstalledVersionAsync(_connectionString);
- if (_installedVersion != null)
- {
- LogMessage($"Installed version: {_installedVersion}", "Info");
-
- /*Check for applicable upgrades*/
- if (_scriptProvider != null)
- {
- var upgrades = _scriptProvider.GetApplicableUpgrades(
- _installedVersion,
- AppAssemblyVersion);
- if (upgrades.Count > 0)
- {
- LogMessage($"Found {upgrades.Count} upgrade(s) to apply", "Warning");
- foreach (var upgrade in upgrades)
- {
- LogMessage($" - {upgrade.FolderName}", "Info");
- }
- }
- }
- }
- else
- {
- LogMessage("No existing installation detected (clean install)", "Info");
- }
-
- InstallButton.IsEnabled = _scriptProvider != null;
- UninstallButton.IsEnabled = _installedVersion != null;
-
- /*Show confirmation MessageBox*/
- string installedVersionText = _installedVersion != null
- ? $"Installed: {_installedVersion}"
- : "No existing installation";
- MessageBox.Show(this,
- $"Connection successful!\n\n" +
- $"Server: {_serverInfo.ServerName}\n" +
- $"Edition: {_serverInfo.SqlServerEdition}\n" +
- $"Version: {(versionLines.Length > 0 ? versionLines[0] : _serverInfo.SqlServerVersion)}\n\n" +
- $"{installedVersionText}",
- "Connection Test",
- MessageBoxButton.OK,
- MessageBoxImage.Information);
- }
- else
- {
- LogMessage($"Connection failed: {_serverInfo.ErrorMessage}", "Error");
- InstallButton.IsEnabled = false;
- }
- }
- catch (Exception ex)
- {
- Logger.LogToFile("TestConnection_Click", ex);
- LogMessage($"Error: {ex.Message}", "Error");
- MessageBox.Show(this, $"Connection test error:\n\n{ex.Message}\n\nSee log file for details:\n{Logger.LogFilePath}",
- "Error", MessageBoxButton.OK, MessageBoxImage.Error);
- }
- finally
- {
- TestConnectionButton.IsEnabled = true;
- TestConnectionButton.Content = "Test Connection";
- }
- }
-
- ///
- /// Install button click
- ///
- private async void Install_Click(object sender, RoutedEventArgs e)
- {
- if (_connectionString == null || _scriptProvider == null)
- {
- MessageBox.Show(this, "Please test the connection first.", "Validation Error",
- MessageBoxButton.OK, MessageBoxImage.Warning);
- return;
- }
-
- /*Confirm clean install*/
- if (CleanInstallCheckBox.IsChecked == true)
- {
- var result = MessageBox.Show(this,
- "WARNING: Clean install will DROP the existing PerformanceMonitor database and ALL collected data.\n\n" +
- "This action CANNOT be undone!\n\n" +
- "Are you sure you want to continue?",
- "Confirm Clean Install",
- MessageBoxButton.YesNo,
- MessageBoxImage.Warning,
- MessageBoxResult.No);
-
- if (result != MessageBoxResult.Yes)
- {
- return;
- }
- }
-
- /*Dispose previous token source if it exists*/
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = new CancellationTokenSource();
- var cancellationToken = _cancellationTokenSource.Token;
-
- SetUIState(installing: true);
- ClearLog();
-
- /*Capture log messages for the report*/
- var logMessages = new List<(string Message, string Status)>();
-
- LogMessage($"Performance Monitor Installer v{AppVersion}", "Info");
- logMessages.Add(($"Performance Monitor Installer v{AppVersion}", "Info"));
- LogMessage($"Copyright (c) {DateTime.Now.Year} Darling Data, LLC", "Info");
- logMessages.Add(($"Copyright (c) {DateTime.Now.Year} Darling Data, LLC", "Info"));
- LogMessage("", "Info");
-
- var progress = new Progress(p =>
- {
- ReportProgress(p);
- logMessages.Add((p.Message, p.Status));
- });
-
- try
- {
- /*
- Execute upgrades if applicable (only when not doing clean install)
- */
- bool isCleanInstall = CleanInstallCheckBox.IsChecked == true;
- if (!isCleanInstall && _installedVersion != null && _scriptProvider != null)
- {
- var (upgradeSuccess, upgradeFailure, upgradeCount) = await InstallationService.ExecuteAllUpgradesAsync(
- _scriptProvider,
- _connectionString,
- _installedVersion,
- AppAssemblyVersion,
- progress,
- cancellationToken);
-
- if (upgradeCount > 0)
- {
- LogMessage("", "Info");
- LogMessage($"Upgrades completed: {upgradeSuccess} scripts succeeded, {upgradeFailure} failed",
- upgradeFailure == 0 ? "Success" : "Warning");
- LogMessage("", "Info");
- }
-
- /*Abort if any upgrade scripts failed — proceeding would reinstall over a partially-upgraded database*/
- if (upgradeFailure > 0)
- {
- LogMessage("", "Info");
- LogMessage("Installation aborted: upgrade scripts must succeed before installation can proceed.", "Error");
- LogMessage("Fix the errors above and re-run the installer.", "Error");
- SetUIState(installing: false);
- return;
- }
- }
-
- /*
- Execute installation
- Community dependencies install automatically before validation (98_validate)
- */
- bool resetSchedule = ResetScheduleCheckBox.IsChecked == true;
- _installationResult = await InstallationService.ExecuteInstallationAsync(
- _connectionString,
- _scriptProvider,
- isCleanInstall,
- resetSchedule,
- progress,
- preValidationAction: async () =>
- {
- await _dependencyInstaller.InstallDependenciesAsync(
- _connectionString,
- progress,
- cancellationToken);
- },
- cancellationToken);
-
- /*
- Log installation history to database
- */
- try
- {
- await InstallationService.LogInstallationHistoryAsync(
- _connectionString,
- AppAssemblyVersion,
- AppVersion,
- _installationResult.StartTime,
- _installationResult.FilesSucceeded,
- _installationResult.FilesFailed,
- _installationResult.Success);
- }
- catch (Exception ex)
- {
- LogMessage($"Warning: Could not log installation history: {ex.Message}", "Warning");
- }
-
- /*
- Run validation if requested
- */
- if (_installationResult.Success && ValidationCheckBox.IsChecked == true)
- {
- LogMessage("Running validation (this may take a moment)...", "Info");
- var (succeeded, failed) = await InstallationService.RunValidationAsync(
- _connectionString,
- progress,
- cancellationToken);
- }
-
- /*
- Generate summary report
- */
- if (_serverInfo != null)
- {
- /*Copy captured log messages to the result for the report*/
- foreach (var (message, status) in logMessages)
- {
- _installationResult.LogMessages.Add((message, status));
- }
-
- _installationResult.ReportPath = InstallationService.GenerateSummaryReport(
- _serverInfo.ServerName,
- _serverInfo.SqlServerVersion,
- _serverInfo.SqlServerEdition,
- AppVersion,
- _installationResult);
-
- LogMessage("", "Info");
- LogMessage($"Installation report saved to: {_installationResult.ReportPath}", "Info");
- }
-
- /*
- Final summary
- */
- LogMessage("", "Info");
- LogMessage("================================================================================", "Info");
- LogMessage("Installation Summary", "Info");
- LogMessage("================================================================================", "Info");
-
- if (_installationResult.Success)
- {
- _installedVersion = AppAssemblyVersion;
-
- LogMessage("Installation completed successfully!", "Success");
- LogMessage("", "Info");
- LogMessage("NEXT STEPS:", "Info");
- LogMessage("1. Ensure SQL Server Agent service is running", "Info");
- LogMessage("2. Verify: SELECT * FROM PerformanceMonitor.report.collection_health;", "Info");
- LogMessage("3. Monitor job history in SQL Server Agent", "Info");
-
- TroubleshootButton.IsEnabled = true;
- ViewReportButton.IsEnabled = _installationResult?.ReportPath != null;
- }
- else
- {
- LogMessage($"Installation completed with {_installationResult.FilesFailed} error(s).", "Warning");
- LogMessage("Review errors above for details.", "Warning");
-
- ViewReportButton.IsEnabled = _installationResult?.ReportPath != null;
- }
-
- ProgressBar.Value = 100;
- ProgressText.Text = "100%";
- }
- catch (OperationCanceledException)
- {
- LogMessage("", "Info");
- LogMessage("Installation cancelled by user.", "Warning");
- }
- catch (Exception ex)
- {
- LogMessage("", "Info");
- LogMessage($"Installation failed: {ex.Message}", "Error");
- }
- finally
- {
- SetUIState(installing: false);
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
- }
-
- /*
- Show completion dialog after re-enabling UI so the
- dialog cannot block SetUIState(false) in the finally block.
- */
- if (_installationResult is not null)
- {
- if (_installationResult.Success)
- {
- MessageBox.Show(this,
- $"Installation completed successfully!\n\n" +
- $"{_installationResult.FilesSucceeded} script(s) executed without errors.",
- "Installation Complete",
- MessageBoxButton.OK,
- MessageBoxImage.Information);
- }
- else
- {
- MessageBox.Show(this,
- $"Installation completed with errors.\n\n" +
- $"Succeeded: {_installationResult.FilesSucceeded}\n" +
- $"Failed: {_installationResult.FilesFailed}\n\n" +
- $"Review the log for details.",
- "Installation Complete",
- MessageBoxButton.OK,
- MessageBoxImage.Warning);
- }
- }
- }
-
- ///
- /// Uninstall button click
- ///
- private async void Uninstall_Click(object sender, RoutedEventArgs e)
- {
- if (_connectionString == null || _installedVersion == null)
- {
- MessageBox.Show(this, "No installation detected.", "Info",
- MessageBoxButton.OK, MessageBoxImage.Information);
- return;
- }
-
- var result = MessageBox.Show(this,
- "WARNING: This will permanently remove the PerformanceMonitor database,\n" +
- "all SQL Agent jobs, Extended Events sessions, and ALL collected data.\n\n" +
- "This action CANNOT be undone!\n\n" +
- $"Installed version: {_installedVersion}\n\n" +
- "Are you sure you want to continue?",
- "Confirm Uninstall",
- MessageBoxButton.YesNo,
- MessageBoxImage.Warning,
- MessageBoxResult.No);
-
- if (result != MessageBoxResult.Yes)
- {
- return;
- }
-
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = new CancellationTokenSource();
- var cancellationToken = _cancellationTokenSource.Token;
-
- SetUIState(installing: true);
- ClearLog();
-
- LogMessage($"Performance Monitor Uninstaller v{AppVersion}", "Info");
- LogMessage("", "Info");
-
- var progress = new Progress(ReportProgress);
-
- try
- {
- bool success = await InstallationService.ExecuteUninstallAsync(
- _connectionString,
- progress,
- cancellationToken);
-
- if (success)
- {
- LogMessage("", "Info");
- LogMessage("================================================================================", "Info");
- LogMessage("Uninstall completed successfully!", "Success");
- LogMessage("================================================================================", "Info");
- LogMessage("", "Info");
- LogMessage("Note: blocked process threshold (s) was NOT reset.", "Info");
-
- _installedVersion = null;
- ProgressBar.Value = 100;
- ProgressText.Text = "100%";
-
- MessageBox.Show(this,
- "Uninstall completed successfully!\n\n" +
- "Database, Agent jobs, and XE sessions have been removed.",
- "Uninstall Complete",
- MessageBoxButton.OK,
- MessageBoxImage.Information);
- }
- }
- catch (OperationCanceledException)
- {
- LogMessage("", "Info");
- LogMessage("Uninstall cancelled by user.", "Warning");
- }
- catch (Exception ex)
- {
- LogMessage("", "Info");
- LogMessage($"Uninstall failed: {ex.Message}", "Error");
-
- MessageBox.Show(this,
- $"Uninstall failed:\n\n{ex.Message}",
- "Uninstall Error",
- MessageBoxButton.OK,
- MessageBoxImage.Error);
- }
- finally
- {
- SetUIState(installing: false);
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
- }
- }
-
- ///
- /// Troubleshoot button click - runs 99_troubleshooting.sql
- ///
- private async void Troubleshoot_Click(object sender, RoutedEventArgs e)
- {
- if (_connectionString == null || _scriptProvider == null)
- {
- return;
- }
-
- /*Dispose previous token source if it exists*/
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = new CancellationTokenSource();
- var cancellationToken = _cancellationTokenSource.Token;
-
- SetUIState(installing: true);
-
- LogMessage("", "Info");
- LogMessage("================================================================================", "Info");
- LogMessage("Running Troubleshooting Script", "Info");
- LogMessage("================================================================================", "Info");
-
- var progress = new Progress(ReportProgress);
-
- try
- {
- bool success = await InstallationService.RunTroubleshootingAsync(
- _connectionString,
- _scriptProvider,
- progress,
- cancellationToken);
-
- if (success)
- {
- LogMessage("Troubleshooting script completed successfully!", "Success");
- }
- }
- catch (OperationCanceledException)
- {
- LogMessage("Troubleshooting cancelled by user.", "Warning");
- }
- catch (Exception ex)
- {
- LogMessage($"Troubleshooting failed: {ex.Message}", "Error");
- }
- finally
- {
- SetUIState(installing: false);
- _cancellationTokenSource?.Dispose();
- _cancellationTokenSource = null;
- }
- }
-
- ///
- /// View report button click
- ///
- private void ViewReport_Click(object sender, RoutedEventArgs e)
- {
- if (_installationResult?.ReportPath == null)
- {
- return;
- }
-
- try
- {
- using var _ = Process.Start(new ProcessStartInfo
- {
- FileName = _installationResult.ReportPath,
- UseShellExecute = true
- });
- }
- catch (Exception ex)
- {
- MessageBox.Show(this, $"Could not open report: {ex.Message}", "Error",
- MessageBoxButton.OK, MessageBoxImage.Error);
- }
- }
-
- ///
- /// Close button click
- ///
- private void Close_Click(object sender, RoutedEventArgs e)
- {
- _cancellationTokenSource?.Cancel();
- Close();
- }
-
- ///
- /// Clean up resources when window closes
- ///
- protected override void OnClosed(EventArgs e)
- {
- _cancellationTokenSource?.Dispose();
- _dependencyInstaller?.Dispose();
- base.OnClosed(e);
- }
-
- ///
- /// Report progress from installation service
- ///
- private async Task CheckForInstallerUpdateAsync()
- {
- try
- {
- using var client = new System.Net.Http.HttpClient { Timeout = TimeSpan.FromSeconds(5) };
- client.DefaultRequestHeaders.Add("User-Agent", "PerformanceMonitor");
- client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
-
- var response = await client.GetAsync(
- "https://api.github.com/repos/erikdarlingdata/PerformanceMonitor/releases/latest");
-
- if (!response.IsSuccessStatusCode) return;
-
- var json = await response.Content.ReadAsStringAsync();
- using var doc = System.Text.Json.JsonDocument.Parse(json);
- var tagName = doc.RootElement.GetProperty("tag_name").GetString() ?? "";
- var versionString = tagName.TrimStart('v', 'V');
-
- if (!Version.TryParse(versionString, out var latest)) return;
- if (!Version.TryParse(AppAssemblyVersion, out var current)) return;
-
- if (latest > current)
- {
- LogMessage($"A newer version ({tagName}) is available at https://github.com/erikdarlingdata/PerformanceMonitor/releases", "Warning");
- }
- }
- catch
- {
- /* Best effort — don't block installation if GitHub is unreachable */
- }
- }
-
- private void ReportProgress(InstallationProgress progress)
- {
- LogMessage(progress.Message, progress.Status);
-
- if (progress.ProgressPercent.HasValue)
- {
- ProgressBar.Value = progress.ProgressPercent.Value;
- ProgressText.Text = $"{progress.ProgressPercent.Value}%";
- }
- }
-
- ///
- /// Log a message to the log text box
- ///
- private void LogMessage(string message, string status)
- {
- Brush brush = status switch
- {
- "Success" => SuccessBrush,
- "Error" => ErrorBrush,
- "Warning" => WarningBrush,
- _ => Brushes.White
- };
-
- string prefix = status switch
- {
- "Success" => "[OK] ",
- "Error" => "[ERROR] ",
- "Warning" => "[WARN] ",
- _ => ""
- };
-
- /*Create a new paragraph for each message*/
- var paragraph = new Paragraph(new Run(prefix + message))
- {
- Margin = new Thickness(0),
- Foreground = brush
- };
-
- LogTextBox.Document.Blocks.Add(paragraph);
-
- /*Auto-scroll to bottom*/
- LogTextBox.ScrollToEnd();
- }
-
- ///
- /// Clear the log text box
- ///
- private void ClearLog()
- {
- LogTextBox.Document.Blocks.Clear();
- var paragraph = new Paragraph();
- LogTextBox.Document.Blocks.Add(paragraph);
- }
-
- ///
- /// Set UI state for installing/not installing
- ///
- private void SetUIState(bool installing)
- {
- ServerTextBox.IsEnabled = !installing;
- WindowsAuthRadio.IsEnabled = !installing;
- SqlAuthRadio.IsEnabled = !installing;
- EntraAuthRadio.IsEnabled = !installing;
- UsernameTextBox.IsEnabled = !installing && (SqlAuthRadio.IsChecked == true || EntraAuthRadio.IsChecked == true);
- PasswordBox.IsEnabled = !installing && SqlAuthRadio.IsChecked == true;
- TestConnectionButton.IsEnabled = !installing;
- CleanInstallCheckBox.IsEnabled = !installing;
- ValidationCheckBox.IsEnabled = !installing;
-
- InstallButton.IsEnabled = !installing;
- InstallButton.Content = installing ? "Installing..." : "Install";
-
- TroubleshootButton.IsEnabled = !installing && _installationResult?.Success == true;
- ViewReportButton.IsEnabled = !installing && _installationResult?.ReportPath != null;
- UninstallButton.IsEnabled = !installing && _installedVersion != null;
-
- if (!installing)
- {
- ProgressBar.Value = 0;
- ProgressText.Text = "0%";
- }
- }
- }
-}
diff --git a/InstallerGui/README.md b/InstallerGui/README.md
deleted file mode 100644
index 4fb10f3b..00000000
--- a/InstallerGui/README.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Performance Monitor GUI Installer
-
-WPF graphical installer for the Full Edition. Provides the same installation as the CLI installer with a visual interface, progress bar, colored log output, and a post-install troubleshooting button.
-
-Both installers execute the same SQL scripts from the `install/` folder and produce identical results.
-
-See the [root README](../README.md) for full documentation.
diff --git a/InstallerGui/Themes/DarkTheme.xaml b/InstallerGui/Themes/DarkTheme.xaml
deleted file mode 100644
index 7e8d0268..00000000
--- a/InstallerGui/Themes/DarkTheme.xaml
+++ /dev/null
@@ -1,337 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/InstallerGui/Utilities/Logger.cs b/InstallerGui/Utilities/Logger.cs
deleted file mode 100644
index adf047ac..00000000
--- a/InstallerGui/Utilities/Logger.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2026 Erik Darling, Darling Data LLC
- *
- * This file is part of the SQL Server Performance Monitor.
- *
- * Licensed under the MIT License. See LICENSE file in the project root for full license information.
- */
-
-using System;
-using System.IO;
-
-namespace PerformanceMonitorInstallerGui.Utilities
-{
- ///
- /// Shared logging utility for error logging to file
- ///
- public static class Logger
- {
- public static readonly string LogFilePath = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
- "InstallerGui_Error.log");
-
- public static void LogToFile(string context, string message)
- {
- try
- {
- var logEntry = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{context}] {message}\n";
- File.AppendAllText(LogFilePath, logEntry);
- }
- catch
- {
- /*Ignore logging errors*/
- }
- }
-
- public static void LogToFile(string context, Exception ex)
- {
- try
- {
- var logEntry = $"""
- ===============================================
- {DateTime.Now:yyyy-MM-dd HH:mm:ss}
- Context: {context}
- Exception: {ex.GetType().FullName}
- Message: {ex.Message}
- Inner Exception: {ex.InnerException?.GetType().FullName}: {ex.InnerException?.Message}
- Stack Trace:
- {ex.StackTrace}
- Inner Stack:
- {ex.InnerException?.StackTrace}
- ===============================================
-
- """;
- File.AppendAllText(LogFilePath, logEntry);
- }
- catch
- {
- /*Ignore logging errors*/
- }
- }
- }
-}