sorry for long delay (i forgot). here are my function with function calls:
function Get-FastADObject {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$SearchBase,
[Parameter(Mandatory = $false)]
[ValidateSet('User', 'Group', 'Computer', 'Any')]
[string]$Category = 'User',
[Parameter(Mandatory = $false)]
[string]$Server,
[Parameter(Mandatory = $false)]
[string[]]$Attributes = @('Name', 'SamAccountName', 'DistinguishedName', 'Enabled')
)
process {
# 1. Virtual Attribute Mapping (RSAT Names -> LDAP Names)
$virtualMap = @{
'Enabled' = 'userAccountControl'
'AccountExpirationDate' = 'accountExpires'
'PasswordLastSet' = 'pwdLastSet'
'LastLogon' = 'lastLogonTimestamp'
'LastLogonDate' = 'lastLogonTimestamp'
'Created' = 'whenCreated'
'Modified' = 'whenChanged'
'Members' = 'member'
'EmailAddress' = 'mail'
'Surname' = 'sn'
'LockedOut' = 'msDS-User-Account-Control-Computed'
'lockoutTime' = 'lockoutTime'
'PasswordExpired' = 'pwdLastSet'
'PasswordNeverExpires' = 'userAccountControl'
'SID' = 'objectSid'
}
# 2. Build the list of real LDAP attributes to load
$ldapAttributes = New-Object System.Collections.Generic.HashSet[string]
foreach ($attr in $Attributes) {
$mapped = $false
foreach ($vKey in $virtualMap.Keys) {
if ($attr -ieq $vKey) {
$null = $ldapAttributes.Add($virtualMap[$vKey].ToLower())
$mapped = $true
break
}
}
if (-not $mapped) {
$null = $ldapAttributes.Add($attr.ToLower())
}
}
# 3. Setup DSSearcher
$prefix = if ($Server) { "LDAP://$Server/" } else { "LDAP://" }
try {
$root = [ADSI]($prefix + $SearchBase)
# Trigger a bind to verify path
$null = $root.NativeGuid
}
catch {
Write-Error "Failed to connect to SearchBase '$SearchBase'. Check path and Server. Error: $_"
return
}
$searcher = [ADSISearcher]$root
$searcher.PageSize = 1000
# Category Filters
$searcher.Filter = switch ($Category) {
'User' { "(&(objectClass=user)(objectCategory=person))" }
'Group' { "(objectClass=group)" }
'Computer' { "(objectClass=computer)" }
'Any' { "(|(sAMAccountName=*)(objectClass=group))" } # Broad filter for principal objects
}
# Add attributes to searcher
foreach ($lAttr in $ldapAttributes) {
$null = $searcher.PropertiesToLoad.Add($lAttr)
}
# 4. Execute Search
$results = $searcher.FindAll()
# 5. Process Results
foreach ($r in $results) {
$propHash = [ordered]@{ }
foreach ($attr in $Attributes) {
# Find corresponding LDAP attribute name
$mappedLDAP = $null
foreach ($vKey in $virtualMap.Keys) {
if ($attr -ieq $vKey) { $mappedLDAP = $virtualMap[$vKey].ToLower(); break }
}
$currentLdapAttr = if ($mappedLDAP) { $mappedLDAP } else { $attr.ToLower() }
$val = $r.Properties[$currentLdapAttr]
# Special Case: 'Enabled' (calculated from userAccountControl bit 2)
if ($attr -ieq 'Enabled') {
if ($null -ne $val -and $val.Count -gt 0) {
$propHash['Enabled'] = (-not ($val[0] -band 2))
}
else {
$propHash['Enabled'] = $null
}
continue
}
if ($attr -ieq 'SID') {
if ($null -ne $val -and $val.Count -gt 0) {
$propHash['SID'] = (New-Object System.Security.Principal.SecurityIdentifier($val[0], 0)).Value
}
else {
$propHash['SID'] = $null
}
continue
}
# Special Case: 'LockedOut'
if ($attr -ieq 'LockedOut') {
if ($null -ne $val -and $val.Count -gt 0) {
try {
$propHash['LockedOut'] = (([long]$val[0] -band 0x10) -ne 0)
}
catch {
$propHash['LockedOut'] = $false
}
}
else {
$propHash['LockedOut'] = $false
}
continue
}
# Special Case: 'lockoutTime' (return the actual datetime)
if ($attr -ieq 'lockoutTime') {
if ($null -ne $val -and $val.Count -gt 0) {
try {
$longVal = [long]$val[0]
if ($longVal -eq 0) {
$propHash['lockoutTime'] = $null
}
else {
$propHash['lockoutTime'] = [datetime]::FromFileTime($longVal)
}
}
catch {
$propHash['lockoutTime'] = $val[0]
}
}
else {
$propHash['lockoutTime'] = $null
}
continue
}
# Special Case: 'PasswordExpired'
if ($attr -ieq 'PasswordExpired') {
# Simple check: if pwdLastSet is 0, password must be changed
if ($null -ne $val -and $val.Count -gt 0 -and $val[0] -eq 0) {
$propHash['PasswordExpired'] = $true
}
else {
$propHash['PasswordExpired'] = $false
}
continue
}
# Special Case: 'PasswordNeverExpires'
if ($attr -ieq 'PasswordNeverExpires') {
if ($null -ne $val -and $val.Count -gt 0) {
$propHash['PasswordNeverExpires'] = (($val[0] -band 65536) -eq 65536)
}
else {
$propHash['PasswordNeverExpires'] = $null
}
continue
}
# Handle Null/Empty
if ($null -eq $val -or $val.Count -eq 0) {
$propHash[$attr] = $null
continue
}
# Handle Multi-valued attributes (Join with semicolon)
# Specifically handle 'member', 'memberof', 'proxyaddresses' or any multi-count val
if ($val.Count -gt 1 -or $currentLdapAttr -match 'member|proxyaddresses') {
$propHash[$attr] = $val -join '; '
}
# Handle 64-bit Dates (FileTime)
elseif ($currentLdapAttr -match 'expires|pwdlastset|lastlogon|timestamp|lockouttime') {
try {
$longVal = [long]$val[0]
if ($longVal -eq 0 -or $longVal -eq 9223372036854775807) {
$propHash[$attr] = $null # or "Never"
}
else {
$propHash[$attr] = [datetime]::FromFileTime($longVal)
}
}
catch {
$propHash[$attr] = $val[0]
}
}
# Handle GeneralizedTime (whenCreated / whenChanged)
elseif ($currentLdapAttr -match 'when') {
try {
# Format is typically "20230101120000.0Z"
$strDate = [string]$val[0]
if ($strDate -match '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})') {
$propHash[$attr] = Get-Date -Year $Matches[1] -Month $Matches[2] -Day $Matches[3] -Hour $Matches[4] -Minute $Matches[5] -Second $Matches[6]
}
else {
$propHash[$attr] = $strDate
}
}
catch {
$propHash[$attr] = $val[0]
}
}
# Standard Text/String
else {
$propHash[$attr] = [string]$val[0]
}
}
[PSCustomObject]$propHash
}
}
}
function Get-FastADUniversal {
[CmdletBinding(DefaultParameterSetName = 'ByLogins')]
param(
[Parameter(Mandatory = $true, ParameterSetName = 'ByLogins')]
[string]$Server,
[Parameter(Mandatory = $true, ParameterSetName = 'ByLogins')]
[string[]]$LoginArray,
[Parameter(Mandatory = $false, ParameterSetName = 'BySearchBase')]
[string]$SearchBase,
[Parameter(Mandatory = $false, ParameterSetName = 'BySearchBase')]
[ValidateSet('User', 'Group', 'Computer', 'Any')]
[string]$Category = 'User',
[Parameter(Mandatory = $false, ParameterSetName = 'BySearchBase')]
[string]$DomainController,
[Parameter(Mandatory = $true)]
[string[]]$Properties,
[Parameter(Mandatory = $false)]
[switch]$AsHashtable = $false
)
$virtualMap = @{
'Enabled' = 'userAccountControl'
'AccountExpirationDate' = 'accountExpires'
'PasswordLastSet' = 'pwdLastSet'
'LastLogon' = 'lastLogonTimestamp'
'LastLogonDate' = 'lastLogonTimestamp'
'Created' = 'whenCreated'
'Modified' = 'whenChanged'
'Members' = 'member'
'EmailAddress' = 'mail'
'Surname' = 'sn'
'LockedOut' = 'msDS-User-Account-Control-Computed'
'lockoutTime' = 'lockoutTime'
'PasswordExpired' = 'pwdLastSet'
'PasswordNeverExpires' = 'userAccountControl'
'SID' = 'objectSid'
'LastBadPasswordAttempt' = 'badpasswordtime'
}
$ldapProperties = @()
foreach ($prop in $Properties) {
if ($virtualMap.ContainsKey($prop)) {
$ldapProperties += $virtualMap[$prop].ToLower()
}
else {
$ldapProperties += $prop.ToLower()
}
}
if ($PSCmdlet.ParameterSetName -eq 'ByLogins' -and "samaccountname" -notin $ldapProperties) {
$ldapProperties += "samaccountname"
}
$searcher = if ($PSCmdlet.ParameterSetName -eq 'ByLogins') {
$filterParts = foreach ($adlogin in $LoginArray) { "(sAMAccountName=$adlogin)" }
$finalFilter = "(|$( -join $filterParts ))"
$root = [ADSI]"LDAP://$Server"
$s = [adsisearcher]$root
$s.Filter = $finalFilter
$s
}
else {
$prefix = if ($DomainController) { "LDAP://$DomainController/" } else { "LDAP://" }
try {
$root = [ADSI]($prefix + $SearchBase)
$null = $root.NativeGuid
}
catch {
Write-Error "Failed to connect to SearchBase '$SearchBase'. Check path and Server. Error: $_"
return
}
$s = [ADSISearcher]$root
$s.PageSize = 1000
$s.Filter = switch ($Category) {
'User' { "(&(objectClass=user)(objectCategory=person))" }
'Group' { "(objectClass=group)" }
'Computer' { "(objectClass=computer)" }
'Any' { "(|(sAMAccountName=*)(objectClass=group))" }
}
$s
}
$searcher.PageSize = 100
$searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::None
$searcher.SearchScope = "Subtree"
$searcher.PropertiesToLoad.AddRange(@($ldapProperties))
$results = $searcher.FindAll()
if ($PSCmdlet.ParameterSetName -eq 'ByLogins' -or $AsHashtable) {
$adLookup = @{}
foreach ($r in $results) {
$property = $r.Properties
if ($null -eq $property) {
continue
}
$loginKey = if ($property.samaccountname -and $property.samaccountname.Count -gt 0) {
$property.samaccountname[0].ToString().ToLower()
}
else {
$null
}
$userObject = @{}
foreach ($prop in $Properties) {
$ldapAttr = if ($virtualMap.ContainsKey($prop)) {
$virtualMap[$prop].ToLower()
}
else {
$prop.ToLower()
}
$allValues = $property[$ldapAttr]
$userObject[$prop] = Get-ProcessedValue -PropertyCollection $property -PropertyName $prop -VirtualMap $virtualMap -AllValues $allValues
}
if ($loginKey) {
$adLookup[$loginKey] = $userObject
}
}
return $adLookup
}
else {
$outputArray = @()
foreach ($r in $results) {
$property = $r.Properties
if ($null -eq $property) {
continue
}
$propHash = [ordered]@{}
foreach ($prop in $Properties) {
$ldapAttr = if ($virtualMap.ContainsKey($prop)) {
$virtualMap[$prop].ToLower()
}
else {
$prop.ToLower()
}
$allValues = $property[$ldapAttr]
$propHash[$prop] = Get-ProcessedValue -PropertyCollection $property -PropertyName $prop -VirtualMap $virtualMap -AllValues $allValues
}
$outputArray += [PSCustomObject]$propHash
}
return $outputArray
}
}
function Get-ProcessedValue {
param(
$PropertyCollection,
[string]$PropertyName,
[hashtable]$VirtualMap,
$AllValues
)
$ldapAttr = if ($VirtualMap.ContainsKey($PropertyName)) {
$VirtualMap[$PropertyName].ToLower()
}
else {
$PropertyName.ToLower()
}
$val = $PropertyCollection[$ldapAttr]
if ($null -eq $val -or $val.Count -eq 0) {
return $null
}
$rawValue = $val[0]
switch -Wildcard ($PropertyName) {
'Enabled' {
return -not [bool]($rawValue -band 2)
}
'SID' {
if ($rawValue -is [byte[]]) {
return (New-Object System.Security.Principal.SecurityIdentifier($rawValue, 0)).Value
}
return $rawValue
}
'LockedOut' {
try {
return (([long]$rawValue -band 0x10) -ne 0)
}
catch {
return $false
}
}
'lockoutTime' {
try {
$longVal = [long]$rawValue
if ($longVal -eq 0) { return $null }
return [DateTime]::FromFileTime($longVal)
}
catch {
return $rawValue
}
}
'PasswordExpired' {
return $rawValue -eq 0
}
'PasswordNeverExpires' {
return [bool]($rawValue -band 65536)
}
'LastLogon*' {
try {
if ([long]$rawValue -gt 0) {
return [DateTime]::FromFileTime($rawValue)
}
return $null
}
catch {
return $null
}
}
'PasswordLastSet' {
try {
if ([long]$rawValue -gt 0) {
return [DateTime]::FromFileTime($rawValue)
}
return $null
}
catch {
return $null
}
}
'AccountExpirationDate' {
try {
$longVal = [long]$rawValue
if ($longVal -gt 0 -and $longVal -ne 9223372036854775807) {
return [DateTime]::FromFileTime($longVal)
}
return $null
}
catch {
return $null
}
}
'LastBadPasswordAttempt' {
try {
if ([long]$rawValue -gt 0) {
return [DateTime]::FromFileTime($rawValue)
}
return $null
}
catch {
return $null
}
}
'Created' {
try {
$strDate = [string]$rawValue
if ($strDate -match '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})') {
return Get-Date -Year $Matches[1] -Month $Matches[2] -Day $Matches[3] -Hour $Matches[4] -Minute $Matches[5] -Second $Matches[6]
}
return $strDate
}
catch {
return $rawValue
}
}
'Modified' {
try {
$strDate = [string]$rawValue
if ($strDate -match '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})') {
return Get-Date -Year $Matches[1] -Month $Matches[2] -Day $Matches[3] -Hour $Matches[4] -Minute $Matches[5] -Second $Matches[6]
}
return $strDate
}
catch {
return $rawValue
}
}
'Members' {
if ($AllValues.Count -gt 1) {
return , @($AllValues)
}
elseif ($AllValues.Count -eq 1) {
return $rawValue
}
return $null
}
default {
if ($val.Count -gt 1 -or $ldapAttr -match 'member|proxyaddresses') {
return $val -join '; '
}
elseif ($ldapAttr -match 'expires|pwdlastset|lastlogon|timestamp|lockouttime') {
try {
$longVal = [long]$rawValue
if ($longVal -eq 0 -or $longVal -eq 9223372036854775807) {
return $null
}
return [DateTime]::FromFileTime($longVal)
}
catch {
return $rawValue
}
}
elseif ($ldapAttr -match 'when') {
try {
$strDate = [string]$rawValue
if ($strDate -match '^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})') {
return Get-Date -Year $Matches[1] -Month $Matches[2] -Day $Matches[3] -Hour $Matches[4] -Minute $Matches[5] -Second $Matches[6]
}
return $strDate
}
catch {
return $rawValue
}
}
else {
return [string]$rawValue
}
}
}
}
and how i call them :
$myAttributes = @('SamAccountName', 'displayname', 'LockedOut', 'lockoutTime', 'PasswordExpired', 'PasswordLastSet', 'Enabled', 'PasswordNeverExpires', 'Name', 'Description', 'DistinguishedName')
$cache:adm_users_object = Get-FastADUniversal -SearchBase $adm_ou -Category User -Properties $myAttributes -DomainController dc002
$cache:pc_ous = Get-ADObject -LDAPFilter "(objectClass=organizationalUnit)" -SearchBase $pc_ou -Properties distinguishedName, Name
$myAttributes = @('name', 'SamAccountName', 'info', 'Members', 'Description', 'DistinguishedName')
$cache:all_security_groups_object = Get-FastADUniversal -SearchBase $OU_Security -Category Group -Properties $myAttributes -DomainController dc002
$myAttributes = @('name', 'LastLogonDate', 'OperatingSystem', 'whenChanged', 'Description', 'Enabled', 'DistinguishedName')
$cache:ad_computers = Get-FastADUniversal -SearchBase $pc_ou -Category Computer -Properties $myAttributes -DomainController dc002
I hope it will help