Collectors/Get-EscWebEnrollmentEndpoint.ps1

function Get-EscWebEnrollmentEndpoint {
    <#
    .SYNOPSIS
        Probes AD CS HTTP(S) enrollment endpoints (read-only GET/HEAD).
    .DESCRIPTION
        For each CA host, sends unauthenticated HTTP HEAD/GET requests (no body,
        no credentials, never a certificate request) to the classic Web Enrollment
        and CES/CEP paths to detect their presence and NTLM acceptance:
          - http(s)://<host>/certsrv/
          - http(s)://<host>/<CAName>_CES_Kerberos/service.svc
          - http(s)://<host>/ADPolicyProvider_CEP_Kerberos/service.svc
        and maps results to WebEndpoint:
          CaName, Url, Scheme, Reachable, NtlmSupported, EpaEnabled(bool|null),
          CesCepPresent

        NtlmSupported is inferred from a 401 WWW-Authenticate header containing
        NTLM or Negotiate. EpaEnabled cannot be reliably determined over
        unauthenticated HTTP, so it is set to $null (analyzers => ManualReview).
        CesCepPresent is set true on every record of a host when any CES/CEP path
        for that host responded.

        Offline mode: pass -InputObject with pre-populated WebEndpoint-shaped
        records to bypass live probing.
    .PARAMETER CA
        Array of CA descriptor objects (each with .Name and .DnsHostName), e.g.
        Get-EscEnrollmentService output.
    .PARAMETER Scheme
        Which schemes to probe: 'https', 'http', or 'both' (default 'both').
    .PARAMETER TimeoutSeconds
        Per-request timeout (default 8).
    .PARAMETER InputObject
        Optional array of offline records.
    .OUTPUTS
        [pscustomobject] WebEndpoint[].
    .EXAMPLE
        Get-EscEnrollmentService | Get-EscWebEnrollmentEndpoint
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true)]
        [object[]] $CA,

        [Parameter(Mandatory = $false)]
        [ValidateSet('https', 'http', 'both')]
        [string] $Scheme = 'both',

        [Parameter(Mandatory = $false)]
        [int] $TimeoutSeconds = 8,

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

    begin {
        $collected = New-Object System.Collections.ArrayList

        if ($PSBoundParameters.ContainsKey('InputObject') -and $null -ne $InputObject) {
            Write-EscLog -Component 'WebEnroll' -Message ("Offline mode: passing through {0} fixture record(s)." -f @($InputObject).Count)
            foreach ($rec in $InputObject) {
                $epa = $null
                if ($rec.PSObject.Properties.Name -contains 'EpaEnabled') { $epa = $rec.EpaEnabled }
                [void]$collected.Add([pscustomobject]@{
                    CaName        = [string] $rec.CaName
                    Url           = [string] $rec.Url
                    Scheme        = [string] $rec.Scheme
                    Reachable     = [bool] $rec.Reachable
                    NtlmSupported = [bool] $rec.NtlmSupported
                    EpaEnabled    = $epa
                    CesCepPresent = [bool] $rec.CesCepPresent
                })
            }
        }

        $schemes = @()
        if ($Scheme -eq 'both') { $schemes = @('https', 'http') } else { $schemes = @($Scheme) }

        $probe = {
            param($url, $timeoutSec)

            $result = @{ Reachable = $false; NtlmSupported = $false; StatusCode = $null }

            foreach ($method in @('HEAD', 'GET')) {
                try {
                    $req = [System.Net.HttpWebRequest]::Create($url)
                    $req.Method = $method
                    $req.Timeout = $timeoutSec * 1000
                    $req.AllowAutoRedirect = $false
                    $req.UserAgent = 'ADCS.ESC.Assessment (read-only probe)'
                    $resp = $req.GetResponse()
                    try {
                        $result.Reachable = $true
                        $result.StatusCode = [int] $resp.StatusCode
                        $auth = $resp.Headers['WWW-Authenticate']
                        if ($auth -match 'NTLM' -or $auth -match 'Negotiate') { $result.NtlmSupported = $true }
                    }
                    finally {
                        $resp.Close()
                    }
                    return $result
                }
                catch [System.Net.WebException] {
                    $we = $_.Exception
                    if ($null -ne $we.Response) {
                        $result.Reachable = $true
                        try {
                            $result.StatusCode = [int] $we.Response.StatusCode
                            $auth = $we.Response.Headers['WWW-Authenticate']
                            if ($auth -match 'NTLM' -or $auth -match 'Negotiate') { $result.NtlmSupported = $true }
                        }
                        catch { }
                        try { $we.Response.Close() } catch { }
                        return $result
                    }
                }
                catch {
                }
            }
            return $result
        }
    }

    process {
        if ($PSBoundParameters.ContainsKey('InputObject')) { return }
        if ($null -eq $CA) { return }

        $origCallback = [System.Net.ServicePointManager]::ServerCertificateValidationCallback
        try {
            [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { param($s, $c, $ch, $e) $true }
            try { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor [System.Net.SecurityProtocolType]::Tls12 } catch { }

            foreach ($caObj in $CA) {
                $caName = [string] $caObj.Name
                $host2  = [string] $caObj.DnsHostName
                if ([string]::IsNullOrWhiteSpace($host2)) { $host2 = $caName }
                if ([string]::IsNullOrWhiteSpace($host2)) { continue }

                $encodedCa = [System.Uri]::EscapeDataString($caName)
                $paths = @(
                    @{ Path = '/certsrv/'; CesCep = $false },
                    @{ Path = ('/{0}_CES_Kerberos/service.svc' -f $encodedCa); CesCep = $true },
                    @{ Path = '/ADPolicyProvider_CEP_Kerberos/service.svc'; CesCep = $true }
                )

                $records = New-Object System.Collections.ArrayList
                $hostCesCep = $false

                foreach ($sch in $schemes) {
                    foreach ($p in $paths) {
                        $url = '{0}://{1}{2}' -f $sch, $host2, $p.Path
                        $r = & $probe $url $TimeoutSeconds
                        if ($r.Reachable) {
                            if ($p.CesCep) { $hostCesCep = $true }
                            [void]$records.Add([pscustomobject]@{
                                CaName        = $caName
                                Url           = $url
                                Scheme        = $sch
                                Reachable     = $true
                                NtlmSupported = [bool] $r.NtlmSupported
                                EpaEnabled    = $null
                                CesCepPresent = $false
                                _IsCesCep     = $p.CesCep
                            })
                        }
                    }
                }

                if ($records.Count -eq 0) {
                    $primaryScheme = $schemes[0]
                    [void]$collected.Add([pscustomobject]@{
                        CaName        = $caName
                        Url           = ('{0}://{1}/certsrv/' -f $primaryScheme, $host2)
                        Scheme        = $primaryScheme
                        Reachable     = $false
                        NtlmSupported = $false
                        EpaEnabled    = $null
                        CesCepPresent = $false
                    })
                }
                else {
                    foreach ($rec in $records) {
                        [void]$collected.Add([pscustomobject]@{
                            CaName        = $rec.CaName
                            Url           = $rec.Url
                            Scheme        = $rec.Scheme
                            Reachable     = $rec.Reachable
                            NtlmSupported = $rec.NtlmSupported
                            EpaEnabled    = $rec.EpaEnabled
                            CesCepPresent = $hostCesCep
                        })
                    }
                }
            }
        }
        finally {
            [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $origCallback
        }
    }

    end {
        Write-EscLog -Component 'WebEnroll' -Message ("Collected {0} web endpoint record(s)." -f $collected.Count)
        return @($collected.ToArray())
    }
}