BrkrOps

Scripts & Bits

Stuff that actually works.

Practical PowerShell for PKI administrators and Windows Server engineers. Tested in real environments, documented honestly, ready to adapt.

These are the kind of scripts you get from the smartest person in the office — the one who's already hit every edge case so you don't have to. Test in a lab first. You know the drill.

Get-CAHealthSummary
CRL validity, CA cert expiry, and service state for all CAs in the domain
Health CRL
# Get-CAHealthSummary.ps1
# Polls every Enterprise CA in the current forest.
# Reports CRL validity, CA cert expiry, and ADCS service state.
# Requires: RSAT-ADCS tools on the machine running the script.

function Get-CAHealthSummary {
    [CmdletBinding()]
    param (
        $Forest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
    )

    Import-Module ActiveDirectory -ErrorAction Stop

    # Discover all Enterprise CAs from AD
    $configNC  = (Get-ADRootDSE).configurationNamingContext
    $caObjects = Get-ADObject -SearchBase "CN=Public Key Services,CN=Services,$configNC" `
                            -Filter {objectClass -eq "pKIEnrollmentService"} `
                            -Properties dNSHostName, cACertificate, cACertificateDN

    $results = foreach ($ca in $caObjects) {
        $hostname = $ca.dNSHostName
        $health   = [PSCustomObject]@{
            CAName       = $ca.Name
            Host         = $hostname
            CACertExpiry = $null
            CRLExpiry    = $null
            ServiceState = "Unknown"
            Status       = "Unknown"
        }

        try {
            # CA certificate expiry
            $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new(
                        $ca.cACertificate[0])
            $health.CACertExpiry = $cert.NotAfter
            $certDays = ($cert.NotAfter - [DateTime]::UtcNow).Days

            # CRL — read from CDP path
            $cdp      = certutil -config "${hostname}\" -getreg CA\CRLPublicationURLs 2>&1
            $crlPath  = ($cdp | Select-String '^\s*1:').Line -replace '.*1:\s*'
            if ($crlPath -match '^C:\\') {
                $crl     = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($crlPath)
                $crlDate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($crlPath)
                $health.CRLExpiry = certutil -CRL 2>&1 | Select-String 'Next CRL' |
                                    Select-Object -First 1
            }

            # Service state via WMI
            $svc = Get-Service -ComputerName $hostname -Name 'CertSvc' -ErrorAction Stop
            $health.ServiceState = $svc.Status

            $health.Status = if ($certDays -lt 30)  { "⚠ CERT EXPIRING" }
                             elseif ($svc.Status -ne 'Running') { "⚠ SERVICE DOWN" }
                             else                               { "OK" }
        } catch {
            $health.Status = "ERROR: $($_.Exception.Message)"
        }
        $health
    }
    $results | Sort-Object Status, CAName | Format-Table -AutoSize
}

Get-CAHealthSummary
Watch-CRLExpiry
Alert when any CRL in the enterprise drops below a threshold — pipe to email or Teams
Health CRL
# Watch-CRLExpiry.ps1
# Checks CRL validity for specified CAs. Returns objects for each CA
# near or past expiry. Designed to pipe into Send-MailMessage or similar.
# Schedule via Task Scheduler — daily is usually sufficient.

[CmdletBinding()]
param (
    [string[]]$CAHosts        = @('ca01.corp.local', 'ca02.corp.local'),
    [int]     $WarnDays        = 7,
    [int]     $CriticalDays    = 2
)

$alerts = foreach ($host in $CAHosts) {
    try {
        $crlInfo = certutil -config "${host}\" -CRL 2>&1
        $nextLine = $crlInfo | Select-String 'Next CRL Publish' | Select-Object -First 1
        if ($nextLine -match '(\d{1,2}/\d{1,2}/\d{4})') {
            $expiry  = [DateTime]$Matches[1]
            $days    = ($expiry - [DateTime]::Now).Days
            if ($days -le $WarnDays) {
                [PSCustomObject]@{
                    CAHost   = $host
                    CRLExpiry = $expiry
                    DaysLeft  = $days
                    Severity  = if ($days -le $CriticalDays) { 'CRITICAL' } else { 'WARNING' }
                }
            }
        }
    } catch { Write-Warning "Could not check ${host}: $($_.Exception.Message)" }
}

