Skip to content

Commit 6aea14d

Browse files
Test-DbaBuild - Add -MaxTimeBehind parameter for time-based compliance checking
Adds a new -MaxTimeBehind parameter that determines compliance based on how long ago the current build was released, using ReleaseDate data from the build reference index. Supports formats like '6Mo' (months) and '180D' (days). Builds without ReleaseDate data are reported as non-compliant with a warning. (do Test-DbaBuild) Co-authored-by: Andreas Jordan <andreasjordan@users.noreply.github.com>
1 parent 87a0873 commit 6aea14d

2 files changed

Lines changed: 91 additions & 5 deletions

File tree

public/Test-DbaBuild.ps1

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ function Test-DbaBuild {
2121
Use format like "1SP", "2CU", or "1SP 1CU" to specify maximum allowed gaps from current releases.
2222
This approach automatically adjusts compliance targets as new patches are released, unlike fixed MinimumBuild values.
2323
24+
.PARAMETER MaxTimeBehind
25+
Defines compliance based on how much time has elapsed since the current build was released.
26+
Use format like "6Mo" for months or "180D" for days to specify the maximum acceptable build age.
27+
Requires ReleaseDate data in the build reference index. Builds without release date information will be reported as non-compliant with a warning.
28+
This approach aligns with organizational patch policies measured in time (e.g., "all instances must run a build released within the last 6 months").
29+
2430
.PARAMETER Latest
2531
Requires SQL Server instances to be running the most current build available for their version.
2632
Use this for environments with strict currency requirements where any outdated build is considered non-compliant.
@@ -84,6 +90,14 @@ function Test-DbaBuild {
8490
- BuildTarget: The target build version that represents the compliance threshold
8591
- MinimumBuild: Hidden (not shown by default)
8692
93+
When -MaxTimeBehind is specified:
94+
- MaxTimeBehind: The maximum time window specification (e.g., "6Mo", "180D")
95+
- BuildTarget: The oldest build version released within the time window (represents minimum acceptable version)
96+
- MinimumBuild: Hidden (not shown by default)
97+
- MaxBehind: Hidden (not shown by default)
98+
- SPTarget: Hidden (not shown by default)
99+
- CUTarget: Hidden (not shown by default)
100+
87101
When -SqlInstance is specified (instead of -Build):
88102
- SqlInstance: The source SQL Server instance where the build was discovered
89103
@@ -145,13 +159,26 @@ function Test-DbaBuild {
145159
146160
Integrate with other cmdlets to have builds checked for all your registered servers on sqlserver2014a.
147161
162+
.EXAMPLE
163+
PS C:\> Test-DbaBuild -Build "16.0.4095" -MaxTimeBehind "6Mo"
164+
165+
Returns information about SQL Server 2022 CU10 (16.0.4095), checking whether it was released within the last 6 months.
166+
Requires ReleaseDate data in the build reference index.
167+
168+
.EXAMPLE
169+
PS C:\> Test-DbaBuild -SqlInstance $servers -MaxTimeBehind "180D" -Update
170+
171+
Checks all instances in $servers to ensure they are running a build released within the last 180 days.
172+
Updates the build reference index before performing the check.
173+
148174
#>
149175
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
150176
[CmdletBinding()]
151177
param (
152178
[version[]]$Build,
153179
[version]$MinimumBuild,
154180
[string]$MaxBehind,
181+
[string]$MaxTimeBehind,
155182
[switch] $Latest,
156183
[parameter(ValueFromPipeline)]
157184
[DbaInstanceParameter[]]$SqlInstance,
@@ -173,16 +200,16 @@ function Test-DbaBuild {
173200
}
174201

175202
$ComplianceSpec = @()
176-
$ComplianceSpecExclusiveParams = @('MinimumBuild', 'MaxBehind', 'Latest')
203+
$ComplianceSpecExclusiveParams = @('MinimumBuild', 'MaxBehind', 'MaxTimeBehind', 'Latest')
177204
foreach ($exclParam in $ComplianceSpecExclusiveParams) {
178205
if (Test-Bound -Parameter $exclParam) { $ComplianceSpec += $exclParam }
179206
}
180207
if ($ComplianceSpec.Length -gt 1) {
181-
Stop-Function -Category InvalidArgument -Message "-MinimumBuild, -MaxBehind and -Latest are mutually exclusive. Please choose only one. Quitting."
208+
Stop-Function -Category InvalidArgument -Message "-MinimumBuild, -MaxBehind, -MaxTimeBehind and -Latest are mutually exclusive. Please choose only one. Quitting."
182209
return
183210
}
184211
if ($ComplianceSpec.Length -eq 0) {
185-
Stop-Function -Category InvalidArgument -Message "You need to choose one from -MinimumBuild, -MaxBehind and -Latest. Quitting."
212+
Stop-Function -Category InvalidArgument -Message "You need to choose one from -MinimumBuild, -MaxBehind, -MaxTimeBehind and -Latest. Quitting."
186213
return
187214
}
188215
if ($MaxBehind) {
@@ -214,6 +241,18 @@ function Test-DbaBuild {
214241
return
215242
}
216243
}
244+
if ($MaxTimeBehind) {
245+
$MaxTimeBehindValidator = [regex]'^(?<howmany>[\d]+)(?<what>Mo|D)$'
246+
$timePieceMatch = $MaxTimeBehindValidator.Match($MaxTimeBehind)
247+
if (-not $timePieceMatch.Success) {
248+
Stop-Function -Category InvalidArgument -Message "MaxTimeBehind has an invalid syntax ('$MaxTimeBehind' could not be parsed). Use formats like '6Mo' for months or '180D' for days."
249+
return
250+
}
251+
$ParsedMaxTimeBehind = @{
252+
HowMany = [int]$timePieceMatch.Groups['howmany'].Value
253+
What = $timePieceMatch.Groups['what'].Value
254+
}
255+
}
217256
}
218257
process {
219258
if (Test-FunctionInterrupt) { return }
@@ -222,9 +261,11 @@ function Test-DbaBuild {
222261
$hiddenProps += 'SqlInstance'
223262
}
224263
if ($MinimumBuild) {
225-
$hiddenProps += 'MaxBehind', 'SPTarget', 'CUTarget', 'BuildTarget'
264+
$hiddenProps += 'MaxBehind', 'MaxTimeBehind', 'SPTarget', 'CUTarget', 'BuildTarget'
226265
} elseif ($MaxBehind -or $Latest) {
227-
$hiddenProps += 'MinimumBuild'
266+
$hiddenProps += 'MinimumBuild', 'MaxTimeBehind'
267+
} elseif ($MaxTimeBehind) {
268+
$hiddenProps += 'MinimumBuild', 'MaxBehind', 'SPTarget', 'CUTarget'
228269
}
229270
if ($Build) {
230271
$BuildVersions = Get-DbaBuild -Build $Build -Update:$Update -EnableException:$EnableException
@@ -310,10 +351,32 @@ function Test-DbaBuild {
310351
if ($inputbuild -ge $targetedBuild.VersionObject) {
311352
$compliant = $true
312353
}
354+
} elseif ($MaxTimeBehind) {
355+
$buildAnchor = "$($inputbuild.Major).$($inputbuild.Minor).*"
356+
if ($inputbuild.Minor -notin (0, 50)) {
357+
$buildAnchor = "$($inputbuild.Major).$($inputbuild.Minor - $inputbuild.Minor % 10).*"
358+
Write-Message -Level Debug -Message "Normalized Minor Version to account version aliases"
359+
}
360+
$IdxVersion = $IdxRef | Where-Object Version -Like $buildAnchor
361+
$today = (Get-Date).Date
362+
if ($ParsedMaxTimeBehind.What -eq "Mo") {
363+
$cutoffDate = $today.AddMonths(-$ParsedMaxTimeBehind.HowMany)
364+
} else {
365+
$cutoffDate = $today.AddDays(-$ParsedMaxTimeBehind.HowMany)
366+
}
367+
$targetedBuild = $IdxVersion | Where-Object { $_.ReleaseDate -and [datetime]$_.ReleaseDate -ge $cutoffDate } | Select-Object -First 1
368+
$currentBuildEntry = $IdxVersion | Where-Object VersionObject -eq $inputbuild
369+
if (-not $currentBuildEntry -or -not $currentBuildEntry.ReleaseDate) {
370+
Write-Message -Level Warning -Message "No ReleaseDate found for build $inputbuild - cannot determine time-based compliance"
371+
$compliant = $false
372+
} else {
373+
$compliant = ([datetime]$currentBuildEntry.ReleaseDate -ge $cutoffDate)
374+
}
313375
}
314376
Add-Member -InputObject $BuildVersion -MemberType NoteProperty -Name Compliant -Value $compliant
315377
Add-Member -InputObject $BuildVersion -MemberType NoteProperty -Name MinimumBuild -Value $MinimumBuild
316378
Add-Member -InputObject $BuildVersion -MemberType NoteProperty -Name MaxBehind -Value $MaxBehind
379+
Add-Member -InputObject $BuildVersion -MemberType NoteProperty -Name MaxTimeBehind -Value $MaxTimeBehind
317380
Add-Member -InputObject $BuildVersion -MemberType NoteProperty -Name SPTarget -Value $targetSPName
318381
Add-Member -InputObject $BuildVersion -MemberType NoteProperty -Name CUTarget -Value $targetCUName
319382
Add-Member -InputObject $BuildVersion -MemberType NoteProperty -Name BuildTarget -Value $targetedBuild.VersionObject

tests/Test-DbaBuild.Tests.ps1

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Describe $CommandName -Tag UnitTests {
1414
"Build",
1515
"MinimumBuild",
1616
"MaxBehind",
17+
"MaxTimeBehind",
1718
"Latest",
1819
"SqlInstance",
1920
"SqlCredential",
@@ -26,6 +27,28 @@ Describe $CommandName -Tag UnitTests {
2627
}
2728
}
2829

30+
Describe $CommandName -Tag UnitTests {
31+
Context "MaxTimeBehind compliance" {
32+
It "Identifies a build as compliant with a wide time window" {
33+
# SQL Server 2022 CU10 (16.0.4095) released 2023-11-16 - compliant within 36 months of today (2026-03-30)
34+
$result = Test-DbaBuild -Build "16.0.4095" -MaxTimeBehind "36Mo"
35+
$result.Compliant | Should -Be $true
36+
}
37+
38+
It "Identifies a build as non-compliant with a narrow time window" {
39+
# SQL Server 2022 CU10 (16.0.4095) released 2023-11-16 - more than 6 months old as of today (2026-03-30)
40+
$result = Test-DbaBuild -Build "16.0.4095" -MaxTimeBehind "6Mo"
41+
$result.Compliant | Should -Be $false
42+
}
43+
44+
It "Returns non-compliant when no ReleaseDate is available" {
45+
# An unrecognized build version has no release date in the index
46+
$result = Test-DbaBuild -Build "16.0.9999" -MaxTimeBehind "6Mo"
47+
$result.Compliant | Should -Be $false
48+
}
49+
}
50+
}
51+
2952
Describe $CommandName -Tag IntegrationTests {
3053
Context "Retired KBs" {
3154
It "Handles retired kbs" {

0 commit comments

Comments
 (0)