4848. PARAMETER NoHistory
4949 Do not append the current run to the history (forecast still uses whatever history exists).
5050
51+ . PARAMETER SeedFromBackupHistory
52+ Bootstrap (method B2): while the snapshot history has fewer than -MinDataPoints points for a volume,
53+ derive a fallback growth rate from msdb full-backup sizes (data growth trend per database, mapped to
54+ volumes by data-file location). Used only as long as B1 is insufficient; reported with
55+ ForecastBasis='BackupHistory' and confidence Low. Requires read access to msdb.dbo.backupset.
56+
5157. PARAMETER ContinueOnError
5258 Continue on error for an instance (otherwise the error is thrown).
5359
@@ -99,6 +105,8 @@ function Get-sqmDiskSpaceReport
99105 [Parameter (Mandatory = $false )]
100106 [switch ]$NoHistory ,
101107 [Parameter (Mandatory = $false )]
108+ [switch ]$SeedFromBackupHistory ,
109+ [Parameter (Mandatory = $false )]
102110 [switch ]$ContinueOnError ,
103111 [Parameter (Mandatory = $false )]
104112 [switch ]$EnableException ,
@@ -277,6 +285,23 @@ ORDER BY vs.volume_mount_point;
277285 }
278286 }
279287
288+ # 2b. Bootstrap (B2): Wachstumsrate aus msdb-Backup-Historie, solange B1 noch zu wenige
289+ # Snapshots hat. Greift in der Detailschleife nur, wenn der B1-Forecast 'Insufficient' ist.
290+ $backupSeed = @ { }
291+ if ($SeedFromBackupHistory )
292+ {
293+ try
294+ {
295+ $backupSeed = Get-sqmBackupGrowthSeed - ConnParams $connParams - Days ([math ]::Max($HistoryDays , 90 )) - Volumes $volRows
296+ Invoke-sqmLogging - Message " [$instance ] Backup-Historie-Seed: $ ( $backupSeed.Keys.Count ) Volume(s) mit Trend." - FunctionName $functionName - Level " VERBOSE"
297+ }
298+ catch
299+ {
300+ Invoke-sqmLogging - Message " [$instance ] Backup-Historie-Seed nicht ermittelbar: $ ( $_.Exception.Message ) " - FunctionName $functionName - Level " WARNING"
301+ $backupSeed = @ { }
302+ }
303+ }
304+
280305 # 3. Detailzeilen aufbereiten (Prognose je Volume aus der Historie)
281306 foreach ($vol in $volRows )
282307 {
@@ -289,10 +314,24 @@ ORDER BY vs.volume_mount_point;
289314 $volHist = @ ($histForecast | Where-Object { $_.MountPoint -eq $mount })
290315 $fc = Get-sqmVolumeForecast - History $volHist - FreeGB $freeGB - MinDataPoints $MinDataPoints
291316
317+ $basis = $fc.Basis
318+ $conf = $fc.Confidence
319+ $dpoints = $fc.DataPoints
292320 $growthPerDay = if ($fc.Basis -eq ' History' -and $fc.SlopePerDayGB -gt 0 ) { $fc.SlopePerDayGB } else { 0 }
293321 $daysUntilFull = if ($fc.Basis -eq ' History' ) { $fc.DaysUntilFull } else { $null }
294322 $growthGB = if ($fc.Basis -eq ' History' ) { $fc.GrowthWindowGB } else { $null }
295323
324+ # Bootstrap (B2) nur solange B1 noch nicht greift.
325+ if ($fc.Basis -ne ' History' -and $backupSeed.ContainsKey ($mount ) -and $backupSeed [$mount ].SlopePerDayGB -gt 0 )
326+ {
327+ $seedSlope = $backupSeed [$mount ].SlopePerDayGB
328+ $growthPerDay = $seedSlope
329+ $daysUntilFull = [math ]::Round($freeGB / $seedSlope , 0 )
330+ $growthGB = [math ]::Round($seedSlope * $HistoryDays , 2 )
331+ $basis = ' BackupHistory'
332+ $conf = ' Low'
333+ }
334+
296335 $status = if ($freePct -le $CriticalThresholdPct ) { ' Critical' }
297336 elseif ($freePct -le $WarnThresholdPct ) { ' Warning' }
298337 elseif ($daysUntilFull -and $daysUntilFull -le 30 ) { ' Warning' }
@@ -310,26 +349,28 @@ ORDER BY vs.volume_mount_point;
310349 GrowthPerDayGB = if ($growthPerDay -gt 0 ) { $growthPerDay } else { $null }
311350 DaysUntilFull = $daysUntilFull
312351 HistoryDays = $HistoryDays
313- DataPoints = $fc .DataPoints
314- ForecastConfidence = $fc .Confidence
315- ForecastBasis = $fc .Basis
352+ DataPoints = $dpoints
353+ ForecastConfidence = $conf
354+ ForecastBasis = $basis
316355 Status = $status
317356 Message = switch ($status )
318357 {
319358 ' Critical' { " Kritisch: nur $freePct % frei ($freeGB GB)!" }
320359 ' Warning' {
321360 if ($daysUntilFull -and $daysUntilFull -le 30 )
322361 {
323- " Warnung: voll in ca. $daysUntilFull Tagen (Konfidenz $ ( $fc.Confidence ) )."
362+ $src = if ($basis -eq ' BackupHistory' ) { ' Bootstrap Backup-Historie' } else { " Konfidenz $conf " }
363+ " Warnung: voll in ca. $daysUntilFull Tagen ($src )."
324364 }
325365 else { " Warnung: nur $freePct % frei ($freeGB GB)." }
326366 }
327367 default {
328- if ($fc .Basis -ne ' History ' )
368+ switch ($basis )
329369 {
330- " OK: $freePct % frei ($freeGB GB). Prognose sammelt noch Daten ($ ( $fc.DataPoints ) von $MinDataPoints Laeufen)."
370+ ' BackupHistory' { " OK: $freePct % frei ($freeGB GB). Prognose (Bootstrap Backup-Historie): voll in ca. $daysUntilFull Tagen." }
371+ ' History' { " OK: $freePct % frei ($freeGB GB)." }
372+ default { " OK: $freePct % frei ($freeGB GB). Prognose sammelt noch Daten ($dpoints von $MinDataPoints Laeufen)." }
331373 }
332- else { " OK: $freePct % frei ($freeGB GB)." }
333374 }
334375 }
335376 })
@@ -379,7 +420,7 @@ ORDER BY vs.volume_mount_point;
379420 $daysDisplay = if ($e.DaysUntilFull ) { $e.DaysUntilFull }
380421 elseif ($e.ForecastBasis -ne ' History' ) { " $ ( $e.DataPoints ) /$MinDataPoints " }
381422 else { ' -' }
382- $confDisplay = if ($e.ForecastBasis -eq ' History' ) { $e.ForecastConfidence } else { ' - ' }
423+ $confDisplay = switch ($e.ForecastBasis ) { ' History' { $e.ForecastConfidence } ' BackupHistory ' { ' Boot ' } default { ' - ' } }
383424 $lines.Add ((" {0,-6} {1,-20} {2,-8} {3,-8} {4,-8} {5,-7} {6,-10} {7,-9} {8,-6} {9}" -f
384425 $e.Status , $e.MountPoint , $e.TotalGB , $e.UsedGB , $e.FreeGB ,
385426 " $ ( $e.FreePct ) %" , $perDayDisplay , $daysDisplay , $confDisplay , $e.Message ))
@@ -396,7 +437,7 @@ ORDER BY vs.volume_mount_point;
396437 $cls = switch ($e.Status ) { ' Critical' { ' crit' } ' Warning' { ' warn' } default { ' ok' } }
397438 $perDayDisplay = if ($e.GrowthPerDayGB ) { $e.GrowthPerDayGB } elseif ($e.ForecastBasis -ne ' History' ) { ' sammelt' } else { ' stabil' }
398439 $daysDisplay = if ($e.DaysUntilFull ) { $e.DaysUntilFull } elseif ($e.ForecastBasis -ne ' History' ) { " $ ( $e.DataPoints ) /$MinDataPoints " } else { ' -' }
399- $confDisplay = if ($e.ForecastBasis -eq ' History' ) { $e.ForecastConfidence } else { ' - ' }
440+ $confDisplay = switch ($e.ForecastBasis ) { ' History' { $e.ForecastConfidence } ' BackupHistory ' { ' Boot ' } default { ' - ' } }
400441 $mp = [string ]$e.MountPoint -replace ' &' , ' &' -replace ' <' , ' <' -replace ' >' , ' >'
401442 $rowsHtml += " <tr><td class='$cls '>$ ( $e.Status ) </td><td>$mp </td><td>$ ( $e.TotalGB ) </td><td>$ ( $e.UsedGB ) </td><td>$ ( $e.FreeGB ) </td><td>$ ( $e.FreePct ) %</td><td>$perDayDisplay </td><td>$daysDisplay </td><td>$confDisplay </td></tr>`n "
402443 }
@@ -530,3 +571,79 @@ function Get-sqmVolumeForecast
530571 Basis = ' History'
531572 }
532573}
574+
575+ # ---------------------------------------------------------------------------
576+ # Private Hilfsfunktion: Bootstrap-Wachstumsrate je Volume aus msdb-Backup-Historie (Methode B2)
577+ # Leitet pro DB den Daten-Wachstumstrend aus Full-Backup-Groessen ab und verteilt die Rate
578+ # proportional zur Datendatei-Groesse auf die Volumes (volume_mount_point). Rueckgabe:
579+ # Hashtable mountpoint -> @{ SlopePerDayGB; Dbs }. Fehler werden an den Aufrufer durchgereicht.
580+ # ---------------------------------------------------------------------------
581+ function Get-sqmBackupGrowthSeed
582+ {
583+ param (
584+ [hashtable ]$ConnParams ,
585+ [int ]$Days = 90 ,
586+ [object []]$Volumes
587+ )
588+
589+ $seed = @ { }
590+
591+ $backupQuery = @"
592+ SELECT bs.database_name AS DatabaseName,
593+ bs.backup_finish_date AS FinishDate,
594+ bs.backup_size / 1073741824.0 AS BackupGB
595+ FROM msdb.dbo.backupset bs
596+ WHERE bs.type = 'D' AND bs.is_copy_only = 0 AND bs.backup_size > 0
597+ AND bs.backup_finish_date >= DATEADD(DAY, -$Days , GETDATE())
598+ ORDER BY bs.database_name, bs.backup_finish_date;
599+ "@
600+ $backups = @ (Invoke-DbaQuery @ConnParams - Query $backupQuery - EnableException:$true )
601+ if ($backups.Count -eq 0 ) { return $seed }
602+
603+ # Datendateien (ROWS) -> Volume ueber laengsten Pfad-Praefix der Mount Points.
604+ $fileRows = @ (Invoke-DbaQuery @ConnParams - Query " SELECT DB_NAME(database_id) AS DatabaseName, physical_name AS PhysicalName, size * 8.0 / 1024 / 1024 AS FileGB FROM sys.master_files WHERE type = 0" - EnableException:$true )
605+ $mounts = @ ($Volumes | ForEach-Object { [string ]$_.MountPoint } | Sort-Object { $_.Length } - Descending)
606+
607+ $dbMountGB = @ { } # "$db|$mount" -> GB
608+ $dbTotalGB = @ { } # "$db" -> GB
609+ foreach ($fl in $fileRows )
610+ {
611+ $path = [string ]$fl.PhysicalName
612+ $mt = $null
613+ foreach ($m in $mounts ) { if ($path -and $m -and $path.StartsWith ($m , [System.StringComparison ]::OrdinalIgnoreCase)) { $mt = $m ; break } }
614+ if (-not $mt ) { continue }
615+ $g = [double ]$fl.FileGB
616+ $db = [string ]$fl.DatabaseName
617+ $dbMountGB [" $db |$mt " ] = [double ]($dbMountGB [" $db |$mt " ]) + $g
618+ $dbTotalGB [$db ] = [double ]($dbTotalGB [$db ]) + $g
619+ }
620+
621+ # Pro DB die Daten-Wachstumsrate (GB/Tag) aus dem Backup-Groessen-Trend bestimmen
622+ # (min. 3 Punkte genuegen fuer einen Bootstrap) und proportional auf die Volumes verteilen.
623+ foreach ($grp in ($backups | Group-Object DatabaseName))
624+ {
625+ $db = [string ]$grp.Name
626+ $total = [double ]($dbTotalGB [$db ])
627+ if ($total -le 0 ) { continue }
628+
629+ $pts = @ ($grp.Group | ForEach-Object { [PSCustomObject ]@ { Timestamp = [datetime ]$_.FinishDate ; UsedGB = [double ]$_.BackupGB } })
630+ $dbFc = Get-sqmVolumeForecast - History $pts - FreeGB 0 - MinDataPoints 3
631+ if ($dbFc.Basis -ne ' History' -or -not $dbFc.SlopePerDayGB -or $dbFc.SlopePerDayGB -le 0 ) { continue }
632+ $slope = [double ]$dbFc.SlopePerDayGB
633+
634+ foreach ($m in $mounts )
635+ {
636+ $key = " $db |$m "
637+ if ($dbMountGB.ContainsKey ($key ))
638+ {
639+ $frac = [double ]($dbMountGB [$key ]) / $total
640+ if (-not $seed.ContainsKey ($m )) { $seed [$m ] = @ { SlopePerDayGB = 0.0 ; Dbs = 0 } }
641+ $seed [$m ].SlopePerDayGB += $slope * $frac
642+ $seed [$m ].Dbs += 1
643+ }
644+ }
645+ }
646+
647+ foreach ($k in @ ($seed.Keys )) { $seed [$k ].SlopePerDayGB = [math ]::Round($seed [$k ].SlopePerDayGB, 3 ) }
648+ return $seed
649+ }
0 commit comments