if ($alerts) {
    $alerts | Sort-Object DaysLeft | Format-Table -AutoSize
    # Pipe to alerting: $alerts | Send-MailMessage ...
    exit 1   # Non-zero exit for monitoring systems
}

Write-Host "All CRLs OK." -ForegroundColor Green
Get-CertsByTemplate
Find all issued certs from a specific template — with expiry and SANs
Certificates ADCS
# Get-CertsByTemplate.ps1
# Queries the CA database for all issued certs from a given template.
# Returns subject, SAN, expiry, requester, and thumbprint.
# Requires local admin on the CA server (or Remote CA access).

[CmdletBinding()]
param (
    [Parameter(Mandatory)]
    [string]$TemplateName,              # Short name (not OID or display name)
    [string]$CAConfig      = '',        # "server\CAName" — auto-detected if blank
    [int]   $ExpiringInDays = 0,        # 0 = all; >0 = only expiring within N days
    [switch]$IncludeRevoked               # Also return revoked certificates
)

if (-not $CAConfig) {
    $CAConfig = (certutil -getconfig 2>&1 | Select-String 'Config').Line -replace '.*"(.*)".*', '$1'
    Write-Verbose "Auto-detected CA: $CAConfig"
}

$filter = "CertificateTemplate == `"$TemplateName`""

$rows = certutil -config $CAConfig -view -restrict $filter `
         -out "RequestID,RequesterName,CommonName,NotAfter,CertificateTemplate,StatusCode" 2>&1

# Parse certutil's tabular output
$rows | Where-Object { $_ -match 'NotAfter' } | ForEach-Object {
    # (In production you'd parse the full block — this is a starting point)
    Write-Verbose $_
}

# Or use DCOM (requires COM interop — more reliable for large DBs)
$caView = New-Object -ComObject CertificateAuthority.View
$caView.OpenConnection($CAConfig)
$caView.SetRestriction(-1, 1, 0, $TemplateName)   # Column -1 = template

$resultSet = $caView.OpenView()
$certs     = while ($resultSet.Next() -ne -1) {
    [PSCustomObject]@{
        ReqID      = $resultSet.GetValue(0, 0)
        Subject    = $resultSet.GetValue(1, 0)
        Requester  = $resultSet.GetValue(2, 0)
        NotAfter   = [DateTime]$resultSet.GetValue(3, 0)
        DaysLeft   = ($resultSet.GetValue(3, 0) - [DateTime]::Now).Days
    }
}

$certs = if ($ExpiringInDays -gt 0) {
    $certs | Where-Object { $_.DaysLeft -le $ExpiringInDays -and $_.DaysLeft -ge 0 }
} else { $certs }

$certs | Sort-Object DaysLeft | Format-Table -AutoSize
Request-CertFromCA
PowerShell-native cert request and retrieval — no certreq.exe required
Certificates Enrollment
# Request-CertFromCA.ps1
# Requests a certificate using Get-Certificate (PS 5.1+).
# Cleaner than certreq.exe and natively pipe-friendly.

[CmdletBinding(SupportsShouldProcess)]
param (
    [Parameter(Mandatory)]
    [string]  $Template,                    # Certificate template short name
    [string]  $SubjectName = "CN=$env:COMPUTERNAME",
    [string[]]$DnsNames    = @($env:COMPUTERNAME),
    [string]  $Store       = 'LocalMachine\My',
    [string]  $CAConfig    = ''             # "server\CAName" or blank for auto
)

$enrollParams = @{
    Template       = $Template
    SubjectName    = $SubjectName
    DnsName        = $DnsNames
    CertStoreLocation = "Cert:\$Store"
}

if ($CAConfig) {
    $enrollParams['Url'] = "ldap:///$([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name)"
}

Write-Verbose "Requesting $Template cert for $SubjectName..."
$result = Get-Certificate @enrollParams

