SDDLParser.psm1

<#
    SDDLParser module
    Version 0.5.0.0 of 2021-05-25
#>


function Convert-CharArrayToGUID {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        $InputValue
    )
    if (16 -eq $InputValue.Count) {
        $out = ('{0:x2}{1:x2}{2:x2}{3:x2}-{4:x2}{5:x2}-{6:x2}{7:x2}-{8:x2}{9:x2}-{10:x2}{11:x2}{12:x2}{13:x2}{14:x2}{15:x2}' -f $InputValue[3], $InputValue[2], $InputValue[1], $InputValue[0], $InputValue[5], $InputValue[4], $InputValue[7], $InputValue[6], $InputValue[8], $InputValue[9], $InputValue[10], $InputValue[11], $InputValue[12], $InputValue[13], $InputValue[14], $InputValue[15])
        $out = $out.ToLower()
    } else {
        Write-Warning ('[Convert-CharArrayToGUID] Wrong byte count [{0}], should be 16' -f $InputValue.Count)
        $out = $null
    }
    return $out
}

<#
.SYNOPSIS
Updates extended rights and schema object definitions from the live Active Directory.
 
.DESCRIPTION
Updates extended rights and schema object definitions from the live Active Directory.
 
.EXAMPLE
PS> Update-ADSchemaObjects
 
.INPUTS
This function expects no input.
 
.OUTPUTS
This function outputs the number of objects added to the inventory.
 
.NOTES
 
#>

function Update-ADSchemaGUID {
    try {
        $rootDSE = [ADSI]'LDAP://rootDSE'
        $cTime = $rootDSE.currentTime[0]
    } catch {
        Write-Warning $_.Exception.Message
        $rootDSE = $null
    }
    $i = -1
    if ($null -ne $rootDSE) {
        $i = 0
        $schemaNC = $rootDSE.schemaNamingContext[0]
        $schemaDE = [ADSI]"LDAP://$schemaNC"
        $configNC = $rootDSE.configurationNamingContext[0]
        $configDE = [ADSI]"LDAP://CN=Extended-Rights,$configNC"
        $dsSearcher = New-Object System.DirectoryServices.DirectorySearcher
        $dsSearcher.SearchRoot = $schemaDE
        $dsSearcher.SearchScope = [System.DirectoryServices.SearchScope]::Subtree
        $dsSearcher.PageSize = 1000
        $dsSearcher.Filter = "(|(objectClass=attributeSchema)(objectClass=classSchema))"
        $results = $dsSearcher.FindAll()
        foreach ($result in $results) {
            $objectName = $result.Properties['name'][0]
            $objectClass = $result.Properties['objectClass'][1]
            $objectGUID = (Convert-CharArrayToGUID -InputValue $result.Properties['schemaIDGUID'][0]).ToLower()
            try {
                $script:schemaObjects.Add($objectGUID, $objectName)
                $i++
            } catch {}
            try {
                $script:schemaObjectClasses.Add($objectGUID, $objectClass)
            } catch {}
        }
        $dsSearcher.SearchRoot = $configDE
        $dsSearcher.Filter = "(objectClass=controlAccessRight)"
        $results = $dsSearcher.FindAll()
        foreach ($result in $results) {
            $objectGUID = $result.Properties['rightsGUID'][0].ToLower()
            $objectName = $result.Properties['name'][0]
            try {
                $script:extendedRights.Add($objectGUID, $objectName)
                $i++
            } catch {}
        }
    }
    return $i
}

<#
.SYNOPSIS
Splits an SDDL string into functional parts.
 
.DESCRIPTION
This function splits an SDDL string into functional parts: O (owner user), G (owner group), DF (DACL flags), D (DACL list), SF (SACL flags) and S (SACL list).
 
.PARAMETER SDDLString
An SDDL string to be split.
 
.EXAMPLE
PS> Split-SDDLString -SDDLString "O:BAG:BAD:AI(D;;DC;;;WD)(A;;LCRPLORC;;;ED)(A;;LCRPLORC;;;AU)S:(AU;SA;WPWDWO;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)"
 
Name Value
---- -----
SF
O BA
DF AI
S (AU;SA;WPWDWO;;;WD)(AU;SA;CR;;;BA)(AU;SA;CR;;;DU)
G BA
D (D;;DC;;;WD)(A;;LCRPLORC;;;ED)(A;;LCRPLORC;;;AU)
 
.OUTPUTS
This function returns a string if the SID could be resolved to a known security principal or retrieved from AD. Otherwise, no value is returned.
 
.NOTES
 
#>


