Private/AD/Core/Get-ADTrustRelationships.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Get-ADTrustRelationships {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable]$Connection,

        [switch]$Quiet
    )

    # ── Trust direction mapping ───────────────────────────────────────
    $trustDirectionMap = @{
        0 = 'Disabled'
        1 = 'Inbound'
        2 = 'Outbound'
        3 = 'Bidirectional'
    }

    # ── Trust type mapping ────────────────────────────────────────────
    $trustTypeMap = @{
        1 = 'Downlevel'     # NTLM (Windows NT 4.0 and earlier)
        2 = 'Uplevel'       # Kerberos (Active Directory)
        3 = 'MIT'           # Non-Windows Kerberos realm
        4 = 'DCE'           # Distributed Computing Environment
    }

    # ── Trust attribute flags ─────────────────────────────────────────
    # 0x0001 = NON_TRANSITIVE
    # 0x0002 = UPLEVEL_ONLY
    # 0x0004 = QUARANTINED_DOMAIN (SID filtering enabled)
    # 0x0008 = FOREST_TRANSITIVE
    # 0x0010 = CROSS_ORGANIZATION (selective authentication)
    # 0x0020 = WITHIN_FOREST
    # 0x0040 = TREAT_AS_EXTERNAL
    # 0x0080 = USES_RC4_ENCRYPTION
    # 0x0200 = CROSS_ORGANIZATION_NO_TGT_DELEGATION
    # 0x0400 = PIM_TRUST
    # 0x0800 = CROSS_ORGANIZATION_ENABLE_TGT_DELEGATION

    $domainDN = $Connection.DomainDN

    if (-not $Quiet) {
        Write-ProgressLine -Phase AUDITING -Message 'Enumerating trust relationships'
    }

    # ── Query trusted domain objects ──────────────────────────────────
    $trustFilter = '(objectClass=trustedDomain)'
    $trustProperties = @(
        'cn', 'flatName', 'trustPartner', 'distinguishedName',
        'trustDirection', 'trustType', 'trustAttributes',
        'securityIdentifier', 'objectSid',
        'whenCreated', 'whenChanged'
    )

    $trustResults = @()
    try {
        $systemContainerDN = "CN=System,$domainDN"
        $systemRoot = New-LdapSearchRoot -Connection $Connection -SearchBase $systemContainerDN
        $trustResults = Invoke-LdapQuery -SearchRoot $systemRoot `
            -Filter $trustFilter `
            -Properties $trustProperties `
            -Scope OneLevel
    } catch {
        Write-Warning "Failed to enumerate trust relationships: $_"
        return @()
    }

    Write-Verbose "Found $($trustResults.Count) trust relationship(s)"

    if ($trustResults.Count -eq 0) {
        if (-not $Quiet) {
            Write-ProgressLine -Phase AUDITING -Message 'No trust relationships found'
        }
        return @()
    }

    # ── Build trust result objects ────────────────────────────────────
    $trusts = [System.Collections.Generic.List[hashtable]]::new()

    foreach ($trust in $trustResults) {
        $trustPartner = if ($trust.ContainsKey('trustpartner')) { $trust['trustpartner'] }
                        elseif ($trust.ContainsKey('cn')) { $trust['cn'] }
                        else { '' }
        $flatName     = if ($trust.ContainsKey('flatname')) { $trust['flatname'] } else { '' }

        # Trust direction
        $trustDirInt = if ($trust.ContainsKey('trustdirection')) { [int]$trust['trustdirection'] } else { 0 }
        $trustDirStr = if ($trustDirectionMap.ContainsKey($trustDirInt)) {
            $trustDirectionMap[$trustDirInt]
        } else {
            "Unknown ($trustDirInt)"
        }

        # Trust type
        $trustTypeInt = if ($trust.ContainsKey('trusttype')) { [int]$trust['trusttype'] } else { 0 }
        $trustTypeStr = if ($trustTypeMap.ContainsKey($trustTypeInt)) {
            $trustTypeMap[$trustTypeInt]
        } else {
            "Unknown ($trustTypeInt)"
        }

        # Trust attributes (bitmask)
        $trustAttribs = if ($trust.ContainsKey('trustattributes')) { [int]$trust['trustattributes'] } else { 0 }

        # Decode attribute flags
        $isNonTransitive      = ($trustAttribs -band 0x0001) -ne 0
        $isUplevelOnly        = ($trustAttribs -band 0x0002) -ne 0
        $isQuarantined        = ($trustAttribs -band 0x0004) -ne 0   # SID filtering
        $isForestTransitive   = ($trustAttribs -band 0x0008) -ne 0
        $isCrossOrganization  = ($trustAttribs -band 0x0010) -ne 0   # Selective authentication
        $isWithinForest       = ($trustAttribs -band 0x0020) -ne 0
        $isTreatAsExternal    = ($trustAttribs -band 0x0040) -ne 0
        $usesRC4              = ($trustAttribs -band 0x0080) -ne 0
        $noTgtDelegation      = ($trustAttribs -band 0x0200) -ne 0
        $isPimTrust           = ($trustAttribs -band 0x0400) -ne 0
        $enableTgtDelegation  = ($trustAttribs -band 0x0800) -ne 0

        # Transitivity: a trust is transitive unless NON_TRANSITIVE is set
        $isTransitive = -not $isNonTransitive

        # SID filtering: QUARANTINED_DOMAIN flag means SID filtering is enabled.
        # For external trusts, SID filtering is on by default. For forest trusts it depends on the flag.
        $sidFilteringEnabled = $isQuarantined

        # SID History: generally the inverse of SID filtering for cross-domain trusts,
        # but only meaningful when SID filtering could be applied
        $sidHistoryEnabled = -not $isQuarantined

        # Azure AD trust detection: look for common indicators
        $isAzureAD = $false
        if ($trustPartner -match '\.windows\.net$' -or
            $trustPartner -match 'microsoftonline\.com$' -or
            $trustPartner -match 'AzureAD$' -or
            $flatName -match '^AzureAD' -or
            $flatName -match '^AAD') {
            $isAzureAD = $true
        }

        # Security identifier of the trusted domain
        $trustSid = ''
        if ($trust.ContainsKey('securityidentifier')) {
            $sidValue = $trust['securityidentifier']
            if ($sidValue -is [byte[]]) {
                try {
                    $trustSid = (New-Object System.Security.Principal.SecurityIdentifier($sidValue, 0)).Value
                } catch {
                    Write-Verbose "Failed to parse trust SID for $trustPartner`: $_"
                }
            } elseif ($sidValue -is [string]) {
                $trustSid = $sidValue
            }
        }

        $trustObj = @{
            TrustPartner            = $trustPartner
            FlatName                = $flatName
            TrustDirection          = $trustDirStr
            TrustDirectionInt       = $trustDirInt
            TrustType               = $trustTypeStr
            TrustTypeInt            = $trustTypeInt
            TrustAttributes         = $trustAttribs
            IsTransitive            = $isTransitive
            SIDFilteringEnabled     = $sidFilteringEnabled
            SelectiveAuthentication = $isCrossOrganization
            ForestTransitive        = $isForestTransitive
            WithinForest            = $isWithinForest
            TreatAsExternal         = $isTreatAsExternal
            UsesRC4Encryption       = $usesRC4
            NoTGTDelegation         = $noTgtDelegation
            PIMTrust                = $isPimTrust
            EnableTGTDelegation     = $enableTgtDelegation
            SIDHistoryEnabled       = $sidHistoryEnabled
            IsAzureAD               = $isAzureAD
            TrustSID                = $trustSid
            WhenCreated             = if ($trust.ContainsKey('whencreated')) { $trust['whencreated'] } else { $null }
            WhenChanged             = if ($trust.ContainsKey('whenchanged')) { $trust['whenchanged'] } else { $null }
            DistinguishedName       = if ($trust.ContainsKey('distinguishedname')) { $trust['distinguishedname'] } else { '' }
        }

        $trusts.Add($trustObj)
    }

    # ── Summary ───────────────────────────────────────────────────────
    if (-not $Quiet) {
        $inbound  = @($trusts | Where-Object { $_.TrustDirection -eq 'Inbound' }).Count
        $outbound = @($trusts | Where-Object { $_.TrustDirection -eq 'Outbound' }).Count
        $bidir    = @($trusts | Where-Object { $_.TrustDirection -eq 'Bidirectional' }).Count
        $forestTr = @($trusts | Where-Object { $_.ForestTransitive }).Count

        $summary = "Found $($trusts.Count) trust(s): $bidir bidirectional, $inbound inbound, $outbound outbound"
        if ($forestTr -gt 0) {
            $summary += ", $forestTr forest"
        }
        Write-ProgressLine -Phase AUDITING -Message $summary
    }

    return @($trusts)
}