Collectors/Get-EscCertificateTemplate.ps1

function Get-EscCertificateTemplate {
    <#
    .SYNOPSIS
        Enumerates AD certificate templates (read-only) as Template[] objects.
    .DESCRIPTION
        Reads objects under
        CN=Certificate Templates,CN=Public Key Services,CN=Services,
        CN=Configuration,<configNC> via LDAP (search only) and maps each to the
        Template schema. Enrollment / write principals are derived
        from nTSecurityDescriptor using ConvertFrom-SecurityDescriptor.

        PublishedOnCAs is left empty here; the orchestrator (or a cross-reference
        with Get-EscEnrollmentService) fills it.

        Offline / test mode: pass -InputObject with an array of property bags
        (hashtables keyed by lowercase attribute name) to bypass live LDAP; the
        pure transform ConvertTo-EscTemplateObject is applied to each.
    .PARAMETER Server
        Optional DC/server to bind to.
    .PARAMETER ConfigurationNamingContext
        Optional config NC DN (auto-detected from RootDSE when omitted).
    .PARAMETER InputObject
        Optional array of raw property bags (offline mode).
    .PARAMETER ExtraLowPrivSid
        Optional extra low-priv SIDs forwarded to the ACL parser.
    .OUTPUTS
        [pscustomobject] Template[].
    .EXAMPLE
        Get-EscCertificateTemplate -Verbose
    #>

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

        [Parameter(Mandatory = $false)]
        [string] $ConfigurationNamingContext,

        [Parameter(Mandatory = $false)]
        [object[]] $InputObject,

        [Parameter(Mandatory = $false)]
        [string[]] $ExtraLowPrivSid = @()
    )

    if ($PSBoundParameters.ContainsKey('InputObject') -and $null -ne $InputObject) {
        Write-EscLog -Component 'Templates' -Message ("Offline mode: transforming {0} fixture record(s)." -f @($InputObject).Count)
        $out = @()
        foreach ($rec in $InputObject) {
            $bag = $rec
            if ($rec -isnot [hashtable]) {
                if ($rec -is [System.DirectoryServices.SearchResult]) {
                    $bag = ConvertTo-EscPropertyBag -SearchResult $rec
                }
                else {
                    $bag = @{}
                    foreach ($p in $rec.PSObject.Properties) {
                        $bag[$p.Name.ToLowerInvariant()] = @($p.Value)
                    }
                }
            }
            $out += ConvertTo-EscTemplateObject -Property $bag -ExtraLowPrivSid $ExtraLowPrivSid
        }
        return @($out)
    }

    $configNc = $ConfigurationNamingContext
    if ([string]::IsNullOrWhiteSpace($configNc)) {
        $configNc = Get-EscConfigNamingContext -Server $Server
    }
    if ([string]::IsNullOrWhiteSpace($configNc)) {
        Write-EscLog -Component 'Templates' -Level Warning -Message 'Could not resolve configuration naming context; returning empty set.'
        return @()
    }

    $searchRoot = 'CN=Certificate Templates,CN=Public Key Services,CN=Services,{0}' -f $configNc
    $props = @(
        'cn', 'name', 'displayName', 'msPKI-Cert-Template-OID', 'msPKI-Template-Schema-Version',
        'msPKI-Certificate-Name-Flag', 'pKIExtendedKeyUsage', 'msPKI-Certificate-Application-Policy',
        'msPKI-Enrollment-Flag', 'msPKI-RA-Signature', 'msPKI-RA-Application-Policies',
        'msPKI-Certificate-Policy', 'nTSecurityDescriptor', 'distinguishedName'
    )

    $results = Invoke-EscLdapSearch -SearchRoot $searchRoot -Filter '(objectClass=pKICertificateTemplate)' `
        -Server $Server -PropertiesToLoad $props -SearchScope 'OneLevel'

    Write-EscLog -Component 'Templates' -Message ("Retrieved {0} template object(s)." -f @($results).Count)

    $out = @()
    foreach ($r in $results) {
        try {
            $bag = ConvertTo-EscPropertyBag -SearchResult $r
            $out += ConvertTo-EscTemplateObject -Property $bag -ExtraLowPrivSid $ExtraLowPrivSid
        }
        catch {
            Write-EscLog -Component 'Templates' -Level Warning -Message ("Failed to map a template: {0}" -f $_.Exception.Message)
        }
    }
    return @($out)
}