function Split-SDDLString {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [string]
        $SDDLString
    )
    $groups = @{
        'O' = $null
        'G' = $null
        'S' = $null
        'SF' = $null
        'D' = $null
        'DF' = $null
    }
    $groupBounds = $SDDLString.IndexOf('O:'), $SDDLString.IndexOf('G:'), $SDDLString.IndexOf('D:'), $SDDLString.IndexOf('S:') | Where-Object {$_ -ge 0} | Sort-Object
    for ($i = 0; $i -lt $groupBounds.Count; $i++) {
        $gn = $SDDLString.Substring($groupBounds[$i],1).ToUpper()
        if ($i -lt ($groupBounds.Count - 1)) {
            $group = $SDDLString.Substring($groupBounds[$i] + 2, ($groupBounds[$i+1] - $groupBounds[$i] - 2))
        } else {
            $group = $SDDLString.Substring($groupBounds[$i] + 2)
        }
        $groups[$gn] = $group
    }
    if ($null -ne $groups['S']) {
        if ($groups['S'].Substring(0,1) -ne '(') {
            $groups['SF'] = $groups['S'].Substring(0, $groups['S'].IndexOf('('))
            $groups['S'] = $groups['S'].Substring($groups['S'].IndexOf('('))
        }
    }
    if ($null -ne $groups['D']) {
        if ($groups['D'].Substring(0,1) -ne '(') {
            $groups['DF'] = $groups['D'].Substring(0, $groups['D'].IndexOf('('))
            $groups['D'] = $groups['D'].Substring($groups['D'].IndexOf('('))
        }
    }
    return $groups
}

<#
.SYNOPSIS
Resolves a well-known principal SID to its display name.
 
.DESCRIPTION
This function resolves a SID supplied as a text string ("S-1-...") to a display name of the corresponding security principal.
Principals known by SID (built-in principals) or RID (domain principals) are resolved usingm a prepopulated list, i.e. the name may not correspond to the representation of that principal in AD.
 
.PARAMETER SID
A string representation of the SID to be resolved.
 
.PARAMETER ResolveFromAD
If supplied, the function will attempt to resolve the SID from Active Directory if neither the SID nor the RID corresponds to a well-known principal.
 
.EXAMPLE
PS> Resolve-WellKnownPrincipal -SID S-1-5-11
Authenticated Users
 
.EXAMPLE
PS> Resolve-WellKnownPrincipal -SID S-1-5-21-3157449993-2863816718-3428150947-500
Administrator
 
.EXAMPLE
PS> Resolve-WellKnownPrincipal -SID S-1-5-21-3157449993-2863816718-3428150947-1601 -ResolveFromAD
DOMAIN\a.user
 
.OUTPUTS
This function returns a string if the SID could be resolved to a known security principal or retrieved from AD. Otherwise, no value is returned.
 
.NOTES
 
#>


