I've got the following code (stripped down to mostly just logic), which gets AD user details.
Sometimes I get all properties back, but rarely. More often than not (95% of the time), the MemberOf value is missing. Very frequently Canononical Name, Last Logon Timestamp, Mail, and TargetAddress will also be missing. I always get back Name, SamAccountName, UPN, DN and SID. The properties names exist in the object, just not their corresponding values.
At first I thought it might be something with the session scope, but that doesn't seem to be the case (I've tested with toast messages outputting the object assigned to a temp variable before handing it off to the session). I don't get this issue if I test in the terminal, nor do I get it running scripts - so the service account is fine. It's only using the app.
Note: The code does a couple of 'sync-udelement' hops because there are other items being pulled back elsewhere in the full version.
$session:UserID = $null
$session:User = $null
$cache:UserProperties = @(
"Name",
"SamAccountName",
"UserPrincipalName",
"Enabled",
"CanonicalName",
"DistinguishedName",
"LastLogonTimestamp",
"SID",
"Mail",
"TargetAddress",
"MemberOf",
"ShowInAddressBook"
)
New-UDApp -Title "User Overview" -Content {
New-UDForm -Content {
New-UDTextbox -Id 'txtSearchQuery' -Label 'Enter User ID'
} -OnSubmit {
$session:UserID = $EventData.txtSearchQuery
Sync-UDElement -Id 'LoadUser'
}
New-UDDynamic -Id 'LoadUser' -Content {
if (-not [string]::IsNullOrWhiteSpace($session:UserID)) {
# Issue appears to be right at the 'get-aduser' call - property values are missing here
$session:User = Get-ADUser -Identity $session:UserID -Property $cache:UserProperties
Sync-UDElement -Id 'UserDetails'
}
}
New-UDDynamic -Id 'UserDetails' -LoadingComponent {
New-UDProgress -Circular
} -Content {
if ($session:User) {
# Splits the object into a name/value list
$Data = ($session:User | Select-Object -Property $cache:UserProperties).psobject.Properties.foreach({ [pscustomobject]@{Property=$_.Name; Value=$_.Value} })
$Columns = @(
New-UDTableColumn -Property Property -Title Property
New-UDTableColumn -Property Value -Title Value
)
New-UDTable -Data $Data -Columns $Columns
}
}
}Ok, to make matters more confusing, I've tested in a script and I see the same behaviour - but still the terminal window is absolutely fine. Testing on an old v3.4.6 install, properties are returned no issue.
Forgot to mention: we're running the latest 2026.3.2.
Hi Lloyd,
Thank you for the code and for the follow-up with the script and v3.4.6 results.
We ran your code on PowerShell Universal 2026.3.2 against our test domain. We left out TargetAddress, which our test domain does not have, and added one line that records what Get-ADUser returned.
In a script, we found one place where the values go missing. When a script returns the Get-ADUser object, the job's Pipeline Output tab keeps only the object's built-in properties, such as Name, SamAccountName, UserPrincipalName, DistinguishedName and SID. MemberOf, CanonicalName, LastLogonTimestamp and Mail appear only as names in the PropertyNames list, with no values. The Log tab of the same job shows their values. We saw this in the Integrated, PowerShell 7 and Windows PowerShell 5.1 environments. Inside the script itself, MemberOf, CanonicalName, LastLogonTimestamp and Mail had their values in all 36 runs.
In the app, we could not reproduce it. With your app code in the Integrated and PowerShell 7 environments (6 runs, 3 in each), those four properties had their values, both right after the Get-ADUser call and after the $session: hop.
Could you tell us:
1. In your script test, where did you see the values missing: inside the script itself, in the job's Log tab, or in its Pipeline Output tab?
2. Which environment and which Run As credential, if any, your app uses, and which ones your terminal uses (a terminal has its own: https://docs.devolutions.net/powershell-universal/automation/terminals/#configure-a-terminal)?
3. The exact line you used for the toast message, so we can repeat the same check here?
Best regards,
Michel
Michel Audi
Thanks for your time Michel.
This has taken a few twists. I managed to isolate it to the node in a single site - so fingers were pointed at AD (replication/permissions/etc) as it was working consistently outside of that site.
BUT!
If I change the environment from Integrated to PowerShell 7, it works consistently, every time. If I switch it back, it breaks.
So, I have a single node that fails to return these attributes every time I use Integrated. Otherwise I have the same: Code, Version, Service Account, and DCs.
Hi Lloyd,
Thank you, isolating it to one node and to Integrated narrows this down a lot. You can keep the app on PowerShell 7 while we look into it. The line below replaces my earlier request for your toast line.
One difference is where the app runs. In Integrated, an app runs inside the PowerShell Universal server process itself, and a Run As credential is not used. In PowerShell 7, it runs in its own process, started with the app's or the environment's Run As credential, if set. Is a Run As credential set on this app or on the PowerShell 7 environment?
We tested one way to get your result. In our lab on 2026.3.2, we removed the ActiveDirectory module's type adapters from an app's session. The table then showed Name, SamAccountName, UserPrincipalName, Enabled, DistinguishedName and SID with values, and MemberOf, CanonicalName, LastLogonTimestamp and Mail empty. We have not confirmed that this is what happens on your node, so could you check it with this line? Add it right after the Get-ADUser line in LoadUser:
Show-UDToast -Duration 60000 -Message ("PowerShell {0} | ActiveDirectory {1} | {2} | adapters {3}" -f $PSVersionTable.PSVersion, (Get-Module ActiveDirectory).Version, [Microsoft.ActiveDirectory.Management.ADEntity].Assembly.Location, @(Get-TypeData Microsoft.ActiveDirectory.Management.* | Where-Object TypeAdapter).Count)
Please run it on that node once in Integrated and once in PowerShell 7, and send us both toasts. In our lab, both environments show "adapters 38" with the same ActiveDirectory version and DLL path; with the adapters removed, "adapters 0". A toast from a working node would help too. Restarting the service starts a new server process, so please capture the toasts before any restart on that node.
Separately, we have reported the Pipeline Output issue from my last message to our developers. If your script test still shows the values missing, where do you see them missing: inside the script itself, in the job's Log tab, or only in its Pipeline Output tab?
Best regards,
Michel
Michel Audi
We haven't set any Run As parameters for PowerShell 7 vs Integrated - it's all as normal. I've also double-checked the properties for the Environments, and no RunAs properties have been set there, either.
I've been testing in a script, so I've pushed the string to Write-Host instead.
Here's integrated:
PowerShell 7.6.5
ActiveDirectory 1.0.1.0
C:\Windows\Microsoft.NET\assembly\GAC_64\Microsoft.ActiveDirectory.Management\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.ActiveDirectory.Management.dll
adapters 38
And here's PowerShell 7:
PowerShell 7.6.5
ActiveDirectory 1.0.1.0
C:\Windows\Microsoft.NET\assembly\GAC_64\Microsoft.ActiveDirectory.Management\v4.0_10.0.0.0__31bf3856ad364e35\Microsoft.ActiveDirectory.Management.dll
adapters 38
To my eyes, they appear identical.
And just for full transparency, here's the updated script:
Param(
[string]$Identity
)
try {
Get-ADUser -Identity $Identity -Server "user.domain" -Property MemberOf,ShowInAddressbook
Write-Host ("PowerShell {0} | ActiveDirectory {1} | {2} | adapters {3}" -f $PSVersionTable.PSVersion, (Get-Module ActiveDirectory).Version, [Microsoft.ActiveDirectory.Management.ADEntity].Assembly.Location, @(Get-TypeData Microsoft.ActiveDirectory.Management.* | Where-Object TypeAdapter).Count)
}
catch {
Write-Error "Unable to fetch user details`n$_"
}And just to spam the posts:
I'm not ruling out authentication/permissions just yet in our environment. At this stage it looks like the way the Integrated environment is treated by Kerberos in some fashion is different to the PS7 method. I'll speak to our AD team and attempt to find anything, but if you could give me some kind of indication what to look for on their end, that would be great.
Hi Lloyd,
Thank you for running the line. Your two results match: in that script, Integrated and PowerShell 7 had the same ActiveDirectory module and number of type adapters. With no Run As set, both environments should use the service's own account (https://docs.devolutions.net/powershell-universal/config/running-as-a-service-account/).
We found one way to get your pattern. $cache: variables are shared by everything that runs in the Integrated environment on that server (https://docs.devolutions.net/powershell-universal/platform/cache/#cache-scope), while an app in PowerShell 7 has its own. In our lab on 2026.3.2, when a second Integrated app set $cache:UserProperties to a shorter list, your app's Get-ADUser asked for that list, so MemberOf, CanonicalName, LastLogonTimestamp and Mail did not come back. The same app in PowerShell 7, and an Integrated copy using a name of its own, were not affected.
We have not confirmed this is what happens on your node. To check, replace the Get-ADUser line in LoadUser with these lines, with your domain in place of user.domain:
$asked = @($cache:UserProperties)
$session:User = Get-ADUser -Identity $session:UserID -Property $asked
$u = $session:User
Show-UDToast -Duration 60000 -Message ("asked {0} | user {1} | MemberOf read {2} | MemberOf in object {3}" -f ($asked -join ','), [Environment]::UserName, $u.MemberOf.Count, $u['MemberOf'].Count)
Show-UDToast -Duration 60000 -Message ("MemberOf over LDAP {0}" -f ([adsi]"LDAP://user.domain/$($u.DistinguishedName)").memberOf.Count)
Please open the app on that node, before any service restart, in Integrated and then in PowerShell 7, and send us both pairs of toasts. In our lab, for a user in three groups, they show your list, "MemberOf read 3 | MemberOf in object 3" and "MemberOf over LDAP 3".
1. If "asked" is not your list, something else in Integrated sets $cache:UserProperties. Search your other apps, APIs and scripts for it, or give this app's variable a name only it uses.
2. If "asked" is your list, "in object" and "over LDAP" show the groups and only "read" is 0, the values were lost inside the session, which is ours to investigate.
3. If "asked" is your list, "in object" is 0 and "over LDAP" is not, take that result to your AD team: Get-ADUser reads through Active Directory Web Services, the LDAP line does not, and the two can reach different domain controllers.
Any other result, please send it as it is. If your script still loses the values, please also tell us where: inside the script, the Log tab, or only the Pipeline Output tab.
Best regards,
Michel
Michel Audi
Hi Michel - I don't believe the $cache: scope is the issue. I'm using session variables for everything other than the properties list. The $cache:UserProperties is also only used within the app, but I'm still experiencing the issue in the test script where I specify the 'MemberOf' property manually.
Plus, combine this with the fact that it only seems to be an issue on one node.
I've got a chat booked in tomorrow with our AD team to attempt to figure this out, so I'll come back then if there's anything that needs querying.
Hi Lloyd,
Thank you. Agreed: with $cache:UserProperties used only in that app, the $cache: lead does not fit, and we are setting it aside.
Before your call tomorrow: your updated script returns the Get-ADUser object. In our lab on 2026.3.2, the Pipeline Output tab of such a job shows MemberOf with no value in Integrated, PowerShell 7 and Windows PowerShell 5.1, while the Log tab of the same job shows the groups. This is the issue we reported to our developers. So on 2026.3.2, compare the Log tab of the job, not Pipeline Output (https://docs.devolutions.net/powershell-universal/automation/jobs/#jobs).
Here is your script with two lines added, which we ran on 2026.3.2 in Integrated and PowerShell 7. Put your domain in place of user.domain, twice:
Param(
[string]$Identity
)
try {
$u = Get-ADUser -Identity $Identity -Server "user.domain" -Property MemberOf,ShowInAddressbook
$u
Write-Host ("user {0} | MemberOf read {1} | MemberOf in object {2}" -f [Environment]::UserName, $u.MemberOf.Count, $u['MemberOf'].Count)
Write-Host ("MemberOf over LDAP {0}" -f ([adsi]"LDAP://user.domain/$($u.DistinguishedName)").memberOf.Count)
}
catch {
Write-Error "Unable to fetch user details`n$_"
}
Please run it on that node in both environments, before any service restart or AD change, and send the two lines from each Log tab. "user" is the Windows user name, "read" and "in object" the groups Get-ADUser returned, and "over LDAP" the groups a direct LDAP read from that node sees; in our lab, all three counts were 3 for a user in three groups.
The app symptom is still open, and the script alone cannot settle it. At the same time, please send both pairs of app toasts from my last message, Integrated and PowerShell 7: they show whether MemberOf is already missing in what Get-ADUser returns inside the app, or is lost after it.
Best regards,
Michel
Michel Audi
I've been dealing with this issue since back when v4 was the thing (there are probably still posts about it on the old forum).
I ended up creating a custom function to fetch AD objects non-default properties into a PSCustomObject.
So for almost every array that I want to have cached, I have two instances:
$cache:ad_users — proper AD objects that I can use in scenarios where I need to deal with AD objects (only default properties)
$cache:ad_users_object — a standard PSCustomObject that has all the properties I want to read from
It's been working flawlessly since then (and fetching is extremely fast thanks to a custom function made by Claude).
I've been dealing with this issue since back when v4 was the thing (there are probably still posts about it on the old forum).
I ended up creating a custom function to fetch AD objects non-default properties into a PSCustomObject.
So for almost every array that I want to have cached, I have two instances:
$cache:ad_users — proper AD objects that I can use in scenarios where I need to deal with AD objects (only default properties)
$cache:ad_users_object — a standard PSCustomObject that has all the properties I want to read from
It's been working flawlessly since then (and fetching is extremely fast thanks to a custom function made by Claude).
Thanks for sharing this - Maybe this will be my workaround. Would you mind sharing the function in question?
I've been dealing with this issue since back when v4 was the thing (there are probably still posts about it on the old forum).
I ended up creating a custom function to fetch AD objects non-default properties into a PSCustomObject.
So for almost every array that I want to have cached, I have two instances:
$cache:ad_users — proper AD objects that I can use in scenarios where I need to deal with AD objects (only default properties)
$cache:ad_users_object — a standard PSCustomObject that has all the properties I want to read from
It's been working flawlessly since then (and fetching is extremely fast thanks to a custom function made by Claude).
Thanks for sharing this - Maybe this will be my workaround. Would you mind sharing the function in question?
got the code at work, i will probably be able to get it tomorrow
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 dc002I hope it will help
Hi Lloyd,
Thank you for your patience while we looked into this. Did your call with your AD team turn up anything about that node?
We have an update on our side. Our developers are now working on the Pipeline Output issue from my earlier messages, where a job that returns a Get-ADUser object shows MemberOf and the other extended values empty in its Pipeline Output tab (https://docs.devolutions.net/powershell-universal/automation/jobs/#jobs). That issue is in how a job stores its output, and we saw it in every environment, so it does not explain why your app loses the values only in Integrated on that one node. That part is still open.
Two readings from that node, before any service restart, will still show us where the values are lost. The first is the two Log tab lines from the script in my last message, from each environment. The second is the two pairs of app toasts from my message before it, which means switching the app back to Integrated briefly, then PowerShell 7. If you move the app to another way of reading AD, please take these readings first. Until then, the app can stay on PowerShell 7.
Best regards,
Michel
Michel Audi
sorry for long delay (i forgot). here are my function with function calls:
....
I hope it will help
No that's great, thank you!
Hi Lloyd,
Thank you for your patience while we looked into this. Did your call with your AD team turn up anything about that node?
We have an update on our side. Our developers are now working on the Pipeline Output issue from my earlier messages, where a job that returns a Get-ADUser object shows MemberOf and the other extended values empty in its Pipeline Output tab (https://docs.devolutions.net/powershell-universal/automation/jobs/#jobs). That issue is in how a job stores its output, and we saw it in every environment, so it does not explain why your app loses the values only in Integrated on that one node. That part is still open.
Two readings from that node, before any service restart, will still show us where the values are lost. The first is the two Log tab lines from the script in my last message, from each environment. The second is the two pairs of app toasts from my message before it, which means switching the app back to Integrated briefly, then PowerShell 7. If you move the app to another way of reading AD, please take these readings first. Until then, the app can stay on PowerShell 7.
Best regards,
Michel
Sorry, I've been away from work for over a week, and the team didn't have any free time - I need to arrange with them again. I'll hopefully have time later today to check with the code you mentioned and post an update.