|
1 | | -#Set your file path |
2 | | -$FilePath = "/Users/brentozar/LocalOnly/Github/SQL-Server-First-Responder-Kit" |
3 | | -$SqlVersionsPath = "$FilePath/SqlServerVersions.sql" |
4 | | -$BlitzFirstPath = "$FilePath/sp_BlitzFirst.sql" |
5 | | - |
6 | | -#Azure - skip sp_Blitz, sp_BlitzBackups, sp_DatabaseRestore, sp_ineachdb |
7 | | -Get-ChildItem -Path "$FilePath" -Filter "sp_Blitz*.sql" | |
8 | | -Where-Object { $_.FullName -notlike "*sp_Blitz.sql*" -and $_.FullName -notlike "*sp_BlitzBackups*" -and $_.FullName -notlike "*sp_DatabaseRestore*"} | |
9 | | -ForEach-Object { Get-Content $_.FullName } | |
10 | | -Set-Content -Path "$FilePath/Install-Azure.sql" -Force |
11 | | -if ( test-path "$BlitzFirstPath") |
12 | | - { Add-Content -Path "$FilePath/Install-All-Scripts.sql" -Value (Get-Content -Path "$BlitzFirstPath")} |
13 | | - |
14 | | - |
15 | | -#All Scripts |
16 | | -Get-ChildItem -Path "$FilePath" -Filter "sp_*.sql" | |
17 | | -Where-Object { $_.FullName -notlike "*sp_BlitzInMemoryOLTP*" -and $_.FullName -notlike "*sp_BlitzFirst*"} | |
18 | | -ForEach-Object { Get-Content $_.FullName } | |
19 | | -Set-Content -Path "$FilePath/Install-All-Scripts.sql" -Force |
20 | | -#append script to (re-)create SqlServerVersions Table (https://github.com/BrentOzarULTD/SQL-Server-First-Responder-Kit/issues/2429) |
21 | | -if ( test-path "$SqlVersionsPath") |
22 | | - { Add-Content -Path "$FilePath/Install-All-Scripts.sql" -Value (Get-Content -Path "$SqlVersionsPath")} |
23 | | -if ( test-path "$BlitzFirstPath") |
24 | | - { Add-Content -Path "$FilePath/Install-All-Scripts.sql" -Value (Get-Content -Path "$BlitzFirstPath")} |
| 1 | +<# |
| 2 | +.SYNOPSIS |
| 3 | + Automates the First Responder Kit release process: bumps version numbers, |
| 4 | + builds the Install-*.sql merged scripts, runs them on a local SQL Server, |
| 5 | + executes test calls, and (if clean) drafts a pull request into dev. |
| 6 | +
|
| 7 | +.PARAMETER ServerInstance |
| 8 | + SQL Server instance to test against. Default: localhost |
| 9 | +
|
| 10 | +.PARAMETER Database |
| 11 | + Database where the procs are installed. Default: master |
| 12 | +
|
| 13 | +.EXAMPLE |
| 14 | + & "Documentation\Development\Build Installation Scripts.ps1" |
| 15 | +#> |
| 16 | +param( |
| 17 | + [string]$ServerInstance = "localhost", |
| 18 | + [string]$Database = "master" |
| 19 | +) |
| 20 | + |
| 21 | +$ErrorActionPreference = "Stop" |
| 22 | + |
| 23 | +# ── Paths ──────────────────────────────────────────────────────────────────── |
| 24 | +$ScriptDir = $PSScriptRoot |
| 25 | +$RepoRoot = (Resolve-Path "$ScriptDir\..\..\").Path |
| 26 | +$ErrorLog = Join-Path $ScriptDir "release_errors_$(Get-Date -Format 'yyyyMMdd').log" |
| 27 | +$Today = Get-Date -Format "yyyyMMdd" |
| 28 | +$BranchName = "${Today}_release_prep" |
| 29 | +$Utf8NoBom = New-Object System.Text.UTF8Encoding $false |
| 30 | + |
| 31 | +# ── Helper: run a SQL string via sqlcmd, return output ─────────────────────── |
| 32 | +function Invoke-SqlCmd-String { |
| 33 | + param([string]$Sql, [string]$Label) |
| 34 | + |
| 35 | + $tmpFile = [System.IO.Path]::GetTempFileName() + ".sql" |
| 36 | + [System.IO.File]::WriteAllText($tmpFile, $Sql, $Utf8NoBom) |
| 37 | + |
| 38 | + $prevPref = $ErrorActionPreference |
| 39 | + $ErrorActionPreference = "Continue" |
| 40 | + $output = & sqlcmd -S $ServerInstance -E -d $Database -C -i $tmpFile -b 2>&1 |
| 41 | + $exitCode = $LASTEXITCODE |
| 42 | + $ErrorActionPreference = $prevPref |
| 43 | + Remove-Item $tmpFile -ErrorAction SilentlyContinue |
| 44 | + |
| 45 | + return @{ Output = ($output -join "`n"); ExitCode = $exitCode; Label = $Label } |
| 46 | +} |
| 47 | + |
| 48 | +# ── Helper: run a .sql file via sqlcmd, return output ──────────────────────── |
| 49 | +function Invoke-SqlCmd-File { |
| 50 | + param([string]$FilePath, [string]$Label) |
| 51 | + |
| 52 | + $prevPref = $ErrorActionPreference |
| 53 | + $ErrorActionPreference = "Continue" |
| 54 | + $output = & sqlcmd -S $ServerInstance -E -d $Database -C -i $FilePath -b 2>&1 |
| 55 | + $exitCode = $LASTEXITCODE |
| 56 | + $ErrorActionPreference = $prevPref |
| 57 | + |
| 58 | + return @{ Output = ($output -join "`n"); ExitCode = $exitCode; Label = $Label } |
| 59 | +} |
| 60 | + |
| 61 | +# ── Helper: log an error ───────────────────────────────────────────────────── |
| 62 | +$script:HasErrors = $false |
| 63 | +function Log-Error { |
| 64 | + param([string]$Label, [string]$Details) |
| 65 | + $script:HasErrors = $true |
| 66 | + $entry = @" |
| 67 | +================================================================================ |
| 68 | +TEST: $Label |
| 69 | +$(Get-Date -Format "yyyy-MM-dd HH:mm:ss") |
| 70 | +-------------------------------------------------------------------------------- |
| 71 | +$Details |
| 72 | +"@ |
| 73 | + Add-Content -Path $ErrorLog -Value $entry -Encoding UTF8 |
| 74 | + Write-Host " FAIL: $Label" -ForegroundColor Red |
| 75 | +} |
| 76 | + |
| 77 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 78 | +# STEP 1 — Determine current version and compute new version |
| 79 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 80 | +Write-Host "`n=== Step 1: Determine version ===" -ForegroundColor Cyan |
| 81 | + |
| 82 | +$sampleFile = Get-ChildItem -Path $RepoRoot -Filter "sp_Blitz.sql" | Select-Object -First 1 |
| 83 | +$sampleContent = Get-Content $sampleFile.FullName -Raw |
| 84 | + |
| 85 | +if ($sampleContent -match "SELECT\s+@Version\s*=\s*'([^']+)',\s*@VersionDate\s*=\s*'([^']+)'") { |
| 86 | + $currentVersion = $Matches[1] |
| 87 | + $currentDate = $Matches[2] |
| 88 | +} else { |
| 89 | + throw "Could not parse current version from $($sampleFile.Name)" |
| 90 | +} |
| 91 | + |
| 92 | +# Bump minor version by 0.01 |
| 93 | +$newVersion = [string]([math]::Round([double]$currentVersion + 0.01, 2)) |
| 94 | +# Ensure two decimal places (e.g. 8.30 not 8.3) |
| 95 | +if ($newVersion -notmatch "\.\d{2}$") { $newVersion = "{0:F2}" -f [double]$newVersion } |
| 96 | + |
| 97 | +Write-Host " Current: v$currentVersion ($currentDate)" |
| 98 | +Write-Host " New: v$newVersion ($Today)" |
| 99 | + |
| 100 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 101 | +# STEP 2 — Create release branch |
| 102 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 103 | +Write-Host "`n=== Step 2: Create branch $BranchName ===" -ForegroundColor Cyan |
| 104 | + |
| 105 | +Push-Location $RepoRoot |
| 106 | +try { |
| 107 | + $ErrorActionPreference = "Continue" |
| 108 | + $null = git checkout dev 2>&1 |
| 109 | + $null = git checkout -b $BranchName 2>&1 |
| 110 | + $ErrorActionPreference = "Stop" |
| 111 | + if ($LASTEXITCODE -ne 0) { throw "Failed to create branch $BranchName" } |
| 112 | + Write-Host " Branch created: $BranchName" |
| 113 | +} finally { |
| 114 | + Pop-Location |
| 115 | +} |
| 116 | + |
| 117 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 118 | +# STEP 3 — Update version numbers and dates in all sp_*.sql files |
| 119 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 120 | +Write-Host "`n=== Step 3: Update version numbers ===" -ForegroundColor Cyan |
| 121 | + |
| 122 | +$spFiles = Get-ChildItem -Path $RepoRoot -Filter "sp_*.sql" |
| 123 | +foreach ($file in $spFiles) { |
| 124 | + $content = [System.IO.File]::ReadAllText($file.FullName) |
| 125 | + $content = $content -replace "SELECT\s+@Version\s*=\s*'[^']+',\s*@VersionDate\s*=\s*'[^']+';", "SELECT @Version = '$newVersion', @VersionDate = '$Today';" |
| 126 | + [System.IO.File]::WriteAllText($file.FullName, $content, $Utf8NoBom) |
| 127 | + Write-Host " Updated: $($file.Name)" |
| 128 | +} |
| 129 | + |
| 130 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 131 | +# STEP 4 — Build installation scripts |
| 132 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 133 | +Write-Host "`n=== Step 4: Build Install-*.sql ===" -ForegroundColor Cyan |
| 134 | + |
| 135 | +$SqlVersionsPath = Join-Path $RepoRoot "SqlServerVersions.sql" |
| 136 | +$BlitzFirstPath = Join-Path $RepoRoot "sp_BlitzFirst.sql" |
| 137 | +$InstallAllPath = Join-Path $RepoRoot "Install-All-Scripts.sql" |
| 138 | +$InstallAzurePath = Join-Path $RepoRoot "Install-Azure.sql" |
| 139 | + |
| 140 | +# ── Install-Azure.sql ──────────────────────────────────────────────────────── |
| 141 | +# All sp_Blitz*.sql except sp_Blitz.sql, sp_BlitzBackups.sql, sp_DatabaseRestore.sql, sp_BlitzFirst.sql |
| 142 | +$azureContent = Get-ChildItem -Path $RepoRoot -Filter "sp_Blitz*.sql" | |
| 143 | + Where-Object { $_.Name -ne "sp_Blitz.sql" -and $_.Name -notlike "*BlitzBackups*" -and $_.Name -notlike "*DatabaseRestore*" -and $_.Name -notlike "*BlitzFirst*" } | |
| 144 | + ForEach-Object { Get-Content $_.FullName -Raw } |
| 145 | + |
| 146 | +if (Test-Path $BlitzFirstPath) { |
| 147 | + $azureContent += Get-Content -Path $BlitzFirstPath -Raw |
| 148 | +} |
| 149 | + |
| 150 | +[System.IO.File]::WriteAllText($InstallAzurePath, ($azureContent -join "`r`n"), $Utf8NoBom) |
| 151 | +Write-Host " Built: Install-Azure.sql" |
| 152 | + |
| 153 | +# ── Install-All-Scripts.sql ────────────────────────────────────────────────── |
| 154 | +# All sp_*.sql except sp_BlitzInMemoryOLTP.sql and sp_BlitzFirst.sql |
| 155 | +$allContent = Get-ChildItem -Path $RepoRoot -Filter "sp_*.sql" | |
| 156 | + Where-Object { $_.Name -notlike "*BlitzInMemoryOLTP*" -and $_.Name -notlike "*BlitzFirst*" } | |
| 157 | + ForEach-Object { Get-Content $_.FullName -Raw } |
| 158 | + |
| 159 | +# Append SqlServerVersions.sql |
| 160 | +if (Test-Path $SqlVersionsPath) { |
| 161 | + $allContent += Get-Content -Path $SqlVersionsPath -Raw |
| 162 | +} |
| 163 | + |
| 164 | +# Append sp_BlitzFirst.sql last |
| 165 | +if (Test-Path $BlitzFirstPath) { |
| 166 | + $allContent += Get-Content -Path $BlitzFirstPath -Raw |
| 167 | +} |
| 168 | + |
| 169 | +[System.IO.File]::WriteAllText($InstallAllPath, ($allContent -join "`r`n"), $Utf8NoBom) |
| 170 | +Write-Host " Built: Install-All-Scripts.sql" |
| 171 | + |
| 172 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 173 | +# STEP 5 — Install procs on local SQL Server |
| 174 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 175 | +Write-Host "`n=== Step 5: Install procs via Install-All-Scripts.sql ===" -ForegroundColor Cyan |
| 176 | + |
| 177 | +$installResult = Invoke-SqlCmd-File -FilePath $InstallAllPath -Label "Install-All-Scripts.sql" |
| 178 | +if ($installResult.ExitCode -ne 0) { |
| 179 | + Log-Error -Label $installResult.Label -Details $installResult.Output |
| 180 | + Write-Host "`n Installation failed. See $ErrorLog for details." -ForegroundColor Red |
| 181 | + Write-Host " Skipping test calls." -ForegroundColor Yellow |
| 182 | +} else { |
| 183 | + Write-Host " Installation succeeded." |
| 184 | + |
| 185 | + # ══════════════════════════════════════════════════════════════════════════ |
| 186 | + # STEP 6 — Run test calls |
| 187 | + # ══════════════════════════════════════════════════════════════════════════ |
| 188 | + Write-Host "`n=== Step 6: Run test calls ===" -ForegroundColor Cyan |
| 189 | + |
| 190 | + $testCalls = @( |
| 191 | + @{ |
| 192 | + Label = "Drop test tables" |
| 193 | + Sql = @" |
| 194 | +DROP TABLE IF EXISTS DBAtools.dbo.Blitz; |
| 195 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzWho_Results; |
| 196 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzFirst; |
| 197 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzFirst_FileStats; |
| 198 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzFirst_PerfmonStats; |
| 199 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzFirst_WaitStats; |
| 200 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzCache; |
| 201 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzWho; |
| 202 | +DROP TABLE IF EXISTS DBAtools.dbo.BlitzLock; |
| 203 | +"@ |
| 204 | + }, |
| 205 | + @{ |
| 206 | + Label = "sp_Blitz" |
| 207 | + Sql = "EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1;" |
| 208 | + }, |
| 209 | + @{ |
| 210 | + Label = "sp_Blitz (output to table)" |
| 211 | + Sql = "EXEC dbo.sp_Blitz @CheckUserDatabaseObjects = 1, @CheckServerInfo = 1, @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'Blitz';" |
| 212 | + }, |
| 213 | + @{ |
| 214 | + Label = "sp_BlitzBackups" |
| 215 | + Sql = "EXEC dbo.sp_BlitzBackups @HoursBack = 1000000;" |
| 216 | + }, |
| 217 | + @{ |
| 218 | + Label = "sp_BlitzCache" |
| 219 | + Sql = "EXEC dbo.sp_BlitzCache;" |
| 220 | + }, |
| 221 | + @{ |
| 222 | + Label = "sp_BlitzCache @AI = 2" |
| 223 | + Sql = "EXEC dbo.sp_BlitzCache @AI = 2;" |
| 224 | + }, |
| 225 | + @{ |
| 226 | + Label = "sp_BlitzCache @SortOrder = 'all'" |
| 227 | + Sql = "EXEC dbo.sp_BlitzCache @SortOrder = 'all';" |
| 228 | + }, |
| 229 | + @{ |
| 230 | + Label = "sp_BlitzCache (output to table)" |
| 231 | + Sql = "EXEC dbo.sp_BlitzCache @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzCache';" |
| 232 | + }, |
| 233 | + @{ |
| 234 | + Label = "sp_BlitzFirst" |
| 235 | + Sql = "EXEC dbo.sp_BlitzFirst;" |
| 236 | + }, |
| 237 | + @{ |
| 238 | + Label = "sp_BlitzFirst @Seconds = 5, @ExpertMode = 1" |
| 239 | + Sql = "EXEC dbo.sp_BlitzFirst @Seconds = 5, @ExpertMode = 1;" |
| 240 | + }, |
| 241 | + @{ |
| 242 | + Label = "sp_BlitzFirst @SinceStartup = 1" |
| 243 | + Sql = "EXEC dbo.sp_BlitzFirst @SinceStartup = 1;" |
| 244 | + }, |
| 245 | + @{ |
| 246 | + Label = "sp_BlitzFirst (output to tables)" |
| 247 | + Sql = @" |
| 248 | +EXEC dbo.sp_BlitzFirst @OutputDatabaseName = 'DBAtools', |
| 249 | + @OutputSchemaName = 'dbo', |
| 250 | + @OutputTableName = 'BlitzFirst', |
| 251 | + @OutputTableNameFileStats = 'BlitzFirst_FileStats', |
| 252 | + @OutputTableNamePerfmonStats = 'BlitzFirst_PerfmonStats', |
| 253 | + @OutputTableNameWaitStats = 'BlitzFirst_WaitStats', |
| 254 | + @OutputTableNameBlitzCache = 'BlitzCache', |
| 255 | + @OutputTableNameBlitzWho = 'BlitzWho'; |
| 256 | +"@ |
| 257 | + }, |
| 258 | + @{ |
| 259 | + Label = "sp_BlitzIndex @Mode = 0" |
| 260 | + Sql = "EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 0;" |
| 261 | + }, |
| 262 | + @{ |
| 263 | + Label = "sp_BlitzIndex @Mode = 1" |
| 264 | + Sql = "EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 1;" |
| 265 | + }, |
| 266 | + @{ |
| 267 | + Label = "sp_BlitzIndex @Mode = 2" |
| 268 | + Sql = "EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 2;" |
| 269 | + }, |
| 270 | + @{ |
| 271 | + Label = "sp_BlitzIndex @Mode = 3" |
| 272 | + Sql = "EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 3;" |
| 273 | + }, |
| 274 | + @{ |
| 275 | + Label = "sp_BlitzIndex @Mode = 4" |
| 276 | + Sql = "EXEC dbo.sp_BlitzIndex @GetAllDatabases = 1, @Mode = 4;" |
| 277 | + }, |
| 278 | + @{ |
| 279 | + Label = "sp_BlitzIndex @DatabaseName = 'StackOverflow', @TableName = 'Users'" |
| 280 | + Sql = "EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow', @TableName = 'Users';" |
| 281 | + }, |
| 282 | + @{ |
| 283 | + Label = "sp_BlitzIndex @DatabaseName = 'StackOverflow', @TableName = 'Users', @AI = 2" |
| 284 | + Sql = "EXEC dbo.sp_BlitzIndex @DatabaseName = 'StackOverflow', @TableName = 'Users', @AI = 2;" |
| 285 | + }, |
| 286 | + @{ |
| 287 | + Label = "sp_BlitzLock" |
| 288 | + Sql = "EXEC dbo.sp_BlitzLock;" |
| 289 | + }, |
| 290 | + @{ |
| 291 | + Label = "sp_BlitzLock (output to table)" |
| 292 | + Sql = "EXEC dbo.sp_BlitzLock @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzLock';" |
| 293 | + }, |
| 294 | + @{ |
| 295 | + Label = "sp_BlitzWho" |
| 296 | + Sql = "EXEC dbo.sp_BlitzWho;" |
| 297 | + }, |
| 298 | + @{ |
| 299 | + Label = "sp_BlitzWho @ExpertMode = 1" |
| 300 | + Sql = "EXEC dbo.sp_BlitzWho @ExpertMode = 1;" |
| 301 | + }, |
| 302 | + @{ |
| 303 | + Label = "sp_BlitzWho (output to table)" |
| 304 | + Sql = "EXEC dbo.sp_BlitzWho @OutputDatabaseName = 'DBAtools', @OutputSchemaName = 'dbo', @OutputTableName = 'BlitzWho_Results';" |
| 305 | + } |
| 306 | + ) |
| 307 | + |
| 308 | + $testNumber = 0 |
| 309 | + foreach ($test in $testCalls) { |
| 310 | + $testNumber++ |
| 311 | + Write-Host " [$testNumber/$($testCalls.Count)] $($test.Label)..." -NoNewline |
| 312 | + $result = Invoke-SqlCmd-String -Sql $test.Sql -Label $test.Label |
| 313 | + if ($result.ExitCode -ne 0) { |
| 314 | + Log-Error -Label $test.Label -Details $result.Output |
| 315 | + } else { |
| 316 | + Write-Host " OK" -ForegroundColor Green |
| 317 | + } |
| 318 | + } |
| 319 | +} |
| 320 | + |
| 321 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 322 | +# STEP 7 — Report results |
| 323 | +# ══════════════════════════════════════════════════════════════════════════════ |
| 324 | +Write-Host "`n=== Step 7: Results ===" -ForegroundColor Cyan |
| 325 | + |
| 326 | +if ($script:HasErrors) { |
| 327 | + Write-Host "`n Errors were found. See log: $ErrorLog" -ForegroundColor Red |
| 328 | + Write-Host " PR was NOT created. Fix the errors and re-run." -ForegroundColor Yellow |
| 329 | +} else { |
| 330 | + Write-Host " All tests passed!" -ForegroundColor Green |
| 331 | + |
| 332 | + # Stage and commit changes |
| 333 | + Push-Location $RepoRoot |
| 334 | + try { |
| 335 | + $ErrorActionPreference = "Continue" |
| 336 | + $null = git add -A 2>&1 |
| 337 | + $null = git commit -m "$Today release prep" 2>&1 |
| 338 | + $null = git push -u origin $BranchName 2>&1 |
| 339 | + $ErrorActionPreference = "Stop" |
| 340 | + |
| 341 | + # Create PR |
| 342 | + $prBody = "## Summary`n- Version bumped to $newVersion`n- Version date set to $Today`n- Install scripts rebuilt`n- All test calls passed on local SQL Server ($ServerInstance)" |
| 343 | + $prUrl = gh pr create --base dev --title "$Today release prep" --body $prBody 2>&1 |
| 344 | + Write-Host " PR created: $prUrl" -ForegroundColor Green |
| 345 | + } finally { |
| 346 | + Pop-Location |
| 347 | + } |
| 348 | +} |
| 349 | + |
| 350 | +Write-Host "`nDone.`n" |
0 commit comments