Collectors/Get-EscCertPublisher.ps1

function Get-EscCertPublisher {
    <#
    .SYNOPSIS
        Enumerates members of the domain 'Cert Publishers' group (read-only, live).
    .DESCRIPTION
        Resolves the Cert Publishers group by its well-known RID (517), which is
        locale-independent, and falls back to a CN match. Reads the group's member
        list via LDAP (search only) and resolves each member to its computer /
        principal identity:
            Name, DnsHostName, SamAccountName, DN, ObjectClass

        Cert Publishers normally contains the computer object of every Enterprise CA;
        comparing it against the Enrollment Services objects surfaces stale, missing,
        or rogue CA hosts. Live-only (requires a reachable domain); returns @() on
        any failure.
    .PARAMETER Server
        Optional DC/server to bind to.
    .PARAMETER DefaultNamingContext
        Optional default (domain) NC DN (auto-detected from RootDSE when omitted).
    .OUTPUTS
        [pscustomobject] CertPublisherMember[] (possibly empty).
    .EXAMPLE
        Get-EscCertPublisher -Verbose
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [string] $Server,

        [Parameter(Mandatory = $false)]
        [string] $DefaultNamingContext
    )

    $dnc = $DefaultNamingContext
    if ([string]::IsNullOrWhiteSpace($dnc)) { $dnc = Get-EscDefaultNamingContext -Server $Server }
    if ([string]::IsNullOrWhiteSpace($dnc)) {
        Write-EscLog -Component 'CertPublishers' -Level Warning -Message 'No default naming context; returning empty set.'
        return @()
    }

    $prefix = 'LDAP://'
    if (-not [string]::IsNullOrWhiteSpace($Server)) { $prefix = 'LDAP://{0}/' -f $Server }

    $groupRes = $null
    try {
        $domEntry = New-Object System.DirectoryServices.DirectoryEntry(('{0}{1}' -f $prefix, $dnc))
        $sidBytes = $domEntry.Properties['objectSid'].Value
        try { $domEntry.Dispose() } catch { }
        if ($null -ne $sidBytes) {
            $domSid = New-Object System.Security.Principal.SecurityIdentifier([byte[]]$sidBytes, 0)
            $grpSid = '{0}-517' -f $domSid.Value
            $res = @(Invoke-EscLdapSearch -SearchRoot $dnc -Filter ('(objectSid={0})' -f $grpSid) -Server $Server `
                -PropertiesToLoad @('distinguishedName', 'member', 'cn') -SearchScope 'Subtree')
            if ($res.Count -gt 0) { $groupRes = $res[0] }
        }
    }
    catch {
        Write-EscLog -Component 'CertPublishers' -Level Warning -Message ("RID 517 lookup failed: {0}" -f $_.Exception.Message)
    }

    if ($null -eq $groupRes) {
        $res = @(Invoke-EscLdapSearch -SearchRoot $dnc -Filter '(&(objectClass=group)(cn=Cert Publishers))' -Server $Server `
            -PropertiesToLoad @('distinguishedName', 'member', 'cn') -SearchScope 'Subtree')
        if ($res.Count -gt 0) { $groupRes = $res[0] }
    }

    if ($null -eq $groupRes) {
        Write-EscLog -Component 'CertPublishers' -Level Warning -Message 'Cert Publishers group not found.'
        return @()
    }

    $memberDns = @()
    try { $memberDns = @($groupRes.Properties['member']) } catch { $memberDns = @() }

    $out = @()
    foreach ($mdn in $memberDns) {
        $m = [string]$mdn
        if ([string]::IsNullOrWhiteSpace($m)) { continue }
        try {
            $mres = @(Invoke-EscLdapSearch -SearchRoot $m -Filter '(objectClass=*)' -Server $Server `
                -PropertiesToLoad @('cn', 'dNSHostName', 'sAMAccountName', 'objectClass', 'distinguishedName') -SearchScope 'Base')
            if ($mres.Count -eq 0) { continue }
            $bag = ConvertTo-EscPropertyBag -SearchResult $mres[0]

            $get = {
                param($k)
                if ($bag.ContainsKey($k)) {
                    $v = $bag[$k]
                    if ($v -is [System.Array]) { if ($v.Length -gt 0) { return [string]$v[0] } else { return '' } }
                    return [string]$v
                }
                return ''
            }
            $oc = @()
            if ($bag.ContainsKey('objectclass')) { $oc = @($bag['objectclass']) }
            $ocLast = ''
            if ($oc.Count -gt 0) { $ocLast = [string]$oc[$oc.Count - 1] }

            $out += [pscustomobject]@{
                Name           = (& $get 'cn')
                DnsHostName    = (& $get 'dnshostname')
                SamAccountName = (& $get 'samaccountname')
                DN             = $m
                ObjectClass    = $ocLast
            }
        }
        catch {
            Write-EscLog -Component 'CertPublishers' -Level Warning -Message ("Failed to resolve member {0}: {1}" -f $m, $_.Exception.Message)
        }
    }

    Write-EscLog -Component 'CertPublishers' -Message ("Cert Publishers group has {0} resolved member(s)." -f @($out).Count)
    return @($out)
}