function Resolve-WellKnownPrincipal {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [string]
        $SID,
        [Parameter(Mandatory=$false)]
        [switch]
        $ResolveFromAD
    )
    # https://docs.microsoft.com/en-us/windows/win32/secauthz/well-known-sids
    # https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/security-identifiers-in-windows
    $sidWKP = @{
        'S-1-0-0' = 'NULL'
        'S-1-1-0' = 'EVERYONE'
        'S-1-2-0' = 'LOCAL'
        'S-1-2-1' = 'Console Logon'
        'S-1-3-0' = 'CREATOR-OWNER'
        'S-1-3-1' = 'CREATOR-GROUP'
        'S-1-3-2' = 'CREATOR-OWNER SERVER'
        'S-1-3-3' = 'CREATOR-GROUP SERVER'
        'S-1-3-4' = 'OWNER RIGHTS'
        'S-1-5-1' = 'NT AUTHORITY\DialUp'
        'S-1-5-2' = 'NT AUTHORITY\Network'
        'S-1-5-3' = 'NT AUTHORITY\Batch'
        'S-1-5-4' = 'NT AUTHORITY\Interactive'
        #'S-1-5-5-*' = 'NT AUTHORITY\Logon Session' this cannot be matched by .ContainsKey()
        'S-1-5-6' = 'NT AUTHORITY\Service'
        'S-1-5-7' = 'NT AUTHORITY\Anonymous'
        'S-1-5-8' = 'NT AUTHORITY\Proxy'
        'S-1-5-9' = 'ENTERPRISE DOMAIN CONTROLLERS'
        'S-1-5-10' = 'SELF'
        'S-1-5-11' = 'Authenticated Users'
        'S-1-5-12' = 'Restricted Code'
        'S-1-5-13' = 'Terminal Services'
        'S-1-5-14' = 'Remote Interactive Logon'
        'S-1-5-15' = 'This Organization'
        'S-1-5-17' = 'IUSR'
        'S-1-5-18' = 'Local System'
        'S-1-5-19' = 'Local Service'
        'S-1-5-20' = 'Network Service'
        'S-1-5-32-544' = 'BUILTIN\Administrators'
        'S-1-5-32-545' = 'BUILTIN\Users'
        'S-1-5-32-546' = 'BUILTIN\Guests'
        'S-1-5-32-547' = 'BUILTIN\Power Users'
        'S-1-5-32-548' = 'BUILTIN\Account Operators'
        'S-1-5-32-549' = 'BUILTIN\Server Operators'
        'S-1-5-32-550' = 'BUILTIN\Print Operators'
        'S-1-5-32-551' = 'BUILTIN\Backup Operators'
        'S-1-5-32-552' = 'BUILTIN\Replicator'
        'S-1-5-32-553' = 'BUILTIN\RAS and IAS Servers'
        'S-1-5-32-554' = 'BUILTIN\Pre-Windows 2000 compatible access'
        'S-1-5-32-555' = 'BUILTIN\Remote Desktop Users'
        'S-1-5-32-556' = 'BUILTIN\Network Configuration Operators'
        'S-1-5-32-557' = 'BUILTIN\Incoming Forest Trust Bilders'
        'S-1-5-32-558' = 'BUILTIN\Performance Monitor Users'
        'S-1-5-32-559' = 'BUILTIN\Preformance Log Users'
        'S-1-5-32-560' = 'BUILTIN\Windows Authorization Access Group'
        'S-1-5-32-561' = 'BUILTIN\RDS License Servers'
        'S-1-5-32-562' = 'BUILTIN\DCOM Users'
        'S-1-5-32-568' = 'BUILTIN\IIS_IUSRS'
        'S-1-5-32-569' = 'BUILTIN\Cryptography Operators'
        'S-1-5-32-571' = 'BUILTIN\Cacheable Principals'
        'S-1-5-32-572' = 'BUILTIN\Non-Cacheable Principals'
        'S-1-5-32-573' = 'BUILTIN\Event Log Readers'
        'S-1-5-32-574' = 'BUILTIN\Certificate Service DCOM access'
        'S-1-5-32-575' = 'BUILTIN\RDS Remote Access Servers'
        'S-1-5-32-576' = 'BUILTIN\RDS Endpoint Servers'
        'S-1-5-32-577' = 'BUILTIN\RDS Management Servers'
        'S-1-5-32-578' = 'BUILTIN\Hyper-V Admins'
        'S-1-5-32-579' = 'BUILTIN\Access Control Assistance Operators'
        'S-1-5-32-580' = 'BUILTIN\Remote management Users'
        'S-1-5-32-581' = 'BUILTIN\Default Account'
        'S-1-5-32-582' = 'BUILTIN\Storage Replica Admins'
        'S-1-5-32-583' = 'BUILTIN\Device Owners'
        'S-1-5-33' = 'Write Restricted Code'
        'S-1-5-80-0' = 'All Services'
        'S-1-5-83-0' = 'NT Virtual Machine\Virtual Machines'
        'S-1-5-90-0' = 'Windows Manager\Windows Manager Group'
        'S-1-16-0' = 'Untrusted Mandatory Level'
        'S-1-16-4096' = 'Low Mandatory Level'
        'S-1-16-8192' = 'Medium Mandatory Level'
        'S-1-16-8448' = 'Medium Plus Mandatory Level'
        'S-1-16-12288' = 'High Mandatory Level'
        'S-1-16-16384' = 'System Mandatory Level'
        'S-1-16-20480' = 'Protected Process Mandatory Level'
        'S-1-16-28672' = 'Secure Process Mandatory Level'
    }
    $ridWKP = @{
        '498' = 'Enterprise RODC'
        '500' = 'Administrator'
        '501' = 'Guest'
        '502' = 'KRBTGT'
        '512' = 'Domain Admins'
        '513' = 'Domain Users'
        '514' = 'Domain Guests'
        '515' = 'Domain Computers'
        '516' = 'Domain Controllers'
        '517' = 'Certificate Publishers'
        '518' = 'Schema Admins'
        '519' = 'Enterprise Admins'
        '520' = 'Group Policy Creator Owners'
        '521' = 'Read-Only Domain Controllers'
        '522' = 'Cloneable Domain Controllers'
        '525' = 'Protected Users'
        '526' = 'Key Admins'
        '527' = 'Enterprise Key Admins'
        '553' = 'RAS and IAS Servers'
        '571' = 'Allowed RODC Password Replication Group'
        '572' = 'Denied RODC Password Replication Group'
    }
    $result = $null
    if ($sidWKP.ContainsKey($SID)) {
        $result = $sidWKP[$SID]
    } else {
        $RID = ($SID -split '\-')[-1]
        if ($ridWKP.ContainsKey($RID)) {
            $result = $ridWKP[$RID]
        } elseif ($ResolveFromAD) {
            try {
                $objSID = New-Object System.Security.Principal.SecurityIdentifier ($SID)
            } catch {
                Write-Warning ('Cannot create a valid SID from {0}' -f $SID)
                $objSID = $null
            }
            if ($null -ne $objSID) {
                try {
                    $objPrincipal = $objSID.Translate([System.Security.Principal.NTAccount]) 
                    $result = $objPrincipal.Value
                } catch {
                    Write-Warning ('Cannot translate {0} to a valid principal: {1}' -f $SID, $_.Exception.Message)
                }
            }
        }
    }
    return $result
}

