bin/Public/Invoke-sqmPatchAnalysis.ps1
|
<# .SYNOPSIS Vergleicht die installierte SQL Server-Version mit bekannten CU/SP-Builds. .DESCRIPTION Liest die installierte SQL Server-Version (ProductVersion) und vergleicht sie gegen eine eingebettete Referenztabelle bekannter Builds. Gibt an ob die Instanz aktuell ist, wie viele Builds sie hinter dem neuesten Stand liegt und gibt eine Patch-Empfehlung aus. .PARAMETER SqlInstance Eine oder mehrere SQL Server-Instanzen. Standard: lokaler Computername. Pipeline-faehig. .PARAMETER SqlCredential PSCredential fuer die Verbindung. .PARAMETER OutputPath Wenn angegeben, wird ein CSV-Bericht gespeichert. .PARAMETER EnableException Ausnahmen sofort ausloesen. .EXAMPLE Invoke-sqmPatchAnalysis -SqlInstance "SQL01" .EXAMPLE "SQL01","SQL02","SQL03" | Invoke-sqmPatchAnalysis .EXAMPLE Invoke-sqmPatchAnalysis -SqlInstance "SQL01","SQL02" -OutputPath "D:\Reports" .NOTES Die eingebettete Referenztabelle enthaelt bekannte Builds zum Zeitpunkt der Modulerstellung. Neuere CUs werden nicht automatisch nachgeladen. Referenz: https://sqlserverbuilds.blogspot.com/ #> function Invoke-sqmPatchAnalysis { [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory = $false, ValueFromPipeline = $true)] [string[]]$SqlInstance = @($env:COMPUTERNAME), [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential]$SqlCredential, [Parameter(Mandatory = $false)] [string]$OutputPath, [Parameter(Mandatory = $false)] [switch]$EnableException ) begin { $functionName = $MyInvocation.MyCommand.Name if (-not $script:dbatoolsAvailable) { $errMsg = "dbatools-Modul nicht gefunden." Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level "ERROR" throw $errMsg } # ============================================================ # Eingebettete Build-Referenztabelle # Format: MajorVersion -> geordnete Liste von [BuildNumber, Label, ReleaseDate] # ============================================================ $buildReference = @{ 13 = @( # SQL Server 2016 [PSCustomObject]@{ Build = [version]'13.0.1601.5'; Label = 'RTM'; ReleaseDate = '2016-06-01' } [PSCustomObject]@{ Build = [version]'13.0.4001.0'; Label = 'SP1'; ReleaseDate = '2016-11-16' } [PSCustomObject]@{ Build = [version]'13.0.4422.0'; Label = 'SP1 CU4'; ReleaseDate = '2017-05-15' } [PSCustomObject]@{ Build = [version]'13.0.4528.0'; Label = 'SP1 CU7'; ReleaseDate = '2017-11-22' } [PSCustomObject]@{ Build = [version]'13.0.5026.0'; Label = 'SP2'; ReleaseDate = '2018-04-24' } [PSCustomObject]@{ Build = [version]'13.0.5201.2'; Label = 'SP2 CU1'; ReleaseDate = '2018-05-30' } [PSCustomObject]@{ Build = [version]'13.0.5337.0'; Label = 'SP2 CU5'; ReleaseDate = '2018-09-18' } [PSCustomObject]@{ Build = [version]'13.0.5492.2'; Label = 'SP2 CU8'; ReleaseDate = '2019-03-19' } [PSCustomObject]@{ Build = [version]'13.0.5598.27';Label = 'SP2 CU13'; ReleaseDate = '2019-09-26' } [PSCustomObject]@{ Build = [version]'13.0.5830.85';Label = 'SP2 CU17'; ReleaseDate = '2020-11-20' } [PSCustomObject]@{ Build = [version]'13.0.6300.2'; Label = 'SP3'; ReleaseDate = '2021-09-15' } [PSCustomObject]@{ Build = [version]'13.0.6404.1'; Label = 'SP3 CU1'; ReleaseDate = '2021-10-18' } [PSCustomObject]@{ Build = [version]'13.0.6419.1'; Label = 'SP3 CU2'; ReleaseDate = '2022-01-19' } [PSCustomObject]@{ Build = [version]'13.0.6430.49';Label = 'SP3 CU3'; ReleaseDate = '2022-03-16' } [PSCustomObject]@{ Build = [version]'13.0.6435.1'; Label = 'SP3 CU4 (latest)'; ReleaseDate = '2022-09-22' } ) 14 = @( # SQL Server 2017 [PSCustomObject]@{ Build = [version]'14.0.1000.169'; Label = 'RTM'; ReleaseDate = '2017-10-02' } [PSCustomObject]@{ Build = [version]'14.0.3006.16'; Label = 'CU1'; ReleaseDate = '2017-10-24' } [PSCustomObject]@{ Build = [version]'14.0.3030.27'; Label = 'CU4'; ReleaseDate = '2018-02-20' } [PSCustomObject]@{ Build = [version]'14.0.3048.4'; Label = 'CU6'; ReleaseDate = '2018-04-17' } [PSCustomObject]@{ Build = [version]'14.0.3076.1'; Label = 'CU8'; ReleaseDate = '2018-06-21' } [PSCustomObject]@{ Build = [version]'14.0.3162.1'; Label = 'CU12'; ReleaseDate = '2018-10-25' } [PSCustomObject]@{ Build = [version]'14.0.3192.2'; Label = 'CU14'; ReleaseDate = '2019-03-25' } [PSCustomObject]@{ Build = [version]'14.0.3238.1'; Label = 'CU17'; ReleaseDate = '2019-10-08' } [PSCustomObject]@{ Build = [version]'14.0.3294.2'; Label = 'CU20'; ReleaseDate = '2020-04-07' } [PSCustomObject]@{ Build = [version]'14.0.3335.7'; Label = 'CU22'; ReleaseDate = '2020-09-10' } [PSCustomObject]@{ Build = [version]'14.0.3381.3'; Label = 'CU26'; ReleaseDate = '2021-09-14' } [PSCustomObject]@{ Build = [version]'14.0.3421.10'; Label = 'CU29'; ReleaseDate = '2022-03-30' } [PSCustomObject]@{ Build = [version]'14.0.3436.1'; Label = 'CU31'; ReleaseDate = '2022-09-20' } [PSCustomObject]@{ Build = [version]'14.0.3460.9'; Label = 'CU39'; ReleaseDate = '2023-11-14' } [PSCustomObject]@{ Build = [version]'14.0.3465.1'; Label = 'CU40 (latest)'; ReleaseDate = '2024-04-11' } ) 15 = @( # SQL Server 2019 [PSCustomObject]@{ Build = [version]'15.0.2000.5'; Label = 'RTM'; ReleaseDate = '2019-11-04' } [PSCustomObject]@{ Build = [version]'15.0.4003.23'; Label = 'CU1'; ReleaseDate = '2020-01-07' } [PSCustomObject]@{ Build = [version]'15.0.4013.40'; Label = 'CU2'; ReleaseDate = '2020-02-13' } [PSCustomObject]@{ Build = [version]'15.0.4023.6'; Label = 'CU3'; ReleaseDate = '2020-03-12' } [PSCustomObject]@{ Build = [version]'15.0.4033.1'; Label = 'CU4'; ReleaseDate = '2020-03-31' } [PSCustomObject]@{ Build = [version]'15.0.4043.16'; Label = 'CU5'; ReleaseDate = '2020-06-22' } [PSCustomObject]@{ Build = [version]'15.0.4053.23'; Label = 'CU6'; ReleaseDate = '2020-08-04' } [PSCustomObject]@{ Build = [version]'15.0.4063.15'; Label = 'CU7'; ReleaseDate = '2020-09-02' } [PSCustomObject]@{ Build = [version]'15.0.4073.23'; Label = 'CU8'; ReleaseDate = '2020-10-01' } [PSCustomObject]@{ Build = [version]'15.0.4102.2'; Label = 'CU10'; ReleaseDate = '2021-06-08' } [PSCustomObject]@{ Build = [version]'15.0.4153.1'; Label = 'CU14'; ReleaseDate = '2021-11-22' } [PSCustomObject]@{ Build = [version]'15.0.4198.2'; Label = 'CU16'; ReleaseDate = '2022-04-18' } [PSCustomObject]@{ Build = [version]'15.0.4236.7'; Label = 'CU18'; ReleaseDate = '2022-09-28' } [PSCustomObject]@{ Build = [version]'15.0.4298.1'; Label = 'CU22'; ReleaseDate = '2023-08-14' } [PSCustomObject]@{ Build = [version]'15.0.4335.1'; Label = 'CU25'; ReleaseDate = '2024-02-15' } [PSCustomObject]@{ Build = [version]'15.0.4385.2'; Label = 'CU29 (latest)'; ReleaseDate = '2024-12-12' } ) 16 = @( # SQL Server 2022 [PSCustomObject]@{ Build = [version]'16.0.1000.6'; Label = 'RTM'; ReleaseDate = '2022-11-16' } [PSCustomObject]@{ Build = [version]'16.0.4003.1'; Label = 'CU1'; ReleaseDate = '2023-02-16' } [PSCustomObject]@{ Build = [version]'16.0.4015.1'; Label = 'CU2'; ReleaseDate = '2023-03-15' } [PSCustomObject]@{ Build = [version]'16.0.4025.1'; Label = 'CU3'; ReleaseDate = '2023-04-13' } [PSCustomObject]@{ Build = [version]'16.0.4035.4'; Label = 'CU4'; ReleaseDate = '2023-05-11' } [PSCustomObject]@{ Build = [version]'16.0.4045.3'; Label = 'CU5'; ReleaseDate = '2023-06-15' } [PSCustomObject]@{ Build = [version]'16.0.4055.4'; Label = 'CU6'; ReleaseDate = '2023-07-13' } [PSCustomObject]@{ Build = [version]'16.0.4065.3'; Label = 'CU7'; ReleaseDate = '2023-08-10' } [PSCustomObject]@{ Build = [version]'16.0.4075.1'; Label = 'CU8'; ReleaseDate = '2023-09-14' } [PSCustomObject]@{ Build = [version]'16.0.4085.2'; Label = 'CU9'; ReleaseDate = '2023-10-12' } [PSCustomObject]@{ Build = [version]'16.0.4095.4'; Label = 'CU10'; ReleaseDate = '2023-11-16' } [PSCustomObject]@{ Build = [version]'16.0.4105.2'; Label = 'CU11'; ReleaseDate = '2024-01-11' } [PSCustomObject]@{ Build = [version]'16.0.4115.5'; Label = 'CU12'; ReleaseDate = '2024-03-14' } [PSCustomObject]@{ Build = [version]'16.0.4125.3'; Label = 'CU13'; ReleaseDate = '2024-05-16' } [PSCustomObject]@{ Build = [version]'16.0.4135.4'; Label = 'CU14'; ReleaseDate = '2024-07-11' } [PSCustomObject]@{ Build = [version]'16.0.4145.4'; Label = 'CU15'; ReleaseDate = '2024-09-12' } [PSCustomObject]@{ Build = [version]'16.0.4155.4'; Label = 'CU16'; ReleaseDate = '2024-11-14' } [PSCustomObject]@{ Build = [version]'16.0.4165.4'; Label = 'CU17 (latest)'; ReleaseDate = '2025-01-16' } ) } $allResults = [System.Collections.Generic.List[PSCustomObject]]::new() Invoke-sqmLogging -Message "Starte $functionName fuer $($SqlInstance.Count) Instanz(en)" -FunctionName $functionName -Level "INFO" } process { foreach ($inst in $SqlInstance) { try { $connParams = @{ SqlInstance = $inst } if ($SqlCredential) { $connParams['SqlCredential'] = $SqlCredential } $versionSql = @" SELECT CAST(SERVERPROPERTY('ProductVersion') AS NVARCHAR(50)) AS ProductVersion, CAST(SERVERPROPERTY('ProductLevel') AS NVARCHAR(50)) AS ProductLevel, CAST(SERVERPROPERTY('ProductUpdateLevel') AS NVARCHAR(50)) AS ProductUpdateLevel, CAST(SERVERPROPERTY('Edition') AS NVARCHAR(100)) AS Edition, CAST(SERVERPROPERTY('EngineEdition') AS INT) AS EngineEdition, @@VERSION AS FullVersion "@ $vRow = Invoke-DbaQuery @connParams -Database master -Query $versionSql -ErrorAction Stop $installed = [version]$vRow.ProductVersion $major = $installed.Major $sqlYear = switch ($major) { 13 { 'SQL Server 2016' } 14 { 'SQL Server 2017' } 15 { 'SQL Server 2019' } 16 { 'SQL Server 2022' } default { "SQL Server (Major $major)" } } # Builds fuer diese Version $builds = $buildReference[$major] if (-not $builds) { $allResults.Add([PSCustomObject]@{ SqlInstance = $inst ProductVersion = $vRow.ProductVersion ProductLevel = $vRow.ProductLevel Edition = $vRow.Edition MajorVersion = $sqlYear LatestKnownBuild = 'N/A' LatestKnownLabel = 'N/A' IsLatest = $null BuildsBehind = $null PatchStatus = 'Unknown' Recommendation = "Keine Referenz fuer Major Version $major vorhanden." }) continue } $latestBuild = $builds[-1] $isLatest = ($installed -ge $latestBuild.Build) # Wie viele bekannte Builds ist die Instanz hinter dem neuesten Stand? $buildsBehind = 0 for ($i = 0; $i -lt $builds.Count; $i++) { if ($installed -lt $builds[$i].Build) { $buildsBehind = $builds.Count - $i; break } } # Naechsthoeheren bekannten Build bestimmen $nextBuild = $builds | Where-Object { $_.Build -gt $installed } | Select-Object -First 1 $patchStatus = if ($isLatest) { 'UpToDate' } elseif ($buildsBehind -ge 5) { 'Critical' } elseif ($buildsBehind -ge 3) { 'MajorUpdate' } else { 'MinorUpdate' } $recommendation = if ($isLatest) { "Aktuell - kein Patch erforderlich." } else { $nextLabel = if ($nextBuild) { $nextBuild.Label } else { $latestBuild.Label } "$buildsBehind Build(s) hinter aktuellem Stand. Naechstes Update: $nextLabel. Empfehlung: auf $($latestBuild.Label) ($($latestBuild.Build)) aktualisieren." } Invoke-sqmLogging -Message "[$inst] $sqlYear $($vRow.ProductVersion) - $patchStatus ($buildsBehind builds behind)" -FunctionName $functionName -Level "INFO" $allResults.Add([PSCustomObject]@{ SqlInstance = $inst ProductVersion = $vRow.ProductVersion ProductLevel = $vRow.ProductLevel UpdateLevel = $vRow.ProductUpdateLevel Edition = $vRow.Edition MajorVersion = $sqlYear InstalledBuild = $installed.ToString() LatestKnownBuild = $latestBuild.Build.ToString() LatestKnownLabel = $latestBuild.Label LatestReleaseDate = $latestBuild.ReleaseDate IsLatest = $isLatest BuildsBehind = $buildsBehind PatchStatus = $patchStatus Recommendation = $recommendation }) } catch { $errMsg = "[$inst] Fehler: $($_.Exception.Message)" Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level "ERROR" $allResults.Add([PSCustomObject]@{ SqlInstance = $inst PatchStatus = 'Error' Recommendation = $errMsg }) if ($EnableException) { throw } } } } end { if ($OutputPath -and $allResults.Count -gt 0) { if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } $ts = Get-Date -Format 'yyyyMMdd_HHmsqm' $csvFile = Join-Path $OutputPath "PatchAnalysis_${ts}.csv" $allResults | Export-Csv -Path $csvFile -NoTypeInformation -Encoding UTF8 -Force Invoke-sqmLogging -Message "Patch-Analyse gespeichert: $csvFile" -FunctionName $functionName -Level "INFO" } $critical = @($allResults | Where-Object { $_.PatchStatus -eq 'Critical' }).Count $outdated = @($allResults | Where-Object { $_.PatchStatus -in @('MajorUpdate','MinorUpdate') }).Count Invoke-sqmLogging -Message "$functionName abgeschlossen: $($allResults.Count) Instanz(en), $critical Critical, $outdated zu aktualisieren." -FunctionName $functionName -Level "INFO" return $allResults.ToArray() } } |