Skip to content

Commit d3e4eec

Browse files
BrentOzarclaude
andcommitted
#3881 build better merge/test/release process
Replace the old build-only script with a single PowerShell script that automates the full release workflow: bump version numbers, build the Install-*.sql files, install on local SQL Server, run test calls, and draft a PR if everything passes. Test Calls.sql is removed — its content is now embedded in the script. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d59f126 commit d3e4eec

File tree

2 files changed

+350
-104
lines changed

2 files changed

+350
-104
lines changed
Lines changed: 350 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,350 @@
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

Comments
 (0)