@@ -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