Private/AD/Core/Invoke-LdapQuery.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 Invoke-LdapQuery { [CmdletBinding()] param( [Parameter(Mandatory)] [System.DirectoryServices.DirectoryEntry]$SearchRoot, [Parameter(Mandatory)] [string]$Filter, [string[]]$Properties = @('distinguishedName'), [System.DirectoryServices.SearchScope]$Scope = 'Subtree', [int]$PageSize = 1000, [int]$SizeLimit = 0, [switch]$RawResults ) $searcher = [System.DirectoryServices.DirectorySearcher]::new($SearchRoot) $searcher.Filter = $Filter $searcher.SearchScope = $Scope $searcher.PageSize = $PageSize if ($SizeLimit -gt 0) { $searcher.SizeLimit = $SizeLimit } # Request security descriptors in binary form for ACL analysis $needsSD = $Properties -contains 'ntsecuritydescriptor' -or $Properties -contains 'ntSecurityDescriptor' if ($needsSD) { $searcher.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl -bor [System.DirectoryServices.SecurityMasks]::Owner -bor [System.DirectoryServices.SecurityMasks]::Group } foreach ($prop in $Properties) { [void]$searcher.PropertiesToLoad.Add($prop.ToLower()) } try { $searchResults = $searcher.FindAll() } catch { Write-Warning "LDAP query failed: $Filter — $_" return @() } if ($RawResults) { return $searchResults } $output = [System.Collections.Generic.List[hashtable]]::new() foreach ($result in $searchResults) { $obj = @{} foreach ($propName in $result.Properties.PropertyNames) { $values = $result.Properties[$propName] if ($values.Count -eq 1) { $obj[$propName] = Convert-LdapValue -Name $propName -Value $values[0] } elseif ($values.Count -gt 1) { $obj[$propName] = @(foreach ($v in $values) { Convert-LdapValue -Name $propName -Value $v }) } } $output.Add($obj) } $searchResults.Dispose() $searcher.Dispose() return @($output) } function Convert-LdapValue { [CmdletBinding()] param( [string]$Name, [object]$Value ) if ($null -eq $Value) { return $null } $nameLower = $Name.ToLower() # Byte arrays: SIDs, GUIDs, security descriptors if ($Value -is [byte[]]) { switch -Wildcard ($nameLower) { 'objectsid' { return (New-Object System.Security.Principal.SecurityIdentifier($Value, 0)).Value } 'objectguid' { return ([guid]$Value).ToString() } 'securityidentifier' { return (New-Object System.Security.Principal.SecurityIdentifier($Value, 0)).Value } 'msds-generationid' { return [BitConverter]::ToString($Value) } 'sidhistory' { try { return (New-Object System.Security.Principal.SecurityIdentifier($Value, 0)).Value } catch { return [BitConverter]::ToString($Value) } } 'ntsecuritydescriptor' { return $Value } # Keep raw for ACL parsing 'msds-allowedtoactonbehalfofotheridentity' { return $Value } # Raw SD default { return [BitConverter]::ToString($Value) } } } # Large integers (timestamps, password ages) if ($Value -is [System.Int64] -or $Value -is [long]) { switch -Wildcard ($nameLower) { 'pwdlastset' { return Convert-FileTimeToDateTime $Value } 'lastlogontimestamp' { return Convert-FileTimeToDateTime $Value } 'lastlogon' { return Convert-FileTimeToDateTime $Value } 'accountexpires' { return Convert-FileTimeToDateTime $Value } 'badpasswordtime' { return Convert-FileTimeToDateTime $Value } 'lockouttime' { return Convert-FileTimeToDateTime $Value } 'msds-lastsuccessfulinteractivelogontime' { return Convert-FileTimeToDateTime $Value } 'maxpwdage' { return Convert-ADTimeSpan $Value } 'minpwdage' { return Convert-ADTimeSpan $Value } 'lockoutduration' { return Convert-ADTimeSpan $Value } 'lockoutobservationwindow' { return Convert-ADTimeSpan $Value } 'forcelogoff' { return Convert-ADTimeSpan $Value } default { return $Value } } } # COM objects (IADsLargeInteger) if ($Value -is [System.__ComObject]) { try { $adsLargeInt = [System.Runtime.InteropServices.Marshal]::GetObjectForIUnknown( [System.Runtime.InteropServices.Marshal]::GetIUnknownForObject($Value) ) $highPart = $adsLargeInt.GetType().InvokeMember('HighPart', 'GetProperty', $null, $adsLargeInt, $null) $lowPart = $adsLargeInt.GetType().InvokeMember('LowPart', 'GetProperty', $null, $adsLargeInt, $null) $int64Val = ([int64]$highPart -shl 32) -bor [uint32]$lowPart switch -Wildcard ($nameLower) { 'pwdlastset' { return Convert-FileTimeToDateTime $int64Val } 'lastlogontimestamp' { return Convert-FileTimeToDateTime $int64Val } 'lastlogon' { return Convert-FileTimeToDateTime $int64Val } 'accountexpires' { return Convert-FileTimeToDateTime $int64Val } 'maxpwdage' { return Convert-ADTimeSpan $int64Val } 'minpwdage' { return Convert-ADTimeSpan $int64Val } 'lockoutduration' { return Convert-ADTimeSpan $int64Val } 'lockoutobservationwindow' { return Convert-ADTimeSpan $int64Val } default { return $int64Val } } } catch { return $Value.ToString() } } return $Value } function Convert-FileTimeToDateTime { [CmdletBinding()] param([long]$FileTime) if ($FileTime -le 0 -or $FileTime -eq [long]::MaxValue -or $FileTime -eq 0x7FFFFFFFFFFFFFFF) { return $null # Never / not set } try { return [datetime]::FromFileTimeUtc($FileTime) } catch { return $null } } function Convert-ADTimeSpan { [CmdletBinding()] param([long]$Value) if ($Value -eq 0 -or $Value -eq [long]::MinValue -or $Value -eq [long]::MaxValue) { return [timespan]::Zero } # AD stores time spans as negative 100-nanosecond intervals try { return [timespan]::FromTicks([Math]::Abs($Value)) } catch { return [timespan]::Zero } } function Get-UACFlags { [CmdletBinding()] param([int]$UserAccountControl) @{ SCRIPT = ($UserAccountControl -band 0x0001) -ne 0 ACCOUNTDISABLE = ($UserAccountControl -band 0x0002) -ne 0 HOMEDIR_REQUIRED = ($UserAccountControl -band 0x0008) -ne 0 LOCKOUT = ($UserAccountControl -band 0x0010) -ne 0 PASSWD_NOTREQD = ($UserAccountControl -band 0x0020) -ne 0 PASSWD_CANT_CHANGE = ($UserAccountControl -band 0x0040) -ne 0 ENCRYPTED_TEXT_PWD_ALLOWED = ($UserAccountControl -band 0x0080) -ne 0 NORMAL_ACCOUNT = ($UserAccountControl -band 0x0200) -ne 0 INTERDOMAIN_TRUST_ACCOUNT = ($UserAccountControl -band 0x0800) -ne 0 WORKSTATION_TRUST_ACCOUNT = ($UserAccountControl -band 0x1000) -ne 0 SERVER_TRUST_ACCOUNT = ($UserAccountControl -band 0x2000) -ne 0 DONT_EXPIRE_PASSWORD = ($UserAccountControl -band 0x10000) -ne 0 MNS_LOGON_ACCOUNT = ($UserAccountControl -band 0x20000) -ne 0 SMARTCARD_REQUIRED = ($UserAccountControl -band 0x40000) -ne 0 TRUSTED_FOR_DELEGATION = ($UserAccountControl -band 0x80000) -ne 0 NOT_DELEGATED = ($UserAccountControl -band 0x100000) -ne 0 USE_DES_KEY_ONLY = ($UserAccountControl -band 0x200000) -ne 0 DONT_REQ_PREAUTH = ($UserAccountControl -band 0x400000) -ne 0 PASSWORD_EXPIRED = ($UserAccountControl -band 0x800000) -ne 0 TRUSTED_TO_AUTH_FOR_DELEGATION = ($UserAccountControl -band 0x1000000) -ne 0 PARTIAL_SECRETS_ACCOUNT = ($UserAccountControl -band 0x04000000) -ne 0 } } |