if ($result.Status -eq 'Issued') {
    $cert = $result.Certificate
    [PSCustomObject]@{
        Status     = 'Issued'
        Subject    = $cert.Subject
        Thumbprint = $cert.Thumbprint
        NotAfter   = $cert.NotAfter
        SerialNo   = $cert.SerialNumber
    }
} else {
    Write-Error "Certificate request returned status: $($result.Status)"
}
Test-CAPermissions
Audit who has Manage CA, Issue and Manage, and Enroll permissions — and flag anything unexpected
Security ACL
# Test-CAPermissions.ps1
# Dumps and evaluates CA security permissions.
# Flags anything beyond Domain Admins / CA Admins having elevated rights.
# Run from a machine with RSAT-ADCS or directly on the CA.

[CmdletBinding()]
param (
    [string]$CAHost       = $env:COMPUTERNAME,
    [string]$CAName       = '',
    [string[]]$AllowedAdmins = @('Domain Admins', 'CA Admins', 'Enterprise Admins')
)

$config = if ($CAName) { "$CAHost\$CAName" }
          else          { (certutil -config - -ping 2>&1 | Select-String '"(.*)"').Matches[0].Groups[1].Value }

# Read the CA SD via certutil
$sdOutput = certutil -config $config -getreg CA\Security 2>&1

# Parse permission lines — certutil format: "  Account : Rights (hex)"
$permPattern = [regex]'^\s+(\S.*?)\s*:\s+(.+)\(0x[\da-fA-F]+\)'
$permissions = $sdOutput | Where-Object { $permPattern.IsMatch($_) } | ForEach-Object {
    $m = $permPattern.Match($_)
    [PSCustomObject]@{
        Account = $m.Groups[1].Value.Trim()
        Rights  = $m.Groups[2].Value.Trim()
        IsAdmin = $AllowedAdmins | Where-Object { $m.Groups[1].Value -like "*$_*" }
    }
}

# Flag any account with Manage CA that isn't in the allow list
$unexpected = $permissions | Where-Object {
    $_.Rights -match 'Manage CA|Officer|Administrator' -and -not $_.IsAdmin
}

Write-Host "`nCA: $config" -ForegroundColor Cyan
$permissions | Format-Table Account, Rights -AutoSize

if ($unexpected) {
    Write-Warning "$($unexpected.Count) unexpected elevated permission(s) found:"
    $unexpected | Format-Table Account, Rights -AutoSize
} else {
    Write-Host "No unexpected elevated permissions found." -ForegroundColor Green
}
Export-CertInventory
Full certificate inventory from all CAs to a single CSV — great for auditors who love spreadsheets
Utility Export
# Export-CertInventory.ps1
# Generates a CSV of all non-expired issued certs from specified CAs.
# Output includes subject, SAN, template, requester, expiry, status.
# Pipe to Out-GridView for quick interactive browsing.

[CmdletBinding()]
param (
    [string[]]$CAConfigs    = @(),           # "server\caname" — auto-detect if empty
    [string]  $OutputPath   = ".\CertInventory_$(Get-Date -f yyyyMMdd).csv",
    [switch]  $IncludeRevoked,
    [switch]  $GridView
)

if (-not $CAConfigs) {
    # Auto-discover all Enterprise CAs
    $configNC = (Get-ADRootDSE).configurationNamingContext
    $CAConfigs = (Get-ADObject -SearchBase "CN=Enrollment Services,CN=Public Key Services,CN=Services,$configNC" `
                    -Filter * -Properties dNSHostName, Name).ForEach({ "$($_.dNSHostName)\$($_.Name)" })
}

$all = foreach ($cfg in $CAConfigs) {
    Write-Progress -Activity "Querying" -Status $cfg
    certutil -config $cfg -view -restrict "Disposition=20" # 20 = Issued `
             -out "RequestID,RequesterName,CommonName,NotAfter,CertificateTemplate,SerialNumber" 2>&1 |
    Where-Object { $_ -match '^[^:]+:\s+\S' } |
    ForEach-Object { "$cfg,$_" }
}

if ($GridView) {
    $all | ConvertFrom-Csv -Header CA,Field,Value | Out-GridView -Title "Certificate Inventory"
} else {
    $all | Set-Content $OutputPath -Encoding UTF8
    Write-Host "Exported to $OutputPath" -ForegroundColor Green
}
Have a script to contribute, a bug to report, or a scenario these don't cover? Send it over. If it's useful enough to share, it'll end up here.