Skip to content

Instantly share code, notes, and snippets.

@indented-automation
Last active September 9, 2024 20:59
Show Gist options
  • Save indented-automation/7a96a71be7eac9afc750e98fddab488f to your computer and use it in GitHub Desktop.
Save indented-automation/7a96a71be7eac9afc750e98fddab488f to your computer and use it in GitHub Desktop.
Get LDAP ACL
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