@@ -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
0 commit comments