<#
.SYNOPSIS
Resolves a trustee in SDDL notation to its SID.
 
.DESCRIPTION
This function resolves a trustee in SDDL notation (e.g. BA for Builtin\Administrators) to its SID or display name.
 
.PARAMETER SDDLPrincipal
An SDDL principal or a SID as listed in an ACE.
 
.PARAMETER DomainSID
Domain SID of the current domain. It is used to compose principals that are only known by their RID, i.e. DA = <DomainSID>-512.
 
.PARAMETER RootDomainSID
Domain SID of the forest root domain. It is used to compose forest-wide principals that are only known by their RID, i.e. EK = <RootDomainSID>-527.
 
.PARAMETER ResolveToName
If this switch is specified, the resulting SID will be passed to Resolve-WellKnownPrincipal for further resolution.
 
.PARAMETER ResolveFromAD
This switch is only used in conjunction with -ResolveToName and is passed to Resolve-WellKnownPrincipal.
 
.EXAMPLE
PS> Resolve-SDDLPrincipal -SDDLPrincipal BA
S-1-5-32-544
 
.EXAMPLE
PS> Resolve-SDDLPrincipal -SDDLPrincipal BA -ResolveToName
BUILTIN\Administrators
 
.EXAMPLE
PS> Resolve-SDDLPrincipal -SDDLPrincipal S-1-5-21-3157449993-2863816718-3428150947-1601 -ResolveToName
S-1-5-21-3157449993-2863816718-3428150947-1601
 
.EXAMPLE
PS> Resolve-SDDLPrincipal -SDDLPrincipal S-1-5-21-3157449993-2863816718-3428150947-1601 -ResolveToName -ResolveFromAD
DOMAIN\a.user
 
.OUTPUTS
This function returns a string containing either the resolved value (SID or display name) or the original value, in case the supplied SDDL principal could not be resolved.
 
.NOTES
 
#>


function Resolve-SDDLPrincipal {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [string]
        $SDDLPrincipal,
        [Parameter(Mandatory=$false)]
        [string]
        $DomainSID = 'S-DOM',
        [Parameter(Mandatory=$false)]
        [string]
        $RootDomainSID = 'S-ROOT',
        [Parameter(Mandatory=$false)]
        [switch]
        $ResolveToName,
        [Parameter(Mandatory=$false)]
        [switch]
        $ResolveFromAD
    )
    $shortWKSP = @{
        'AA' = 'S-1-5-32-579' # access control assistance operators
        'AC' = 'SECURITY_BUILTIN_PACKAGE_ANY_PACKAGE' # all app packages
        'AN' = 'S-1-5-7' # Anonymous logon
        'AO' = 'S-1-5-32-548' # Account operators
        'AP' = 'S-DOM-525' # Protected Users
        'AU' = 'S-1-5-11' # Authenticated users
        'BA' = 'S-1-5-32-544' # Built-in Administrators
        'BG' = 'S-1-5-32-546' # Built-in guests
        'BO' = 'S-1-5-32-551' # backup operators
        'BU' = 'S-1-5-32-545' # Built-In Users
        'CA' = 'S-DOM-517' # certificate publishers
        'CD' = 'S-1-5-32-574' # cert services DCOM access
        'CG' = 'S-1-3-1' # creator group
        'CN' = 'S-DOM-522' #cloneable DCs
        'CO' = 'S-1-3-0' # creator owner
        'CY' = 'S-1-5-32-569' # cryprto operators
        'DA' = 'S-DOM-512' # domain admins
        'DC' = 'S-DOM-515' # domain computers
        'DD' = 'S-DOM-516' # domain controllers
        'DG' = 'S-DOM-514' # domain guests
        'DU' = 'S-DOM-513' # domain users
        'EA' = 'S-ROOT-519' # enterprise admins
        'ED' = 'S-1-5-9' # enterprise domain controllers
        'EK' = 'S-ROOT-527' # enterprise key admins
        'ER' = 'S-1-5-32-573' # event log readers
        'ES' = 'S-1-5-32-576' # RDS endpoint servers
        'HA' = 'S-1-5-32-578' # hyper-v admins
        'HI' = 'S-1-16-12288' # high integrity
        'IS' = 'S-1-5-32-568' # IIS Users
        'IU' = 'S-1-5-4' # interactive user
        'KA' = 'S-DOM-526' # key admin
        'LA' = 'S-DOM-500' # local administrator
        'LG' = 'S-DOM-501' # local guest
        'LS' = 'S-1-5-19' # local service
        'LU' = 'S-1-5-32-559' # perflog users
        'LW' = 'S-1-16-4096' # low integrity
        'ME' = 'S-1-16-8192' # medium integrity
        'MP' = 'S-1-16-8448' # medium plus integrity
        'MU' = 'S-1-5-32-558' # perfmon users
        'NO' = 'S-1-5-32-556' # network config operators
        'NS' = 'S-1-5-20' # network service
        'NU' = 'S-1-5-2' # network logon
        'OW' = 'S-1-3-4' # owner rights
        'PA' = 'S-DOM-520' # group policy admins
        'PO' = 'S-1-5-32-550' # print operators
        'PS' = 'S-1-5-10' # Principal SELF
        'PU' = 'S-1-5-32-547' # power users
        'RA' = 'S-1-5-32-575' # remote access servers
        'RC' = 'S-1-5-12' # restricted code
        'RD' = 'S-1-5-13' # terminal server users
        'RE' = 'S-1-5-32-552' # replicator
        'RM' = 'VISTA_RMS_SERVICE' # rms service, vista only
        'RO' = 'S-ROOT-498' # enterprise RODCs
        'RS' = 'S-DOM-553' # RAS servers
        'RU' = 'S-1-5-32-554' # Pre-Win200 compatible access
        'SA' = 'S-ROOT-518' # schema admins
        'SI' = 'S-1-16-16384' # system integrity
        'SO' = 'S-1-5-32-549' # server operators
        'SS' = 'SECURITY_AUTHENTICATION_SERVICE_ASSERTED_RID' # service asserted
        'SU' = 'S-1-5-6' # service logon user
        'SY' = 'S-1-5-18' # SYSTEM
        'UD' = 'SECURITY_USERMODEDRIVERHOST_ID_BASE_RID' # user mode drivers
        'WD' = 'S-1-1-0' # everyone
        'WR' = 'S-1-5-33' # write restricted code
    }
    if ($shortWKSP.ContainsKey($SDDLPrincipal)) {
        $result = (($shortWKSP[$SDDLPrincipal] -replace 'S-DOM',$DomainSID) -replace 'S-ROOT', $RootDomainSID)
    } else {
        $result = $SDDLPrincipal
    }
    if ($ResolveToName) {
        if ($ResolveFromAD) {
            $name = Resolve-WellKnownPrincipal -SID $result -ResolveFromAD
        } else {
            $name = Resolve-WellKnownPrincipal -SID $result
        }
        if ($null -ne $name) {
            $result = $name
        }
    }
    return $result
}

