diff --git a/Installer/Program.cs b/Installer/Program.cs index 74710e41..1acfcbf5 100644 --- a/Installer/Program.cs +++ b/Installer/Program.cs @@ -103,6 +103,7 @@ SQL command timeout constants (in seconds) private const int ShortTimeoutSeconds = 60; // Quick operations (cleanup, queries) private const int MediumTimeoutSeconds = 120; // Dependency installation private const int LongTimeoutSeconds = 300; // SQL file execution (5 minutes) + private const int UpgradeTimeoutSeconds = 3600; // Upgrade data migrations (1 hour, large tables) /* Exit codes for granular error reporting @@ -117,6 +118,7 @@ private static class ExitCodes public const int VersionCheckFailed = 5; public const int SqlFilesNotFound = 6; public const int UninstallFailed = 7; + public const int UpgradesFailed = 8; } static async Task Main(string[] args) @@ -523,8 +525,9 @@ Search current directory and up to 5 parent directories string fileName = Path.GetFileName(f); if (!SqlFileNamePattern.IsMatch(fileName)) return false; - /*Exclude test and troubleshooting scripts from main install*/ - if (fileName.StartsWith("97_", StringComparison.Ordinal) || + /*Exclude uninstall, test, and troubleshooting scripts from main install*/ + if (fileName.StartsWith("00_", StringComparison.Ordinal) || + fileName.StartsWith("97_", StringComparison.Ordinal) || fileName.StartsWith("99_", StringComparison.Ordinal)) return false; return true; @@ -699,6 +702,21 @@ Traces are server-level and persist after database drops Console.WriteLine(); Console.WriteLine($"Upgrades complete: {upgradeSuccessCount} succeeded, {upgradeFailureCount} failed"); + + /*Abort if any upgrade scripts failed — proceeding would reinstall over a partially-upgraded database*/ + if (upgradeFailureCount > 0) + { + Console.WriteLine(); + Console.WriteLine("================================================================================"); + Console.WriteLine("Installation aborted: upgrade scripts must succeed before installation can proceed."); + Console.WriteLine("Fix the errors above and re-run the installer."); + Console.WriteLine("================================================================================"); + if (!automatedMode) + { + WaitForExit(); + } + return ExitCodes.UpgradesFailed; + } } else { @@ -1332,7 +1350,15 @@ FROM PerformanceMonitor.config.installation_history return version.ToString(); } - return null; + /* + Fallback: database and history table exist but no SUCCESS rows. + This can happen if a prior GUI install didn't write history (#538/#539). + Return "1.0.0" so all idempotent upgrade scripts are attempted + rather than treating this as a fresh install (which would drop the database). + */ + Console.WriteLine("Warning: PerformanceMonitor database exists but installation_history has no records."); + Console.WriteLine("Treating as v1.0.0 to apply all available upgrades."); + return "1.0.0"; } } catch (SqlException ex) @@ -1480,7 +1506,7 @@ Execute an upgrade folder using (var cmd = new SqlCommand(trimmedBatch, connection)) { - cmd.CommandTimeout = LongTimeoutSeconds; + cmd.CommandTimeout = UpgradeTimeoutSeconds; try { await cmd.ExecuteNonQueryAsync().ConfigureAwait(false); diff --git a/InstallerGui/MainWindow.xaml.cs b/InstallerGui/MainWindow.xaml.cs index d4ae570e..ee0d48b2 100644 --- a/InstallerGui/MainWindow.xaml.cs +++ b/InstallerGui/MainWindow.xaml.cs @@ -408,6 +408,16 @@ private async void Install_Click(object sender, RoutedEventArgs e) 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; + } } /* diff --git a/InstallerGui/Services/InstallationService.cs b/InstallerGui/Services/InstallationService.cs index e95c07d0..fe4e7c95 100644 --- a/InstallerGui/Services/InstallationService.cs +++ b/InstallerGui/Services/InstallationService.cs @@ -238,8 +238,9 @@ public static (string? SqlDirectory, string? MonitorRootDirectory, List /*Match numbered SQL files but exclude 97 (tests) and 99 (troubleshooting)*/ if (!SqlFilePattern.IsMatch(fileName)) return false; - /*Exclude test and troubleshooting scripts from main install*/ - if (fileName.StartsWith("97_", StringComparison.Ordinal) || + /*Exclude uninstall, test, and troubleshooting scripts from main install*/ + if (fileName.StartsWith("00_", StringComparison.Ordinal) || + fileName.StartsWith("97_", StringComparison.Ordinal) || fileName.StartsWith("99_", StringComparison.Ordinal)) return false; return true; @@ -1113,7 +1114,13 @@ FROM PerformanceMonitor.config.installation_history return version.ToString(); } - return null; + /* + Fallback: database and history table exist but no SUCCESS rows. + This can happen if a prior GUI install didn't write history (#538/#539). + Return "1.0.0" so all idempotent upgrade scripts are attempted + rather than treating this as a fresh install (which would drop the database). + */ + return "1.0.0"; } catch (SqlException) { @@ -1272,7 +1279,7 @@ Parse versions and filter to only applicable upgrades continue; using var cmd = new SqlCommand(trimmedBatch, connection); - cmd.CommandTimeout = 300; + cmd.CommandTimeout = 3600; /*1 hour — upgrade migrations on large tables need extended time*/ try {