Public/Get-CwCaHealth.ps1
|
function Get-CwCaHealth { <# .SYNOPSIS Collects ADCS CA health: CertSvc status, CA type and lifetime, pending/failed request backlog and database size. .DESCRIPTION Read-only. Detects a local ADCS installation via the CertSvc configuration registry hive. Backlog figures come from PSPKI when the module is available, otherwise via certutil restrictions (best effort: values are $null when neither source works). Always emits one object; IsCa=$false on machines without ADCS. .PARAMETER FailedLookbackHours Time window for the failed-request counter. Default 24. .PARAMETER WarnDays Warning threshold for the CA certificate lifetime. Default 180. .PARAMETER CritDays Critical threshold for the CA certificate lifetime. Default 30. .PARAMETER PendingWarnCount Pending requests at or above this count raise WARNING. Default 25. .EXAMPLE Get-CwCaHealth | Format-List #> [CmdletBinding()] [OutputType([pscustomobject])] param( [int]$FailedLookbackHours = 24, [int]$WarnDays = 180, [int]$CritDays = 30, [int]$PendingWarnCount = 25 ) $configKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration' if (-not (Test-Path -Path $configKey)) { return [pscustomobject]@{ PSTypeName = 'CertWatchtower.CaHealth' IsCa = $false CaName = $null CaType = $null ServiceStatus = $null CaCertNotAfter = $null CaCertDaysRemaining = $null PendingRequests = $null FailedRequests24h = $null DbSizeMB = $null ActiveTemplateCount = $null Severity = 'OK' } } $caName = (Get-ItemProperty -Path $configKey -ErrorAction SilentlyContinue).Active $caKey = Join-Path $configKey $caName $caProps = Get-ItemProperty -Path $caKey -ErrorAction SilentlyContinue # CAType registry value: 0=EnterpriseRoot 1=EnterpriseSub 3=StandaloneRoot 4=StandaloneSub $caType = switch ($caProps.CAType) { 0 { 'EnterpriseRoot' } 1 { 'EnterpriseSub' } 3 { 'StandaloneRoot' } 4 { 'StandaloneSub' } default { "Unknown($($caProps.CAType))" } } $serviceStatus = try { (Get-Service -Name CertSvc -ErrorAction Stop).Status.ToString() } catch { 'NotFound' } # CA certificate: find it in LocalMachine\My by subject = CA common name $caCert = Get-ChildItem -Path Cert:\LocalMachine\My -ErrorAction SilentlyContinue | Where-Object { $_.Subject -match [regex]::Escape($caName) } | Sort-Object NotAfter -Descending | Select-Object -First 1 $caCertNotAfter = $caCert.NotAfter $caCertDays = if ($caCertNotAfter) { [math]::Round(($caCertNotAfter - (Get-Date)).TotalDays, 1) } else { $null } # backlog: PSPKI preferred, certutil fallback $pending = $null; $failed = $null if (Get-Module -ListAvailable -Name PSPKI) { try { Import-Module PSPKI -ErrorAction Stop $ca = Get-CertificationAuthority -ComputerName $env:COMPUTERNAME -ErrorAction Stop $pending = @($ca | Get-PendingRequest -ErrorAction Stop).Count $failed = @($ca | Get-FailedRequest -Filter "Request.SubmittedWhen -ge $((Get-Date).AddHours(-$FailedLookbackHours))" -ErrorAction Stop).Count } catch { Write-Verbose "PSPKI backlog query failed: $_" } } if ($null -eq $pending) { try { $out = & certutil.exe -view -restrict 'Disposition=9' -out 'RequestID' csv 2>$null if ($LASTEXITCODE -eq 0) { $pending = [math]::Max(0, @($out | Where-Object { $_ -match '^\d' }).Count) } } catch { Write-Verbose "certutil pending query failed: $_" } } if ($null -eq $failed) { try { $since = (Get-Date).AddHours(-$FailedLookbackHours).ToString('MM/dd/yyyy HH:mm', [cultureinfo]::InvariantCulture) $out = & certutil.exe -view -restrict "Disposition=30,Request.SubmittedWhen>=$since" -out 'RequestID' csv 2>$null if ($LASTEXITCODE -eq 0) { $failed = [math]::Max(0, @($out | Where-Object { $_ -match '^\d' }).Count) } } catch { Write-Verbose "certutil failed-request query failed: $_" } } # DB size $dbSizeMb = $null if ($caProps.DBDirectory -and (Test-Path -Path $caProps.DBDirectory)) { $edb = Get-ChildItem -Path $caProps.DBDirectory -Filter '*.edb' -ErrorAction SilentlyContinue if ($edb) { $dbSizeMb = [math]::Round((($edb | Measure-Object Length -Sum).Sum) / 1MB, 1) } } # active templates (rough hygiene indicator, Enterprise CAs only) $templateCount = $null if ($caProps.PSObject.Properties['Templates'] -and $caProps.Templates) { $templateCount = @($caProps.Templates | Where-Object { $_ }).Count } # severity: stopped service or CA cert below crit threshold => CRITICAL $severity = 'OK' if ($null -ne $caCertDays) { $severity = ConvertTo-CwSeverity -DaysRemaining $caCertDays -WarnDays $WarnDays -CritDays $CritDays } if ($null -ne $pending -and $pending -ge $PendingWarnCount -and $severity -eq 'OK') { $severity = 'WARNING' } if ($serviceStatus -ne 'Running') { $severity = 'CRITICAL' } [pscustomobject]@{ PSTypeName = 'CertWatchtower.CaHealth' IsCa = $true CaName = $caName CaType = $caType ServiceStatus = $serviceStatus CaCertNotAfter = $caCertNotAfter CaCertDaysRemaining = $caCertDays PendingRequests = $pending FailedRequests24h = $failed DbSizeMB = $dbSizeMb ActiveTemplateCount = $templateCount Severity = $severity } } |