Public/Invoke-sqmSetupReport.ps1
|
<#
.SYNOPSIS Professional SQL Server Setup Report with critical issues, security, and database overview. .DESCRIPTION Comprehensive setup report including: - CRITICAL ISSUES (SA, Backups, MaxMemory) - SECURITY (Sysadmins, Logins with roles, CLR, xp_cmdshell) - INFRASTRUCTURE (Service Accounts, SPNs, Splunk) - CONFIGURATION (MAXDOP, Cost Threshold, TempDB) - DATABASES (DBOs, Recovery Models, Last Backups) .PARAMETER SqlInstance SQL Server instance. Default: local computer name. .PARAMETER SqlCredential Credentials for SQL connection. .PARAMETER OutputPath Output path for HTML report. .PARAMETER PassThru Return the file path. .PARAMETER NoOpen Don't open the report in browser. .EXAMPLE Invoke-sqmSetupReport -SqlInstance "SQL01" #> function Invoke-sqmSetupReport { [CmdletBinding()] [OutputType([string])] param ( [Parameter(Mandatory = $false, Position = 0)] [string]$SqlInstance, [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential]$SqlCredential, [Parameter(Mandatory = $false)] [string]$OutputPath, [Parameter(Mandatory = $false)] [switch]$PassThru, [Parameter(Mandatory = $false)] [switch]$NoOpen ) begin { $functionName = $MyInvocation.MyCommand.Name if (-not $PSBoundParameters.ContainsKey('SqlInstance') -or [string]::IsNullOrWhiteSpace($SqlInstance)) { $SqlInstance = $env:COMPUTERNAME } if (-not $PSBoundParameters.ContainsKey('OutputPath') -or [string]::IsNullOrWhiteSpace($OutputPath)) { $OutputPath = Get-sqmConfig -Key 'OutputPath' if (-not $OutputPath) { $OutputPath = "C:\System\WinSrvLog\MSSQL" } } Invoke-sqmLogging -Message "Starte $functionName auf $SqlInstance" -FunctionName $functionName -Level 'INFO' } process { try { $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $safeInstance = $SqlInstance -replace '[\\:]', '_' $datestamp = Get-Date -Format 'yyyyMMdd_HHmm' $server = $null # Connect to SQL Server try { $server = Connect-DbaInstance -SqlInstance $SqlInstance -SqlCredential $SqlCredential -ErrorAction Stop } catch { throw "Verbindung zu $SqlInstance fehlgeschlagen: $($_.Exception.Message)" } # ========================================== # CRITICAL ISSUES # ========================================== # SA Account Status $saLogin = Get-DbaLogin -SqlInstance $server -Login 'sa' -ErrorAction SilentlyContinue if (-not $saLogin) { $saSid = '0x01' $saLogin = Get-DbaLogin -SqlInstance $server | Where-Object { $_.SID -eq $saSid } } $saName = if ($saLogin) { $saLogin.Name } else { 'NOT FOUND' } $saDisabled = if ($saLogin) { $saLogin.IsDisabled } else { $null } $saIsRenamed = ($saName -ne 'sa') $saStatus = if ($saIsRenamed -and $saDisabled) { 'OK (renamed & disabled)' } elseif ($saIsRenamed) { 'OK (renamed)' } elseif ($saDisabled) { 'WARNING (disabled only)' } else { 'CRITICAL (not renamed)' } $saStatusColor = if ($saName -ne 'sa' -or $saDisabled) { 'green' } else { 'red' } # Backup Jobs Status $backupJobs = Get-DbaAgentJob -SqlInstance $server -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*backup*' -or $_.Name -like '*bkp*' } $backupJobCount = @($backupJobs).Count $backupJobsEnabled = @($backupJobs | Where-Object { $_.IsEnabled }).Count $backupJobStatus = if ($backupJobCount -eq 0) { 'NO BACKUP JOBS' } elseif ($backupJobsEnabled -eq $backupJobCount) { "OK ($backupJobCount jobs)" } else { "WARNING ($backupJobsEnabled/$backupJobCount enabled)" } $backupStatusColor = if ($backupJobCount -gt 0 -and $backupJobsEnabled -eq $backupJobCount) { 'green' } else { 'orange' } # Max Memory (synchronized with Test-sqmMaxMemory logic) # WICHTIG: sowohl max server memory (ConfigValue) als auch SMO Server.PhysicalMemory # sind in MB. Frueher wurde PhysicalMemory faelschlich durch 1024 geteilt (= GB), # wodurch jeder konfigurierte Wert als "TOO HIGH" erschien. $maxMem = $server.Configuration.MaxServerMemory.ConfigValue $totalMem = [int]$server.PhysicalMemory if (-not $totalMem -or $totalMem -le 0) { try { $totalMem = [math]::Round((Get-WmiObject -Class Win32_ComputerSystem -ErrorAction Stop).TotalPhysicalMemory / 1MB) } catch { } } $unconfiguredValue = 2147483647 $lowerBound = [math]::Round($totalMem * 0.85) $upperBound = [math]::Round($totalMem * 0.95) if ($maxMem -eq $unconfiguredValue) { $maxMemStatus = "NOT CONFIGURED (default)" $maxMemColor = 'orange' } elseif ($maxMem -gt $upperBound) { $maxMemStatus = "TOO HIGH ($maxMem MB > $upperBound MB)" $maxMemColor = 'orange' } elseif ($maxMem -lt $lowerBound) { $maxMemStatus = "TOO LOW ($maxMem MB < $lowerBound MB)" $maxMemColor = 'orange' } else { $maxMemStatus = "OK ($maxMem MB)" $maxMemColor = 'green' } # ========================================== # SECURITY CHECKS # ========================================== # Sysadmin Accounts # Robuste Ermittlung ueber die Rollenmitgliedschaft (Get-DbaLogin hat keine # zuverlaessige IsSysAdmin-Eigenschaft -> Liste blieb sonst leer). # sys.server_principals.sid liefert zugleich die SID fuer die BUILTIN-Erkennung. $sysadminLogins = @() try { $sysadminQuery = @" SELECT sp.name AS Name, sp.sid AS Sid FROM sys.server_role_members rm JOIN sys.server_principals r ON rm.role_principal_id = r.principal_id JOIN sys.server_principals sp ON rm.member_principal_id = sp.principal_id WHERE r.type = 'R' AND r.name = N'sysadmin' ORDER BY sp.name "@ $sysadminLogins = @(Invoke-DbaQuery -SqlInstance $server -Query $sysadminQuery -ErrorAction Stop) } catch { # Fallback: dbatools-Rollenmitglieder (ohne SID -> nur Namensabgleich) try { $sysadminLogins = @(Get-DbaServerRoleMember -SqlInstance $server -ServerRole 'sysadmin' -ErrorAction Stop | ForEach-Object { [PSCustomObject]@{ Name = $_.Name; Sid = $null } }) } catch { } } $sysadmins = @($sysadminLogins | Select-Object -ExpandProperty Name | Where-Object { $_ }) # Warnung: BUILTIN\Administrators (lokalisiert z.B. VORDEFINIERT\Administratoren) als sysadmin # ist ein Least-Privilege-Verstoss (jeder lokale Admin wird damit zum SQL-sysadmin). # Erkennung primaer ueber die Well-Known-SID S-1-5-32-544 (sprachunabhaengig), # Fallback ueber den Namen. $builtinAdmins = @() foreach ($sa in $sysadminLogins) { $isBuiltin = $false try { if ($sa.Sid) { $sidStr = (New-Object System.Security.Principal.SecurityIdentifier(([byte[]]$sa.Sid), 0)).Value if ($sidStr -eq 'S-1-5-32-544') { $isBuiltin = $true } } } catch { } if (-not $isBuiltin -and $sa.Name -match '^(BUILTIN|VORDEFINIERT|INTEGR)\\.*Admin') { $isBuiltin = $true } if ($isBuiltin) { $builtinAdmins += $sa.Name } } $builtinAdmins = @($builtinAdmins | Select-Object -Unique) $hasBuiltinAdmins = $builtinAdmins.Count -gt 0 $sysadminColor = if ($hasBuiltinAdmins) { 'red' } else { '' } $sysadminWarning = if ($hasBuiltinAdmins) { "WARNUNG: $($builtinAdmins -join ', ') hat sysadmin-Rechte - fuer Least Privilege entfernen" } else { '' } # Logins with Server Roles (only server-level roles) $advancedLogins = @() try { $serverRoles = @('sysadmin', 'serveradmin', 'securityadmin', 'processadmin', 'dbcreator', 'diskadmin') $allLogins = Get-DbaLogin -SqlInstance $server -ErrorAction SilentlyContinue foreach ($login in $allLogins) { $roles = @() foreach ($role in $serverRoles) { try { $query = "SELECT IS_SRVROLEMEMBER('$role', '$($login.Name)') AS IsMember" $result = Invoke-DbaQuery -SqlInstance $server -Query $query -ErrorAction SilentlyContinue if ($result -and $result.IsMember -eq 1) { $roles += $role } } catch { } } if ($roles.Count -gt 0) { $advancedLogins += "$($login.Name) [$($roles -join ', ')]" } } } catch { } if ($advancedLogins.Count -eq 0) { $advancedLogins = @('None with server roles') } # CLR Status $clrEnabled = $server.Configuration.IsSqlClrEnabled.ConfigValue $clrStatus = if ($clrEnabled) { 'ENABLED (check if needed)' } else { 'OK (disabled)' } $clrColor = if ($clrEnabled) { 'orange' } else { 'green' } # xp_cmdshell Status $xpCmdEnabled = $server.Configuration.XPCmdShell.ConfigValue $xpStatus = if ($xpCmdEnabled) { 'ENABLED (security risk)' } else { 'OK (disabled)' } $xpColor = if ($xpCmdEnabled) { 'orange' } else { 'green' } # ========================================== # SERVICE ACCOUNTS & INFRASTRUCTURE # ========================================== # Service Accounts $serviceAccounts = @() try { # SQL Server Service $sqlSvc = Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "MSSQL|SQL Server" -and $_.Name -notmatch "Agent|Browser" } | Select-Object -First 1 if ($sqlSvc) { $svcInfo = Get-CimInstance -ClassName Win32_Service -Filter "Name='$($sqlSvc.Name)'" -ErrorAction SilentlyContinue if ($svcInfo) { $serviceAccounts += "SQL Server: $($svcInfo.StartName)" } } # SQL Agent Service $agentSvc = Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Name -match "SQLSERVERAGENT|SQLAgent" } | Select-Object -First 1 if ($agentSvc) { $svcInfo = Get-CimInstance -ClassName Win32_Service -Filter "Name='$($agentSvc.Name)'" -ErrorAction SilentlyContinue if ($svcInfo) { $serviceAccounts += "SQL Agent: $($svcInfo.StartName)" } } } catch { } if ($serviceAccounts.Count -eq 0) { $serviceAccounts = @('Unable to determine') } # SPN Status (List all SPNs + overall OK/Warning summary) $spnLines = @('Not checked') $spnStatus = 'Not checked' $spnColor = 'orange' try { $spnReport = Get-sqmSpnReport -ComputerName $env:COMPUTERNAME -ErrorAction SilentlyContinue if ($spnReport) { if ($spnReport.DetailRows) { $spnDetails = @() foreach ($row in $spnReport.DetailRows) { $spnDetails += "$($row.SPN) [$($row.Status)]" } $spnLines = if ($spnDetails.Count -gt 0) { $spnDetails } else { @('No SPNs found') } $cntOk = @($spnReport.DetailRows | Where-Object { $_.Status -eq 'OK' }).Count $cntMissing = @($spnReport.DetailRows | Where-Object { $_.Status -eq 'Missing' }).Count $cntUnexp = @($spnReport.DetailRows | Where-Object { $_.Status -eq 'Unexpected' }).Count $cntTotal = @($spnReport.DetailRows).Count } else { $cntOk = 0; $cntMissing = 0; $cntUnexp = 0; $cntTotal = 0 } # Gesamtstatus: bevorzugt das Status-Feld des Reports, sonst aus den Zaehlern ableiten switch ("$($spnReport.Status)") { 'OK' { $spnStatus = "OK ($cntOk/$cntTotal SPNs registriert)"; $spnColor = 'green' } 'Warning' { $spnStatus = "WARNUNG ($cntMissing fehlend, $cntUnexp unerwartet)"; $spnColor = 'orange' } 'NoNetwork' { $spnStatus = 'Nicht pruefbar (kein AD/Netzwerk)'; $spnColor = 'orange' } 'Error' { $spnStatus = 'Fehler bei SPN-Pruefung'; $spnColor = 'red' } default { if ($cntMissing -gt 0 -or $cntUnexp -gt 0) { $spnStatus = "WARNUNG ($cntMissing fehlend, $cntUnexp unerwartet)"; $spnColor = 'orange' } elseif ($cntOk -gt 0) { $spnStatus = "OK ($cntOk/$cntTotal SPNs registriert)"; $spnColor = 'green' } else { $spnStatus = 'Keine SPNs gefunden'; $spnColor = 'orange' } } } } } catch { $spnLines = @('Error retrieving SPNs') $spnStatus = 'Fehler bei SPN-Pruefung' $spnColor = 'red' } # Splunk Status (via Invoke-sqmSplunkConfiguration -Test) $splunkStatus = 'Not configured' try { $splunkResult = Invoke-sqmSplunkConfiguration -Mode Test -ErrorAction SilentlyContinue if ($splunkResult) { if ($splunkResult.IsConfigured) { $splunkStatus = "Configured (service: $($splunkResult.ServiceStatus))" } else { $splunkStatus = 'Not configured' } } } catch { $splunkStatus = 'Error checking Splunk' } # ========================================== # CONFIGURATION # ========================================== # MAXDOP $maxdop = $server.Configuration.MaxDegreeOfParallelism.ConfigValue $cpuCount = $server.Processors $recommendedMaxdop = if ($cpuCount -le 4) { $cpuCount } elseif ($cpuCount -le 8) { 4 } elseif ($cpuCount -le 16) { 8 } else { 16 } $maxdopStatus = if ($maxdop -ge 2 -and $maxdop -le $recommendedMaxdop) { "OK ($maxdop)" } else { "CHECK ($maxdop, recommended $recommendedMaxdop)" } # Cost Threshold $ctp = $server.Configuration.CostThresholdForParallelism.ConfigValue $ctpStatus = if ($ctp -ge 50) { "OK ($ctp)" } else { "WARNING ($ctp, recommended >= 50)" } # TempDB $tempdb = Get-DbaDatabase -SqlInstance $server -Database 'tempdb' $tempdbFileCount = $tempdb.FileGroups.Files.Count $idealCount = [Math]::Min($cpuCount, 8) $tempdbStatus = if ($tempdbFileCount -eq $idealCount) { "OK ($tempdbFileCount files)" } else { "CHECK ($tempdbFileCount files, ideal $idealCount)" } # ========================================== # DATABASES # ========================================== $databases = @() try { $allDbs = Get-DbaDatabase -SqlInstance $server -ExcludeSystem foreach ($db in $allDbs) { $dbo = $db.Owner $lastBackup = $db.LastFullBackupDate $daysAgo = if ($lastBackup) { (New-TimeSpan -Start $lastBackup -End (Get-Date)).Days } else { -1 } $backupStatus = if ($daysAgo -lt 0) { 'Never' } elseif ($daysAgo -eq 0) { 'Today' } elseif ($daysAgo -le 7) { "$daysAgo days" } else { "$daysAgo days ⚠️" } $databases += [PSCustomObject]@{ Name = $db.Name Recovery = $db.RecoveryModel DBO = $dbo LastFullBackup = $backupStatus } } } catch { } # ========================================== # BUILD HTML # ========================================== $html = _Build-ModernReportHtml ` -SqlInstance $SqlInstance ` -Timestamp $timestamp ` -SAStatus $saStatus ` -SAStatusColor $saStatusColor ` -SAName $saName ` -BackupStatus $backupJobStatus ` -BackupStatusColor $backupStatusColor ` -MaxMemStatus $maxMemStatus ` -MaxMemColor $maxMemColor ` -Sysadmins $sysadmins ` -SysadminColor $sysadminColor ` -SysadminWarning $sysadminWarning ` -AdvancedLogins $advancedLogins ` -CLRStatus $clrStatus ` -CLRColor $clrColor ` -XPStatus $xpStatus ` -XPColor $xpColor ` -ServiceAccounts $serviceAccounts ` -SPNList $spnLines ` -SPNStatus $spnStatus ` -SPNColor $spnColor ` -SplunkStatus $splunkStatus ` -MAXDOP $maxdopStatus ` -CostThreshold $ctpStatus ` -TempDB $tempdbStatus ` -Databases $databases # ========================================== # SAVE REPORT # ========================================== if (-not (Test-Path $OutputPath)) { $null = New-Item -ItemType Directory -Path $OutputPath -Force } $htmlFile = Join-Path $OutputPath "sqmSetupReport_${safeInstance}_${datestamp}.html" $html | Out-File -FilePath $htmlFile -Encoding UTF8 -Force Invoke-sqmOpenReport -HtmlFile $htmlFile -NoOpen:$NoOpen Invoke-sqmLogging -Message "Report erstellt: $htmlFile" -FunctionName $functionName -Level 'INFO' Write-Host "`n✅ Setup-Report: $htmlFile`n" -ForegroundColor Green Copy-sqmToCentralPath -Path $htmlFile if ($PassThru) { return $htmlFile } } catch { $errMsg = "Fehler: $($_.Exception.Message)" Invoke-sqmLogging -Message $errMsg -FunctionName $functionName -Level 'ERROR' Write-Error $errMsg } } } # ====================================================================== # HTML Builder Function # ====================================================================== function _Build-ModernReportHtml { param( [string]$SqlInstance, [string]$Timestamp, [string]$SAStatus, [string]$SAStatusColor, [string]$SAName, [string]$BackupStatus, [string]$BackupStatusColor, [string]$MaxMemStatus, [string]$MaxMemColor, [string[]]$Sysadmins, [string]$SysadminColor, [string]$SysadminWarning, [string[]]$AdvancedLogins, [string]$CLRStatus, [string]$CLRColor, [string]$XPStatus, [string]$XPColor, [string[]]$ServiceAccounts, [string[]]$SPNList, [string]$SPNStatus, [string]$SPNColor, [string]$SplunkStatus, [string]$MAXDOP, [string]$CostThreshold, [string]$TempDB, [PSCustomObject[]]$Databases ) function _HtmlEncode { param([string]$Text) if (-not $Text) { return '' } $Text -replace '&', '&' -replace '<', '<' -replace '>', '>' -replace '"', '"' -replace "'", ''' } # Rendert eine Werteliste untereinander (eine Zeile pro Eintrag), HTML-kodiert. function _HtmlList { param([string[]]$Items, [string]$EmptyText = 'None') $vals = @($Items | Where-Object { $_ -ne $null -and "$_".Trim() -ne '' }) if ($vals.Count -eq 0) { return (_HtmlEncode $EmptyText) } return (($vals | ForEach-Object { _HtmlEncode $_ }) -join '<br>') } # Mappt die Status-Farbnamen (green/orange/red) auf Hex fuer Inline-Text. function _SpnColorHex { param([string]$Color) switch ($Color) { 'green' { '#27ae60' } 'red' { '#e74c3c' } 'orange' { '#f39c12' } default { '#e2e8f0' } } } $sysadminWarningHtml = if ($SysadminWarning) { "<div class=`"card-detail`" style=`"color:#e74c3c;font-weight:600;`">$(_HtmlEncode $SysadminWarning)</div>" } else { '' } $dbRows = if ($Databases) { $Databases | ForEach-Object { "<tr><td>$(_HtmlEncode $_.Name)</td><td>$($_.Recovery)</td><td>$(_HtmlEncode $_.DBO)</td><td>$($_.LastFullBackup)</td></tr>" } | Out-String } else { '<tr><td colspan="4">No databases</td></tr>' } return @" <!DOCTYPE html> <html lang="de"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>SQL Server Setup Report - $(_HtmlEncode $SqlInstance)</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', Arial, sans-serif; background: #060f20; color: #e2e8f0; font-size: 14px; line-height: 1.6; } .header { background: linear-gradient(160deg, #060f20 0%, #0b1e3d 100%); border-bottom: 3px solid #2e86c1; padding: 32px 40px; } .header h1 { font-size: 28px; font-weight: 600; color: #5dade2; margin-bottom: 8px; } .header .meta { color: #94a8c0; font-size: 13px; } .container { max-width: 1200px; margin: 0 auto; padding: 32px 40px; } /* Critical Issues Section */ .section-title { font-size: 18px; font-weight: 700; color: #5dade2; margin-top: 32px; margin-bottom: 16px; border-bottom: 2px solid #1e3a5f; padding-bottom: 8px; } .cards-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-bottom: 24px; } .card { background: #0d1f38; border-left: 4px solid; padding: 20px; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); } .card.green { border-left-color: #27ae60; background: rgba(39, 174, 96, 0.08); } .card.orange { border-left-color: #f39c12; background: rgba(243, 156, 18, 0.08); } .card.red { border-left-color: #e74c3c; background: rgba(231, 76, 60, 0.12); } .card-label { color: #94a8c0; font-size: 12px; text-transform: uppercase; font-weight: 600; letter-spacing: 0.05em; margin-bottom: 8px; } .card-value { color: #e2e8f0; font-size: 16px; font-weight: 600; } .card-detail { color: #94a8c0; font-size: 12px; margin-top: 6px; } /* Info Sections */ .info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-bottom: 32px; } .info-block h3 { font-size: 14px; color: #5dade2; font-weight: 600; margin-bottom: 12px; text-transform: uppercase; } .info-block p { color: #e2e8f0; font-size: 13px; margin-bottom: 6px; word-wrap: break-word; } .info-label { color: #94a8c0; font-weight: 600; display: inline-block; min-width: 140px; } /* Database Table */ table { width: 100%; border-collapse: collapse; background: #0d1f38; border-radius: 6px; overflow: hidden; margin-top: 16px; } th { background: #0b1e3d; color: #94a8c0; font-weight: 600; font-size: 12px; text-transform: uppercase; padding: 12px 16px; text-align: left; border-bottom: 1px solid #1e3a5f; } td { padding: 12px 16px; border-bottom: 1px solid #0f2540; color: #e2e8f0; } tr:hover { background: rgba(93, 173, 226, 0.06); } .footer { margin-top: 40px; padding-top: 24px; border-top: 1px solid #1e3a5f; color: #4a6080; font-size: 12px; } </style> </head> <body> <div class="header"> <h1>SQL Server Setup Report</h1> <div class="meta">Instance: <strong>$(_HtmlEncode $SqlInstance)</strong> | Timestamp: $Timestamp</div> </div> <div class="container"> <!-- CRITICAL ISSUES --> <div class="section-title">CRITICAL ISSUES</div> <div class="cards-grid"> <div class="card $SAStatusColor"> <div class="card-label">SA Account</div> <div class="card-value">$SAStatus</div> <div class="card-detail">Name: $(_HtmlEncode $SAName)</div> </div> <div class="card $BackupStatusColor"> <div class="card-label">Backup Jobs</div> <div class="card-value">$BackupStatus</div> <div class="card-detail">Enable backups immediately if missing</div> </div> <div class="card $MaxMemColor"> <div class="card-label">Max Memory</div> <div class="card-value">$MaxMemStatus</div> <div class="card-detail">Tolerance: 85-95% of RAM</div> </div> </div> <!-- SECURITY --> <div class="section-title">SECURITY</div> <div class="cards-grid"> <div class="card $SysadminColor"> <div class="card-label">Sysadmin Accounts</div> <div class="card-value" style="font-size: 13px; line-height: 1.8;">$(_HtmlList $Sysadmins 'None')</div> $sysadminWarningHtml </div> <div class="card"> <div class="card-label">Logins with Extended Roles</div> <div class="card-value" style="font-size: 13px; line-height: 1.8;">$(_HtmlList $AdvancedLogins 'None with server roles')</div> </div> </div> <div class="info-grid"> <div class="info-block"> <h3>Server-Level Features</h3> <p><span class="info-label">CLR:</span> $CLRStatus</p> <p><span class="info-label">xp_cmdshell:</span> $XPStatus</p> </div> <div class="info-block"> <h3>Infrastructure</h3> <p><span class="info-label">SPN Status:</span> <strong style="color: $(_SpnColorHex $SPNColor);">$(_HtmlEncode $SPNStatus)</strong></p> <p style="margin-left: 12px;">$(_HtmlList $SPNList 'Not checked')</p> <p><span class="info-label">Splunk:</span> $SplunkStatus</p> </div> </div> <!-- SERVICE ACCOUNTS --> <div class="section-title">SERVICE ACCOUNTS</div> <div class="info-block"> <p>$(_HtmlList $ServiceAccounts 'Unable to determine')</p> </div> <!-- CONFIGURATION --> <div class="section-title">CONFIGURATION</div> <div class="info-grid"> <div class="info-block"> <h3>Query Execution</h3> <p><span class="info-label">MAXDOP:</span> $MAXDOP</p> <p><span class="info-label">Cost Threshold:</span> $CostThreshold</p> </div> <div class="info-block"> <h3>Tempdb</h3> <p><span class="info-label">Files:</span> $TempDB</p> </div> </div> <!-- DATABASES --> <div class="section-title">DATABASES</div> <table> <thead> <tr> <th>Database</th> <th>Recovery Model</th> <th>DBO Owner</th> <th>Last Full Backup</th> </tr> </thead> <tbody> $dbRows </tbody> </table> <div class="footer"> Report generated by sqmSQLTool - Setup Report v2.0 | All times UTC<br> Quelle: <a href="https://www.powershelldba.de">www.powershelldba.de</a> </div> </div> </body> </html> "@ } |