Skip to content

Commit fd7f1e1

Browse files
Merge pull request #639 from erikdarlingdata/feature/cli-colors-and-update-check
Add colored output and version check to CLI installer
2 parents 366dced + 1129ddf commit fd7f1e1

1 file changed

Lines changed: 81 additions & 15 deletions

File tree

Installer/Program.cs

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ static async Task<int> Main(string[] args)
133133
Console.WriteLine("Licensed under the MIT License");
134134
Console.WriteLine("https://github.com/erikdarlingdata/PerformanceMonitor");
135135
Console.WriteLine("================================================================================");
136-
Console.WriteLine();
136+
137+
await CheckForInstallerUpdateAsync(version);
138+
137139

138140
/*
139141
Determine if running in automated mode (command-line arguments provided)
@@ -422,7 +424,7 @@ Test connection and get SQL Server version
422424
using (var connection = new SqlConnection(builder.ConnectionString))
423425
{
424426
await connection.OpenAsync().ConfigureAwait(false);
425-
Console.WriteLine("Connection successful!");
427+
WriteSuccess("Connection successful!");
426428

427429
/*Capture SQL Server version for summary report*/
428430
using (var versionCmd = new SqlCommand(@"
@@ -468,7 +470,7 @@ Azure MI (EngineEdition 8) is always current, skip the check.*/
468470
}
469471
catch (Exception ex)
470472
{
471-
Console.WriteLine($"Connection failed: {ex.Message}");
473+
WriteError($"Connection failed: {ex.Message}");
472474
Console.WriteLine($"Exception type: {ex.GetType().Name}");
473475
if (ex.InnerException != null)
474476
{
@@ -655,7 +657,7 @@ Traces are server-level and persist after database drops
655657
}
656658
}
657659

658-
Console.WriteLine("✓ Clean install completed (jobs and database removed)");
660+
WriteSuccess("Clean install completed (jobs and database removed)");
659661
}
660662
catch (Exception ex)
661663
{
@@ -736,7 +738,7 @@ Traces are server-level and persist after database drops
736738
{
737739
Console.WriteLine();
738740
Console.WriteLine("================================================================================");
739-
Console.WriteLine("Installation aborted: upgrade scripts must succeed before installation can proceed.");
741+
WriteError("Installation aborted: upgrade scripts must succeed before installation can proceed.");
740742
Console.WriteLine("Fix the errors above and re-run the installer.");
741743
Console.WriteLine("================================================================================");
742744
if (!automatedMode)
@@ -854,12 +856,12 @@ Match GO only when it's a whole word on its own line
854856
}
855857
}
856858

857-
Console.WriteLine("✓ Success");
859+
WriteSuccess("Success");
858860
installSuccessCount++;
859861
}
860862
catch (Exception ex)
861863
{
862-
Console.WriteLine($"✗ FAILED");
864+
WriteError("FAILED");
863865
Console.WriteLine($" Error: {ex.Message}");
864866
installFailureCount++;
865867
installationErrors.Add((fileName, ex.Message));
@@ -938,7 +940,7 @@ Use SYSDATETIME() (local) because collection_time is stored in server local time
938940
command.CommandTimeout = LongTimeoutSeconds;
939941
await command.ExecuteNonQueryAsync().ConfigureAwait(false);
940942
}
941-
Console.WriteLine("✓ Success");
943+
WriteSuccess("Success");
942944

943945
/*
944946
Verify data was collected — only from this validation run, not historical errors
@@ -1089,7 +1091,7 @@ WHERE t.name LIKE 'query_snapshots_%'
10891091
}
10901092
}
10911093

1092-
Console.WriteLine("✓ Success");
1094+
WriteSuccess("Success");
10931095
installFailureCount = 0; /* Reset failure count */
10941096
}
10951097
}
@@ -1159,7 +1161,7 @@ await LogInstallationHistory(
11591161

11601162
if (installationSuccessful)
11611163
{
1162-
Console.WriteLine("Installation completed successfully!");
1164+
WriteSuccess("Installation completed successfully!");
11631165
Console.WriteLine();
11641166
Console.WriteLine("WHAT WAS INSTALLED:");
11651167
Console.WriteLine("✓ PerformanceMonitor database and all collection tables");
@@ -1179,7 +1181,7 @@ await LogInstallationHistory(
11791181
}
11801182
else
11811183
{
1182-
Console.WriteLine($"Installation completed with {totalFailureCount} error(s).");
1184+
WriteWarning($"Installation completed with {totalFailureCount} error(s).");
11831185
Console.WriteLine("Review errors above and check PerformanceMonitor.config.collection_log for details.");
11841186
}
11851187

@@ -1307,7 +1309,7 @@ private static async Task<int> PerformUninstallAsync(string connectionString, bo
13071309
await command.ExecuteNonQueryAsync().ConfigureAwait(false);
13081310

13091311
Console.WriteLine();
1310-
Console.WriteLine("✓ Uninstall completed successfully");
1312+
WriteSuccess("Uninstall completed successfully");
13111313
Console.WriteLine();
13121314
Console.WriteLine("Note: blocked process threshold (s) was NOT reset.");
13131315
}
@@ -1550,12 +1552,12 @@ Execute an upgrade folder
15501552
}
15511553
}
15521554

