Last active
September 9, 2024 20:59
-
-
Save indented-automation/7a96a71be7eac9afc750e98fddab488f to your computer and use it in GitHub Desktop.
Get LDAP ACL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using assembly System.DirectoryServices.Protocols | |
using namespace System.DirectoryServices.Protocols; using namespace System.Collections.Generic | |
function Get-LdapObject { | |
<# | |
.SYNOPSIS | |
Get objects from an LDAP directory. | |
.DESCRIPTION | |
Get objects from an LDAP directory. | |
.EXAMPLE | |
Get-LdapObject -LdapFilter "(sAMAccountName=$env:USERNAME)" -SearchBase (Get-LdapRootDSE).defaultNamingContext | |
#> | |
[CmdletBinding()] | |
param ( | |
# An LDAP filter. | |
[String]$LdapFilter = '(objectClass=*)', | |
# A list of attributes to return. | |
[String[]]$Attributes, | |
# The search base. | |
[String]$SearchBase, | |
# The scope of the search. If SearchBase is not set the SeachScope will be set to Base, returning RootDSE. | |
[SearchScope]$SearchScope = 'Subtree', | |
# The remote directory server. If server is not defined serverless binding is used to discover a DC within the current domain. | |
[String]$Server, | |
# Use SSL for this connection. | |
[Switch]$UseSSL, | |
# Ignore any certificate validation errors raised when using SSL. | |
[Switch]$IgnoreSSLErrors, | |
# Use a Global Catalog. | |
[Switch]$UseGC, | |
# Bind to the directory using the supplied credentials. | |
[PSCredential][System.Management.Automation.Credential()]$Credential, | |
# A set of LDAP controls to define when creating the search request. | |
[DirectoryControl[]]$Controls = @( | |
[PageResultRequestControl]::new(1000), | |
[SearchOptionsControl]::new('DomainScope') | |
) | |
) | |
try { | |
if ($useGC -and $useSSL) { | |
$port = 3268 | |
} elseif ($UseSSL) { | |
$port = 636 | |
} elseif ($UseGC) { | |
$port = 3269 | |
} else { | |
$port = 389 | |
} | |
$directoryIdentifier = [LdapDirectoryIdentifier]::new($Server, $port) | |
if ($Credential) { | |
$ldapConnection = [LdapConnection]::new($directoryIdentifier, $Credential.GetNetworkCredential()) | |
$ldapConnection.AuthType = 'Basic' | |
} else { | |
$ldapConnection = [LdapConnection]::new($directoryIdentifier) | |
$ldapConnection.AuthType = 'Kerberos' | |
} | |
if ($UseSSL) { | |
$ldapConnection.SessionOptions.ProtocolVersion = 3 | |
$ldapConnection.SessionOptions.SecureSocketLayer = $true | |
if ($IgnoreCertiticateError) { | |
$ldapConnection.SessionOptions.VerifyServerCertificate = { $true } | |
} | |
} | |
$searchRequest = [SearchRequest]::new() | |
$searchRequest.DistinguishedName = $SearchBase | |
$searchRequest.Filter = $LdapFilter | |
if (-not $SearchBase) { | |
$searchRequest.Scope = 'Base' | |
} else { | |
$searchRequest.Scope = $SearchScope | |
} | |
if ($Attributes) { | |
$searchRequest.Attributes.AddRange($Attributes) | |
} | |
if ($Controls) { | |
$searchRequest.Controls.AddRange($Controls) | |
} | |
$ldapConnection.Bind() | |
$complete = $false | |
do { | |
$searchResponse = $LdapConnection.SendRequest($searchRequest) | |
if ($searchResponse.Controls) { | |
$pageResponse = [PageResultResponseControl]$searchResponse.Controls[0] | |
if ($pageResponse.Cookie.Length -gt 0) { | |
$searchRequest.Controls[0].Cookie = $pageResponse.Cookie | |
} else { | |
$complete = $true | |
} | |
} else { | |
$complete = $true | |
} | |
$searchResponse.Entries | |
} until ($complete) | |
} catch { | |
$pscmdlet.ThrowTerminatingError($_) | |
} finally { | |
if ($ldapConnection) { | |
$ldapConnection.Dispose() | |
} | |
} | |
} | |
function Get-LdapRootDSE { | |
<# | |
.SYNOPSIS | |
Get information from Root DSE. | |
.DESCRIPTION | |
Get information from Root DSE. | |
.EXAMPLE | |
Get-LdapRootDSE | |
#> | |
[CmdletBinding()] | |
param ( | |
# The remote directory server. If server is not defined serverless binding is used to discover a DC within the current domain. | |
[String]$Server, | |
# Use SSL for this connection. | |
[Switch]$UseSSL, | |
# Ignore any certificate validation errors raised when using SSL. | |
[Switch]$IgnoreSSLErrors, | |
# Use a Global Catalog. | |
[Switch]$UseGC, | |
# Bind to the directory using the supplied credentials. | |
[PSCredential][System.Management.Automation.Credential()]$Credential, | |
# Absorbs any arguments passed to this command but not intended for this command. | |
[Parameter(DontShow, ValueFromRemainingArguments)] | |
$remainingArgs | |
) | |
try { | |
$null = $psboundparameters.Remove('remainingArgs') | |
$searchResult = Get-LdapObject @psboundparameters | |
$rootDSE = New-Object PSObject | |
foreach ($attribute in $searchResult.Attributes.Keys) { | |
$value = $searchResult.Attributes[$attribute].GetValues([String]) | |
if ($value.Count -eq 1) { | |
$value = $value[0] | |
} | |
$rootDSE | Add-Member $attribute $value | |
} | |
$rootDSE | |
} catch { | |
$pscmdlet.ThrowTerminatingError($_) | |
} | |
} | |
function Get-LdapExtendedRight { | |
<# | |
.SYNOPSIS | |
Get an extended right based on the right GUID. | |
.DESCRIPTION | |
Get an extended right based on the right GUID. | |
.EXAMPLE | |
Get-LdapExtendedRight -RightsGuid '1f298a89-de98-47b8-b5cd-572ad53d267e' | |
#> | |
[CmdletBinding()] | |
[OutputType([String])] | |
param ( | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[Guid]$RightsGuid, | |
[String]$SearchBase, | |
# The remote directory server. If server is not defined serverless binding is used to discover a DC within the current domain. | |
[String]$Server, | |
# Use SSL for this connection. | |
[Switch]$UseSSL, | |
# Ignore any certificate validation errors raised when using SSL. | |
[Switch]$IgnoreSSLErrors, | |
# Use a Global Catalog. | |
[Switch]$UseGC, | |
# Bind to the directory using the supplied credentials. | |
[PSCredential][System.Management.Automation.Credential()]$Credential, | |
# Absorbs any arguments passed to this command but not intended for this command. | |
[Parameter(DontShow, ValueFromRemainingArguments)] | |
$remainingArgs | |
) | |
begin { | |
$null = $psboundparameters.Remove('remainingArgs') | |
if (-not $Script:adExtendedRightCache) { | |
$Script:adExtendedRightCache = [Dictionary[Guid,String]]::new() | |
} | |
if (-not $SearchBase) { | |
$null = $psboundparameters.Add('SearchBase', 'CN=Extended-Rights,{0}' -f (Get-LdapRootDSE @psboundparameters).configurationNamingContext) | |
} | |
} | |
process { | |
if ($Script:adExtendedRightCache.ContainsKey($RightsGuid)) { | |
$Script:adExtendedRightCache[$RightsGuid] | |
} else { | |
$null = $psboundparameters.Remove('RightsGuid') | |
$ldapFilter = '(rightsGuid={0})' -f $RightsGuid.ToString() | |
# Discard duplicates. Handles duplicate rightsGuid on Validated-DNS-Host-Name and DNS-Host-Name-Attributes | |
$searchResult = Get-LdapObject -LdapFilter $ldapFilter -Attributes displayName @psboundparameters | | |
Select-Object -First 1 | |
if ($searchResult) { | |
$displayName = $searchResult.Attributes['displayname'].GetValues([String])[0] | |
$Script:adExtendedRightCache.Add($RightsGuid, $displayName) | |
$displayName | |
} | |
} | |
} | |
} | |
function Get-LdapDisplayName { | |
<# | |
.SYNOPSIS | |
Get the LDAP display name for an attribute in the schema. | |
.DESCRIPTION | |
Get the LDAP display name for an attribute in the schema based on the attribute GUID. | |
.EXAMPLE | |
Get-LdapDisplayName -SchemaIDGuid bf967a0a-0de6-11d0-a285-00aa003049e2 | |
#> | |
[CmdletBinding()] | |
[OutputType([String])] | |
param ( | |
# The GUID of the schema entry to retrieve. | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[Guid]$SchemaIDGuid, | |
# The search base. If not set, this command will attempt to discover the schema naming context. | |
[String]$SearchBase, | |
# The remote directory server. If server is not defined serverless binding is used to discover a DC within the current domain. | |
[String]$Server, | |
# Use SSL for this connection. | |
[Switch]$UseSSL, | |
# Ignore any certificate validation errors raised when using SSL. | |
[Switch]$IgnoreSSLErrors, | |
# Use a Global Catalog. | |
[Switch]$UseGC, | |
# Bind to the directory using the supplied credentials. | |
[PSCredential][System.Management.Automation.Credential()]$Credential, | |
# Absorbs any arguments passed to this command but not intended for this command. | |
[Parameter(DontShow, ValueFromRemainingArguments)] | |
$remainingArgs | |
) | |
begin { | |
$null = $psboundparameters.Remove('remainingArgs') | |
if (-not $Script:adSchemaDisplayName) { | |
$Script:adSchemaDisplayName = [Dictionary[Guid,String]]::new() | |
} | |
if (-not $SearchBase) { | |
$null = $psboundparameters.Add('SearchBase', (Get-LdapRootDSE @psboundparameters).schemaNamingContext) | |
} | |
} | |
process { | |
if ($Script:adSchemaDisplayName.ContainsKey($SchemaIDGuid)) { | |
$Script:adSchemaDisplayName[$SchemaIDGuid] | |
} else { | |
$null = $psboundparameters.Remove('SchemaIDGuid') | |
$hex = foreach ($byte in $SchemaIDGuid.ToByteArray()) { | |
'{0:X2}' -f $byte | |
} | |
$ldapFilter = '(SchemaIDGUID=\{0})' -f ($hex -join '\') | |
$searchResult = Get-LdapObject -LdapFilter $ldapFilter -Attributes ldapDisplayName @psboundparameters | |
if ($searchResult) { | |
$ldapDisplayName = $searchResult.Attributes['ldapdisplayname'].GetValues([String])[0] | |
$Script:adSchemaDisplayName.Add($SchemaIDGuid, $ldapDisplayName) | |
$ldapDisplayName | |
} | |
} | |
} | |
} | |
function Get-LdapAcl { | |
<# | |
.SYNOPSIS | |
Get access control lists from Active Directory. | |
.DESCRIPTION | |
Get-LdapAcl uses System.DirectoryServices.Protocols to execute searches against an LDAP directory. | |
.EXAMPLE | |
Get-LdapAcl -LdapFilter "(sAMAccountName=$env:USERNAME)" | |
#> | |
[CmdletBinding(DefaultParameterSetName = 'UsingLdapFilter')] | |
[OutputType('Indented.DS.Security.DACL')] | |
param ( | |
# An LDAP filter to use with the search. The filter (objectClass=*) is used by default. | |
[Parameter(ParameterSetName = 'UsingLdapFilter')] | |
[String]$LdapFilter = '(objectClass=organizationalUnit)', | |
# The search root must be specified as a distinguishedName. | |
[String]$SearchBase, | |
# The search scope is either Base, OneLevel or Subtree. Subtree is the default value. | |
[SearchScope]$SearchScope = 'Subtree', | |
# An optional server to use for this query. If server is not populated Get-ADObject uses serverless binding, passing off server selection to the site-aware DC locator process. | |
# | |
# Server is mandatory when executing a query against a remote domain. | |
[String]$Server, | |
# Credentials to use for the LDAP search. | |
[PSCredential][System.Management.Automation.Credential()]$Credential | |
) | |
try { | |
$rootDSE = Get-LdapRootDSE @psboundparameters | |
$extendedRightsDN = 'CN=Extended-Rights,{0}' -f $rootDSE.configurationNamingContext | |
$schemaDN = $rootDSE.schemaNamingContext | |
$null = $psboundparameters.Remove('SearchBase') | |
$params = @{ | |
Controls = @( | |
[PageResultRequestControl]::new(1000), | |
[SearchOptionsControl]::new('DomainScope') | |
[SecurityDescriptorFlagControl]::new('Dacl') | |
) | |
Attributes = 'distinguishedName', 'name', 'ntSecurityDescriptor' | |
SearchBase = $SearchBase | |
} | |
if (-not $SearchBase) { | |
$params.SearchBase = $rootDSE.defaultNamingContext | |
} | |
Get-LdapObject @psboundparameters @params | ForEach-Object { | |
$searchResult = $_ | |
$bytes = $searchResult.Attributes['ntsecuritydescriptor'].GetValues([Byte[]])[0] | |
$securityDescriptor = [System.DirectoryServices.ActiveDirectorySecurity]::new() | |
$securityDescriptor.SetSecurityDescriptorBinaryForm($bytes) | |
foreach ($ace in $securityDescriptor.Access) { | |
$ace | Add-Member ObjectName $searchResult.Attributes['name'][0] | |
$ace | Add-Member DistinguishedName $searchResult.Attributes['distinguishedname'][0] | |
$objectTypeNames = $ace.ObjectType | ForEach-Object { | |
if ($_ -ne [Guid]'00000000-0000-0000-0000-000000000000') { | |
if ($extendedRight = Get-LdapExtendedRight -RightsGuid $_ -SearchBase $extendedRightsDN @psboundparameters) { | |
$extendedRight | |
} else { | |
Get-LdapDisplayName -SchemaIDGUID $_ -SearchBase $schemaDN @psboundparameters | |
} | |
} | |
} | |
$ace | Add-Member ObjectTypeNames $objectTypeNames | |
$inheritedObjectTypeNames = $ace.InheritedObjectType | | |
Where-Object { $_ -ne [Guid]'00000000-0000-0000-0000-000000000000' } | | |
Get-LdapDisplayName @psboundparameters -SearchBase $schemaDN | |
$ace | Add-Member InheritedObjectTypeNames $inheritedObjectTypeNames | |
$ace | Add-Member -TypeName 'Indented.DS.Security.DACL' -PassThru | |
} | |
} | |
} catch { | |
$pscmdlet.ThrowTerminatingError($_) | |
} | |
} | |
Update-TypeData -TypeName 'Indented.DS.Security.DACL' -DefaultDisplayPropertySet ObjectName, ActiveDirectoryRights, ObjectTypeNames, InheritedObjectTypeNames, AccessControlType, IdentityReference, IsInherited, InheritanceFlags, PropagationFlags -Force | |
Get-LdapAcl -LdapFilter "(sAMAccountName=$env:USERNAME)" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment