Private/UserLookupHelpers.ps1
|
function Get-NSPADForestDomainInfo { # Read every domain NC in the selected forest, excluding external references, application # partitions and pre-created (not yet active) domains. No GC partial attribute set is used. param([Parameter(Mandatory)][string]$Server, [int]$PageSize = 500) $seed = Get-NSPADDomainInfo -Server $Server if (-not $seed.ConfigurationDN) { throw 'The seed domain has no ConfigurationDN; forest discovery cannot continue.' } $refs = @(Read-NSPADLdap -Server $seed.Server -SearchBase "CN=Partitions,$($seed.ConfigurationDN)" -Scope OneLevel -Filter '(&(objectClass=crossRef)(systemFlags:1.2.840.113556.1.4.803:=3)(!(enabled=FALSE)))' -Property nCName,dnsRoot -PageSize $PageSize) if (-not $refs.Count) { throw 'No forest domain crossRefs are visible; refusing an empty forest inventory.' } $domains = @{} foreach ($ref in $refs) { $dn = [string]$ref['ncname'] $dns = [string]$ref['dnsroot'] if (-not $dn -or -not $dns) { throw 'A forest domain crossRef is missing nCName or dnsRoot.' } if ($domains.ContainsKey($dn)) { throw 'Multiple forest domain crossRefs have the same naming context.' } $dc = Find-NSPADDomainController -DomainName $dns $domain = Get-NSPADDomainInfo -Server $dc if ($domain.DistinguishedName -ne $dn -or $domain.DNSRoot -ne $dns -or $domain.ConfigurationDN -ne $seed.ConfigurationDN) { throw "Forest domain discovery resolved a different domain or forest for $dns." } $domains[$dn] = $domain } if (-not $domains.ContainsKey([string]$seed.DistinguishedName)) { throw 'The seed domain is absent from forest discovery.' } # Keep discovery order for predictable inventory output. foreach ($ref in $refs) { $domains[[string]$ref['ncname']] } } function ConvertTo-NSPADOptionalInt64 { # LDAP Integer8 values normally arrive as Int64. Retain exact integer text too, but never # coerce missing, floating point, Boolean or multi-valued data to a plausible zero/flag. param([AllowNull()]$Value) if ($null -eq $Value -or $Value -is [array] -or $Value -is [bool] -or $Value -is [double] -or $Value -is [single] -or $Value -is [decimal]) { return $null } $number = [long]0 if ([long]::TryParse([string]$Value, [Globalization.NumberStyles]::Integer, [Globalization.CultureInfo]::InvariantCulture, [ref]$number)) { return $number } return $null } function ConvertTo-NSPADUser { param([Parameter(Mandatory)][hashtable]$Entry, [Parameter(Mandatory)]$DomainInfo) foreach ($required in 'distinguishedname', 'objectguid', 'objectsid', 'useraccountcontrol') { if (-not $Entry.ContainsKey($required) -or $null -eq $Entry[$required]) { throw "LDAP user result is missing $required; identity and enabled state must be readable." } } $passwordTime = $null $passwordState = 'Unknown' $mustChange = $null $fileTime = ConvertTo-NSPADOptionalInt64 -Value $Entry['pwdlastset'] if ($null -ne $fileTime) { if ($fileTime -eq 0) { $passwordState = 'Zero' $mustChange = [bool](-not ([int]$Entry['useraccountcontrol'] -band 0x10000)) } elseif ($fileTime -gt 0) { try { $passwordTime = [datetime]::FromFileTimeUtc($fileTime) $passwordState = 'Known' $mustChange = $false } catch [ArgumentOutOfRangeException] { # Optional corrupt/unreadable metadata must not invent a timestamp or status. $passwordTime = $null } } } [pscustomobject]@{ PSTypeName = 'NSP.ActiveDirectory.User' DistinguishedName = [string]$Entry['distinguishedname'] SamAccountName = [string]$Entry['samaccountname'] UserPrincipalName = [string]$Entry['userprincipalname'] DisplayName = [string]$Entry['displayname'] GivenName = [string]$Entry['givenname'] Surname = [string]$Entry['sn'] Mail = [string]$Entry['mail'] ProxyAddresses = [string[]]@($Entry['proxyaddresses'] | Where-Object { $null -ne $_ }) ObjectGUID = [guid]::new([byte[]]$Entry['objectguid']) ConsistencyGuidBytes = if ($Entry['ms-ds-consistencyguid']) { , [byte[]]$Entry['ms-ds-consistencyguid'] } else { $null } ObjectSID = [Security.Principal.SecurityIdentifier]::new([byte[]]$Entry['objectsid'], 0).Value Enabled = [bool](-not ([int]$Entry['useraccountcontrol'] -band 2)) DomainDNSRoot = [string]$DomainInfo.DNSRoot Server = [string]$DomainInfo.Server PasswordLastSetUtc = $passwordTime PasswordLastSetState = $passwordState PasswordMustChangeAtNextLogon = $mustChange MsExchRecipientTypeDetails = ConvertTo-NSPADOptionalInt64 -Value $Entry['msexchrecipienttypedetails'] } } |