<#
.SYNOPSIS
Parses SDDL String
 
.DESCRIPTION
 
 
.PARAMETER SDDLString
 
.EXAMPLE
PS>
 
.OUTPUTS
 
 
.NOTES
 
#>

function Expand-SDDLString {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory=$true)]
        [string]
        $SDDLString,
        [Parameter(Mandatory=$false)]
        [string]
        $DomainSID = 'S-DOM',
        [Parameter(Mandatory=$false)]
        [string]
        $RootDomainSID = 'S-ROOT',
        [Parameter(Mandatory=$false)]
        [ValidateSet('None','SID','WellKnown','NameFromAD')]
        [string]
        $ResolvePrincipalsTo = 'SID',
        [Parameter(Mandatory=$false)]
        [switch]
        $SplitACEs,
        [Parameter(Mandatory=$false)]
        [switch]
        $DoNotResolveACETypes,
        [Parameter(Mandatory=$false)]
        [switch]
        $DoNotResolveACEFlags
    )
    # https://docs.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language
    $result = $null
    $sddlParts = Split-SDDLString -SDDLString $SDDLString
    if ((($sddlParts['D']).Count -gt 0) -or (($sddlParts['S']).Count -gt 0)) {
        # prepopulate hashtables
        $aceTypes = @{
            'A' = 'Allow'
            'D' = 'Deny'
            'OA' = 'Object Allow'
            'OD' = 'Object Deny'
            'AU' = 'System Audit'
            'AL' = 'System Alarm'
            'OU' = 'Object Audit'
            'OL' = 'Object Alarm'
            'ML' = 'Mandatory Level'
            'SP' = 'Scoped Policy'
            'XA' = 'Callback Access Allowed'
            'XD' = 'Callback Access Denied'
            'RA' = 'Resource Attribute'
            'XU' = 'Callback Audit'
            'ZA' = 'Callback Object Access Allowed'
            'TL' = 'Process Trust Label'
            'FL' = 'Access Filter'
        }
        $aceFlags = @{
            'CI' = 'Container Inherit'
            'OI' = 'Object Inherit'
            'NP' = 'No propagate inherit'
            'IO' = 'Inherit only'
            'ID' = 'Inherited'
            'SA' = 'Audit Success'
            'FA' = 'Audit Failure'
            'TP' = 'Trust protected filter'
            'CR' = 'Critical'
        }
        $accessRights = @{
            'GA' = 'Generic All'
            'GR' = 'Generic Read'
            'GW' = 'Generic Write'
            'GX' = 'Generic Execute'
            'RC' = 'Read Permissions'
            'SD' = 'Delete'
            'WD' = 'Modify Permissions'
            'WO' = 'Modify Owner'
            'RP' = 'Read All Properties'
            'WP' = 'Write All Properties'
            'CC' = 'Create All Child Objects'
            'DC' = 'Delete All Child Objects'
            'LC' = 'List Contents'
            'SW' = 'All Validated Writes'
            'LO' = 'List Object'
            'DT' = 'Delete Subtree'
            'CR' = 'Extended Rights'
            'FA' = 'File Generic All'
            'FR' = 'File Generic Read'
            'FW' = 'File Generic Write'
            'FX' = 'File Generic Execute'
            'KA' = 'RegKey Generic All'
            'KR' = 'RegKey Generic Read'
            'KW' = 'RegKey Generic Write'
            'KX' = 'RegKey Generic Execute'
        }
        $result = New-Object -TypeName PSCustomObject -Property @{
            'Owner' = $null
            'OwnerGroup' = $null
            'DACLFlags' = $null
            'DACL' = @()
            'SACLFlags' = $null
            'SACL' = @()
        }
        switch ($ResolvePrincipalsTo) {
            'None' {
                $result.Owner = $sddlParts['O']
                $result.OwnerGroup = $sddlParts['G']
            }
            'SID' {
                $result.Owner = Resolve-SDDLPrincipal -SDDLPrincipal $sddlParts['O'] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
                $result.OwnerGroup = Resolve-SDDLPrincipal -SDDLPrincipal $sddlParts['G'] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
            }
            'WellKnown' {
                $result.Owner = Resolve-WellKnownPrincipal -SID (Resolve-SDDLPrincipal -SDDLPrincipal $sddlParts['O'] -DomainSID $DomainSID -RootDomainSID $RootDomainSID)
                $result.OwnerGroup = Resolve-WellKnownPrincipal -SID (Resolve-SDDLPrincipal -SDDLPrincipal $sddlParts['G'] -DomainSID $DomainSID -RootDomainSID $RootDomainSID)
            }
            'NameFromAD' {
                $result.Owner = Resolve-WellKnownPrincipal -SID (Resolve-SDDLPrincipal -SDDLPrincipal $sddlParts['O'] -DomainSID $DomainSID -RootDomainSID $RootDomainSID) -ResolveFromAD
                $result.OwnerGroup = Resolve-WellKnownPrincipal -SID (Resolve-SDDLPrincipal -SDDLPrincipal $sddlParts['G'] -DomainSID $DomainSID -RootDomainSID $RootDomainSID) -ResolveFromAD
            }
        }
        $result.DACLFlags = $sddlParts['DF']
        $result.SACLFlags = $sddlParts['SF']
        if (![string]::IsNullOrWhiteSpace($sddlParts['D'])) {
            $daclstring = ($sddlParts['D']).Substring(1, (($sddlParts['D']).Length -2))
            $daces = $daclstring -split "\)\("
            foreach ($ace in $daces) {
                $aceparts = $ace -split ";"
                switch ($ResolvePrincipalsTo) {
                    'None' {
                        $trustee = $aceparts[5]
                    }
                    'SID' {
                        $trustee = Resolve-SDDLPrincipal -SDDLPrincipal $aceparts[5] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
                    }
                    'WellKnown' {
                        $tsid = Resolve-SDDLPrincipal -SDDLPrincipal $aceparts[5] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
                        if ($null -ne (Resolve-WellKnownPrincipal -SID $tsid)) {
                            $trustee = Resolve-WellKnownPrincipal -SID $tsid
                        } else {
                            $trustee = $tsid
                        }
                    }
                    'NameFromAD' {
                        $tsid = Resolve-SDDLPrincipal -SDDLPrincipal $aceparts[5] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
                        $tnad = Resolve-WellKnownPrincipal -SID $tsid -ResolveFromAD
                        if ($null -ne $tnad) {
                            $trustee = $tnad
                        } else {
                            $trustee = $tsid
                        }
                    }
                }
                $acetemplate = New-Object -TypeName PSCustomObject -Property @{
                    'AccessType' = $aceparts[0]
                    'AccessFlags' = $aceparts[1]
                    'AccessRightsRaw' = $aceparts[2]
                    'AccessRights' = $aceparts[2]
                    'ExtendedRight' = ''
                    'PermissionTarget' = ''
                    'Object' = $aceparts[3]
                    'ObjectInherit' = $aceparts[4]
                    'Trustee' = $trustee
                    'IsInherited' = $false
                }

                $ob = $null
                $oi = $null
                $er = $null
                $pt = $null
                if (![string]::IsNullOrEmpty($aceparts[3])) {
                    $ob = $aceparts[3]
                }
                if (![string]::IsNullOrEmpty($aceparts[4])) {
                    $oi = $aceparts[4]
                }
                
                if ($null -eq $ob) {
                    $er = $null
                    if ($null -ne $oi) {
                        if ($schemaObjects.ContainsKey($oi)) {
                            $pt = $schemaObjects[$oi]
                        } else {
                            $pt = "UNKNOWN [$oi]"
                        }
                    }
                } elseif ($extendedRights.ContainsKey($ob)) {
                    $er = $extendedRights[$ob]
                    if ($null -ne $oi) {
                        if ($schemaObjects.ContainsKey($oi)) {
                            $pt = $schemaObjects[$oi]
                        } else {
                            $pt = "UNKNOWN [$oi]"
                        }
                    }
                } elseif ($null -ne $oi) {
                    if ($schemaObjects.ContainsKey($oi)) {
                        $pt = $schemaObjects[$oi]
                    } else {
                        $pt = "UNKNOWN [$oi]"
                    }
                    if ($schemaObjects.ContainsKey($ob)) {
                        $er = $schemaObjects[$ob]
                    } else {
                        $er = "UNKNOWN [$ob]"
                    }
                } elseif ($schemaObjects.ContainsKey($ob)) {
                    $pt = $schemaObjects[$ob]
                } else {
                    $pt = "UNKNOWN [$ob]"
                }
                $acetemplate.ExtendedRight = $er
                $acetemplate.PermissionTarget = $pt

                if (!$DoNotResolveACETypes) {
                    if ($aceTypes.ContainsKey($aceparts[0])) {
                        $acetemplate.AccessType = $aceTypes[$aceparts[0]]
                    } else {
                        $acetemplate.AccessType = "UNKNOWN [$($aceparts[0])]"
                    }
                }
                
                $flags = @()
                if ($aceparts[1].Length -gt 0) {
                    for ($i = 0; $i -lt ($aceparts[1].Length - 1); $i = $i + 2) {
                        $flag = $aceparts[1].Substring($i, 2)
                        if ($flag -eq 'ID') {
                            $acetemplate.IsInherited = $true
                        }
                        if (!$DoNotResolveACEFlags) {
                            if ($aceFlags.ContainsKey($flag)) {
                                $flag = $aceFlags[$flag]
                            } else {
                                $flag = "UNKNOWN [$flag]"
                            }
                            $flags += $flag
                        }
                    }
                }
                if (!$DoNotResolveACEFlags) {
                    $acetemplate.AccessFlags = $flags
                }
                if ($SplitACEs) {
                    for ($i = 0; $i -lt ($aceparts[2].Length - 1); $i = $i + 2) {
                        $perm = $aceparts[2].Substring($i, 2)
                        $aceobject = $acetemplate
                        $aceobject.AccessRightsRaw = $perm
                        if ($accessRights.ContainsKey($perm)) {
                            $aceobject.AccessRights = $accessRights[$perm]
                        } else {
                            $aceobject.AccessRights = "UNKNOWN [$perm]"
                        }

                        $result.DACL += $aceobject
                    }
                } else {
                    $aceobject = $acetemplate
                    $aceobject.AccessRights = @()
                    for ($i = 0; $i -lt ($aceparts[2].Length - 1); $i = $i + 2) {
                        $perm = $aceparts[2].Substring($i, 2)
                        if ($accessRights.ContainsKey($perm)) {
                            $aceobject.AccessRights += $accessRights[$perm]
                        } else {
                            $aceobject.AccessRights += "UNKNOWN [$perm]"
                        }
                        
                    }
                    $result.DACL += $aceobject
                }
            }
        }
        if (![string]::IsNullOrWhiteSpace($sddlParts['S'])) {
            $saclstring = ($sddlParts['S']).Substring(1, (($sddlParts['S']).Length -2))
            $saces = $saclstring -split "\)\("
            foreach ($ace in $saces) {
                $aceparts = $ace -split ";"
                switch ($ResolvePrincipalsTo) {
                    'None' {
                        $trustee = $aceparts[5]
                    }
                    'SID' {
                        $trustee = Resolve-SDDLPrincipal -SDDLPrincipal $aceparts[5] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
                    }
                    'WellKnown' {
                        $tsid = Resolve-SDDLPrincipal -SDDLPrincipal $aceparts[5] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
                        if ($null -ne (Resolve-WellKnownPrincipal -SID $tsid)) {
                            $trustee = Resolve-WellKnownPrincipal -SID $tsid
                        } else {
                            $trustee = $tsid
                        }
                    }
                    'NameFromAD' {
                        $tsid = Resolve-SDDLPrincipal -SDDLPrincipal $aceparts[5] -DomainSID $DomainSID -RootDomainSID $RootDomainSID
                        $tnad = Resolve-WellKnownPrincipal -SID $tsid -ResolveFromAD
                        if ($null -ne $tnad) {
                            $trustee = $tnad
                        } else {
                            $trustee = $tsid
                        }
                    }
                }
                $acetemplate = New-Object -TypeName PSCustomObject -Property @{
                    'AccessType' = $aceparts[0]
                    'AccessFlags' = $aceparts[1]
                    'AccessRightsRaw' = $aceparts[2]
                    'AccessRights' = $aceparts[2]
                    'ExtendedRight' = ''
                    'PermissionTarget' = ''
                    'Object' = $aceparts[3]
                    'ObjectInherit' = $aceparts[4]
                    'Trustee' = $trustee
                    'IsInherited' = $false
                }

                $ob = $null
                $oi = $null
                $er = $null
                $pt = $null
                if (![string]::IsNullOrEmpty($aceparts[3])) {
                    $ob = $aceparts[3]
                }
                if (![string]::IsNullOrEmpty($aceparts[4])) {
                    $oi = $aceparts[4]
                }
                
                if ($null -eq $ob) {
                    $er = $null
                    if ($null -ne $oi) {
                        if ($schemaObjects.ContainsKey($oi)) {
                            $pt = $schemaObjects[$oi]
                        } else {
                            $pt = "UNKNOWN [$oi]"
                        }
                    }
                } elseif ($extendedRights.ContainsKey($ob)) {
                    $er = $extendedRights[$ob]
                    if ($null -ne $oi) {
                        if ($schemaObjects.ContainsKey($oi)) {
                            $pt = $schemaObjects[$oi]
                        } else {
                            $pt = "UNKNOWN [$oi]"
                        }
                    }
                } elseif ($null -ne $oi) {
                    if ($schemaObjects.ContainsKey($oi)) {
                        $pt = $schemaObjects[$oi]
                    } else {
                        $pt = "UNKNOWN [$oi]"
                    }
                    if ($schemaObjects.ContainsKey($ob)) {
                        $er = $schemaObjects[$ob]
                    } else {
                        $er = "UNKNOWN [$ob]"
                    }
                } elseif ($schemaObjects.ContainsKey($ob)) {
                    $pt = $schemaObjects[$ob]
                } else {
                    $pt = "UNKNOWN [$ob]"
                }
                $acetemplate.ExtendedRight = $er
                $acetemplate.PermissionTarget = $pt

                if (!$DoNotResolveACETypes) {
                    if ($aceTypes.ContainsKey($aceparts[0])) {
                        $acetemplate.AccessType = $aceTypes[$aceparts[0]]
                    } else {
                        $acetemplate.AccessType = "UNKNOWN [$($aceparts[0])]"
                    }
                }
                
                $flags = @()
                if ($aceparts[1].Length -gt 0) {
                    for ($i = 0; $i -lt ($aceparts[1].Length - 1); $i = $i + 2) {
                        $flag = $aceparts[1].Substring($i, 2)
                        if ($flag -eq 'ID') {
                            $acetemplate.IsInherited = $true
                        }
                        if (!$DoNotResolveACEFlags) {
                            if ($aceFlags.ContainsKey($flag)) {
                                $flag = $aceFlags[$flag]
                            } else {
                                $flag = "UNKNOWN [$flag]"
                            }
                            $flags += $flag
                        }
                    }
                }
                if (!$DoNotResolveACEFlags) {
                    $acetemplate.AccessFlags = $flags
                }
                if ($SplitACEs) {
                    for ($i = 0; $i -lt ($aceparts[2].Length - 1); $i = $i + 2) {
                        $perm = $aceparts[2].Substring($i, 2)
                        $aceobject = $acetemplate
                        $aceobject.AccessRightsRaw = $perm
                        if ($accessRights.ContainsKey($perm)) {
                            $aceobject.AccessRights = $accessRights[$perm]
                        } else {
                            $aceobject.AccessRights = "UNKNOWN [$perm]"
                        }

                        $result.SACL += $aceobject
                    }
                } else {
                    $aceobject = $acetemplate
                    $aceobject.AccessRights = @()
                    for ($i = 0; $i -lt ($aceparts[2].Length - 1); $i = $i + 2) {
                        $perm = $aceparts[2].Substring($i, 2)
                        if ($accessRights.ContainsKey($perm)) {
                            $aceobject.AccessRights += $accessRights[$perm]
                        } else {
                            $aceobject.AccessRights += "UNKNOWN [$perm]"
                        }
                        
                    }
                    $result.SACL += $aceobject
                }
            }
        }
    } else {
        Write-Warning 'SDDL string has an incorrect format!'
    }
    return $result
}
$adObjectsPath = Join-Path -Path $PSScriptRoot -ChildPath 'SDDLParserADObjects.ps1'
if (Test-Path $adObjectsPath -PathType Leaf) {
    . $adObjectsPath
} else {
    Write-Warning 'AD Objects include not found'
}
Export-ModuleMember -Function @('Expand-SDDLString', 'Resolve-SDDLPrincipal', 'Resolve-WellKnownPrincipal', 'Update-ADSchemaGUID')