I'm trying to retrieve the AD property LastLogon
from each of our domain controllers, for each user. Due to the amount of time this will take, I'm trying to use the Parallel
feature from PowerShell 7
$users = Get-ADUser -Filter "[filter]"
$DCs = Get-ADDomainController -Filter * | Where-Object { $_.Site -in $using:sites }
$users | ForEach-Object -ThrottleLimit 2 -Parallel {
$serv = $using:DCs
foreach ($DC in $serv) {
$lastLogonAD = Get-ADUser -Identity $_ -Properties LastLogon -Server $DC -ErrorAction Stop | Select-Object -ExpandProperty LastLogon
$lastLogonConverted = [datetime]::FromFileTimeUTC($lastLogonAD)
}
}
Get-ADUser:
Line |
7 | … $lastLogonAD = Get-ADUser -Identity $_ -Properties LastLogon -Server …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| The server has returned the following error: invalid enumeration context.
What I've found is that if I remove the -Server
parameter, it works, or if I don't store the property into a variable, it works.
foreach ($DC in $DCs) {
$lastLogonAD = Get-ADUser -Identity $_ -Properties LastLogon -ErrorAction Stop | Select-Object -ExpandProperty LastLogon
$lastLogonConverted = [datetime]::FromFileTimeUTC($lastLogonAD)
}
foreach ($DC in $DCs) {
Get-ADUser -Identity $_ -Properties LastLogon -Server $DC -ErrorAction Stop | Select-Object -ExpandProperty LastLogon
$lastLogonConverted = [datetime]::FromFileTimeUTC($lastLogonAD)
}
The error "The server has returned the following error: invalid enumeration context." is mainly because your code is very inefficient, this error occurs on queries running for more than 30 minutes. See TechNet article Active Directory Troubleshooting: server has returned the following error - invalid enumeration context.
Your parallel loop should be running per Domain Controller instead of per User. The parallel invocations should be processing all users per runspace.
What below code does:
DistinguishedName
s that will be processed in parallel.DistinguishedName
s to process per parallel invocation.DistinguishedName
, sort each group by LastLogon
and pick user on each group with the latest LastLogon
.LastLogonDate
.$sites = # needs to be defined here...
# only need the users `DistinguishedName` here
$users = (Get-ADUser -Filter '[filter]').DistinguishedName
# parallel loop per DC instead of per user
Get-ADDomainController -Filter * |
Where-Object { $_.Site -in $sites } |
ForEach-Object -Parallel {
$using:users | Get-ADUser -Properties LastLogon -Server $_
} |
Group-Object DistinguishedName |
ForEach-Object {
$_.Group | Sort-Object LastLogon -Descending -Top 1 |
Select-Object *, @{ N='LastLogonDate'; E={ [datetime]::FromFileTimeUtc($_.LastLogon) }}
}
If you really need to have a reference of the Source DC per user then the way around it is to recreate the objects with a new SourceDC
property inside the parallel loop, for instance:
# No code changes before this
ForEach-Object -Parallel {
$sourceDC = $_
$using:users | Get-ADUser -Properties LastLogon -Server $_ |
Select-Object *, @{ N='SourceDC'; E={ $sourceDC.Name }}
}
# No code changes after this
If you're still facing the same issue after this change then you will need to reduce the number of users you're querying. Or perhaps you can try with a single call to the DCs using an elaborate LDAP Filter:
# create one big ldap filter
$filter = -join @(
'(|'
(Get-ADUser -Filter '[filter]').DistinguishedName |
ForEach-Object { "(distinguishedName=$_)" }
')'
)
Get-ADDomainController -Filter * |
Where-Object { $_.Site -in $sites } |
ForEach-Object -Parallel {
# then using this filter we can make a single call per DC
Get-ADUser -LDAPFilter $using:filter -Properties LastLogon -Server $_
} |
# rest of the code stays the same here...