1553-
Console.WriteLine("✓ Success");
1555+
WriteSuccess("Success");
15541556
successCount++;
15551557
}
15561558
catch (Exception ex)
15571559
{
1558-
Console.WriteLine($"✗ FAILED");
1560+
WriteError("FAILED");
15591561
Console.WriteLine($" Error: {ex.Message}");
15601562
failureCount++;
15611563
}
@@ -1899,7 +1901,7 @@ private static async Task InstallDependenciesAsync(string connectionString)
18991901
await command.ExecuteNonQueryAsync().ConfigureAwait(false);
19001902
}
19011903

1902-
Console.WriteLine("✓ Success");
1904+
WriteSuccess("Success");
19031905
Console.WriteLine($" {description}");
19041906
successCount++;
19051907
}
@@ -2050,6 +2052,70 @@ Write file
20502052
return reportPath;
20512053
}
20522054

2055+
private static void WriteSuccess(string message)
2056+
{
2057+
Console.ForegroundColor = ConsoleColor.Green;
2058+
Console.Write("√ ");
2059+
Console.ResetColor();
2060+
Console.WriteLine(message);
2061+
}
2062+
2063+
private static void WriteError(string message)
2064+
{
2065+
Console.ForegroundColor = ConsoleColor.Red;
2066+
Console.Write("✗ ");
2067+
Console.ResetColor();
2068+
Console.WriteLine(message);
2069+
}
2070+
2071+
private static void WriteWarning(string message)
2072+
{
2073+
Console.ForegroundColor = ConsoleColor.Yellow;
2074+
Console.Write("! ");
2075+
Console.ResetColor();
2076+
Console.WriteLine(message);
2077+
}
2078+
2079+
private static async Task CheckForInstallerUpdateAsync(string currentVersion)
2080+
{
2081+
try
2082+
{
2083+
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
2084+
client.DefaultRequestHeaders.Add("User-Agent", "PerformanceMonitor");
2085+
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
2086+
2087+
var response = await client.GetAsync(
2088+
"https://api.github.com/repos/erikdarlingdata/PerformanceMonitor/releases/latest")
2089+
.ConfigureAwait(false);
2090+
2091+
if (!response.IsSuccessStatusCode) return;
2092+
2093+
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
2094+
using var doc = System.Text.Json.JsonDocument.Parse(json);
2095+
var tagName = doc.RootElement.GetProperty("tag_name").GetString() ?? "";
2096+
var versionString = tagName.TrimStart('v', 'V');
2097+
2098+
if (!Version.TryParse(versionString, out var latest)) return;
2099+
if (!Version.TryParse(currentVersion, out var current)) return;
2100+
2101+
if (latest > current)
2102+
{
2103+
Console.WriteLine();
2104+
Console.ForegroundColor = ConsoleColor.Yellow;
2105+
Console.WriteLine("╔══════════════════════════════════════════════════════════════════════╗");
2106+
Console.WriteLine($"║ A newer version ({tagName}) is available! ");
2107+
Console.WriteLine("║ https://github.com/erikdarlingdata/PerformanceMonitor/releases ");
2108+
Console.WriteLine("╚══════════════════════════════════════════════════════════════════════╝");
2109+
Console.ResetColor();
2110+
Console.WriteLine();
2111+
}
2112+
}
2113+
catch
2114+
{
2115+
/* Best effort — don't block installation if GitHub is unreachable */
2116+
}
2117+
}
2118+
20532119
[GeneratedRegex(@"^\s*GO\s*(?:--[^\r\n]*)?\s*$", RegexOptions.IgnoreCase | RegexOptions.Multiline)]
20542120
private static partial Regex GoBatchRegExp();
20552121
}

0 commit comments

Comments
 (0)