Private/ConvertFrom-SecurityDescriptor.ps1

function ConvertFrom-SecurityDescriptor {
    <#
    .SYNOPSIS
        Translates a security descriptor's DACL into an array of Ace[] objects
        matching the 'Ace' sub-object schema.
    .DESCRIPTION
        Read-only. Accepts raw nTSecurityDescriptor bytes, a base64 string, or an
        already-constructed [System.Security.AccessControl.RawSecurityDescriptor]
        and returns one [pscustomobject] per Allow/Deny ACE in the DACL with:
            PrincipalSid, PrincipalName, IsLowPriv, Rights(string[]),
            AccessMask(int), AceType(Allow|Deny)

        Two access-mask interpretation contexts are supported:
          - 'AdObject' (default): interprets the mask as Active Directory object
             rights and resolves extended-right ObjectType GUIDs to Enroll /
             AutoEnroll. Also maps WriteDacl, WriteOwner, WriteProperty,
             GenericAll, GenericWrite.
          - 'CaSecurity': interprets the mask as CERTSRV_ACCESS bits used by the
             CA object security descriptor (ManageCA=0x1, ManageCertificates=0x2,
             Enroll=0x200).

        SID -> name resolution is best effort and tolerates failure (PrincipalName
        becomes $null). Low-privilege classification is delegated to
        Test-EscLowPrivPrincipal.
    .PARAMETER SecurityDescriptor
        Raw descriptor: byte[], base64 string, or RawSecurityDescriptor.
    .PARAMETER Context
        'AdObject' (default) or 'CaSecurity'. Controls access-mask translation.
    .PARAMETER ExtraLowPrivSid
        Optional extra SIDs forwarded to Test-EscLowPrivPrincipal.
    .OUTPUTS
        [pscustomobject] Ace records (0..N).
    .EXAMPLE
        ConvertFrom-SecurityDescriptor -SecurityDescriptor $bytes
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [object] $SecurityDescriptor,

        [Parameter(Mandatory = $false)]
        [ValidateSet('AdObject', 'CaSecurity')]
        [string] $Context = 'AdObject',

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

    $enrollGuid     = '0e10c968-78fb-11d2-90d4-00c04f79dc55'
    $autoEnrollGuid = 'a05b8cc2-17bc-4802-a710-e7c15ab866a2'

    $ADS_RIGHT_DS_CONTROL_ACCESS = 0x00000100
    $ADS_RIGHT_DS_WRITE_PROP     = 0x00000020
    $WRITE_DAC                   = 0x00040000
    $WRITE_OWNER                 = 0x00080000
    $ADS_RIGHT_GENERIC_ALL       = 0x10000000
    $ADS_RIGHT_GENERIC_WRITE     = 0x40000000
    $CERTSRV_MANAGE_CA           = 0x00000001
    $CERTSRV_MANAGE_CERTS        = 0x00000002
    $CERTSRV_ENROLL              = 0x00000200

    $rsd = $null
    try {
        if ($null -eq $SecurityDescriptor) {
            return @()
        }
        elseif ($SecurityDescriptor -is [System.Security.AccessControl.RawSecurityDescriptor]) {
            $rsd = $SecurityDescriptor
        }
        elseif ($SecurityDescriptor -is [byte[]]) {
            $rsd = New-Object System.Security.AccessControl.RawSecurityDescriptor($SecurityDescriptor, 0)
        }
        elseif ($SecurityDescriptor -is [string]) {
            $bytes = [System.Convert]::FromBase64String($SecurityDescriptor)
            $rsd = New-Object System.Security.AccessControl.RawSecurityDescriptor($bytes, 0)
        }
        else {
            $bytes = [byte[]] $SecurityDescriptor
            $rsd = New-Object System.Security.AccessControl.RawSecurityDescriptor($bytes, 0)
        }
    }
    catch {
        Write-EscLog -Component 'SDParser' -Level Warning -Message ("Failed to parse security descriptor: {0}" -f $_.Exception.Message)
        return @()
    }

    if ($null -eq $rsd -or $null -eq $rsd.DiscretionaryAcl) {
        return @()
    }

    $results = New-Object System.Collections.ArrayList

    foreach ($ace in $rsd.DiscretionaryAcl) {
        $aceType = $null
        if ($ace.AceType -eq [System.Security.AccessControl.AceType]::AccessAllowed -or
            $ace.AceType -eq [System.Security.AccessControl.AceType]::AccessAllowedObject) {
            $aceType = 'Allow'
        }
        elseif ($ace.AceType -eq [System.Security.AccessControl.AceType]::AccessDenied -or
                $ace.AceType -eq [System.Security.AccessControl.AceType]::AccessDeniedObject) {
            $aceType = 'Deny'
        }
        else {
            continue
        }

        $sidValue = $null
        try { $sidValue = $ace.SecurityIdentifier.Value } catch { $sidValue = $null }

        $mask = 0
        try { $mask = [int] $ace.AccessMask } catch { $mask = 0 }

        $objectTypeGuid = $null
        $hasObjectType = $false
        try {
            if ($ace.PSObject.Properties.Name -contains 'ObjectAceFlags') {
                if (($ace.ObjectAceFlags -band [System.Security.AccessControl.ObjectAceFlags]::ObjectAceTypePresent) -ne 0) {
                    $hasObjectType = $true
                    $objectTypeGuid = $ace.ObjectAceType.ToString().ToLowerInvariant()
                }
            }
        }
        catch {
            $hasObjectType = $false
        }

        $rights = New-Object System.Collections.ArrayList

        if ($Context -eq 'CaSecurity') {
            if (($mask -band $CERTSRV_MANAGE_CA) -ne 0)    { [void]$rights.Add('ManageCA') }
            if (($mask -band $CERTSRV_MANAGE_CERTS) -ne 0) { [void]$rights.Add('ManageCertificates') }
            if (($mask -band $CERTSRV_ENROLL) -ne 0)       { [void]$rights.Add('Enroll') }
        }
        else {
            $isGenericAll   = (($mask -band $ADS_RIGHT_GENERIC_ALL) -ne 0)
            $isGenericWrite = (($mask -band $ADS_RIGHT_GENERIC_WRITE) -ne 0)

            if ($isGenericAll)   { [void]$rights.Add('GenericAll') }
            if ($isGenericWrite) { [void]$rights.Add('GenericWrite') }
            if (($mask -band $WRITE_DAC) -ne 0)   { [void]$rights.Add('WriteDacl') }
            if (($mask -band $WRITE_OWNER) -ne 0) { [void]$rights.Add('WriteOwner') }
            if (($mask -band $ADS_RIGHT_DS_WRITE_PROP) -ne 0) { [void]$rights.Add('WriteProperty') }

            if (($mask -band $ADS_RIGHT_DS_CONTROL_ACCESS) -ne 0) {
                if ($hasObjectType) {
                    if ($objectTypeGuid -eq $enrollGuid)     { [void]$rights.Add('Enroll') }
                    elseif ($objectTypeGuid -eq $autoEnrollGuid) { [void]$rights.Add('AutoEnroll') }
                }
                else {
                    [void]$rights.Add('Enroll')
                    [void]$rights.Add('AutoEnroll')
                }
            }

            if ($isGenericAll) {
                if ($rights -notcontains 'Enroll')     { [void]$rights.Add('Enroll') }
                if ($rights -notcontains 'AutoEnroll') { [void]$rights.Add('AutoEnroll') }
            }
        }

        $principalName = $null
        if (-not [string]::IsNullOrWhiteSpace($sidValue)) {
            try {
                $sidObj = New-Object System.Security.Principal.SecurityIdentifier($sidValue)
                $principalName = $sidObj.Translate([System.Security.Principal.NTAccount]).Value
            }
            catch {
                $principalName = $null
            }
        }

        $isLowPriv = $false
        try {
            $isLowPriv = Test-EscLowPrivPrincipal -Sid $sidValue -ExtraLowPrivSid $ExtraLowPrivSid
        }
        catch {
            $isLowPriv = $false
        }

        $obj = [pscustomobject]@{
            PrincipalSid  = $sidValue
            PrincipalName = $principalName
            IsLowPriv     = $isLowPriv
            Rights        = @($rights.ToArray())
            AccessMask    = $mask
            AceType       = $aceType
        }
        [void]$results.Add($obj)
    }

    return @($results.ToArray())
}