Jonesy
Jonesy

Reputation: 629

Get-ADUser not caching results

$users = get-aduser -filter 'extensionAttribute10 -notlike "*" -and proxyaddresses -like "*"' -Properties extensionAttribute10,proxyaddresses,company

foreach($user in $users){
    $user
}

In the above sample the get-aduser commandlet returns ~20,000 objects (and takes some time to run). Once Get-ADUser is complete and the script moves onto the foreach loop, PowerShell seems to call Get-ADUser again for each object in the array rather than just returning the values from the variable (and is subsequently really, really slow).

The same behaviour applies if I reference an object in the array by using $users[100] - the first time it is slow to return the user as it makes a call to the Domain Controller, the second time I call it it returns instantly as the result is cached.

Is this expected behaviour, and is there a way of controlling it / caching all results upfront?

PSVersion: 5.1.15063.1563

Update - It seems this only occurs when you're querying objects in a forest which is remote to the user executing the command:

$myForest | Get-ADUser -filter * 
$myForest[0]        # <-- this doesn't reach back to a DC to return the user

$remoteForest | Get-ADUser -filter * -server dc1.remoteforest.com
$remoteForest[0]    # <-- this will call back to a DC to fetch the user even though it's 
                    # been successfully retrieved in the previous line

Upvotes: 3

Views: 425

Answers (3)

Michael
Michael

Reputation: 749

I've experienced the same issue. There are several workarounds depending on your specifics of your use-case. My particular use-case was the most challenging and I'm sharing what ended up being the best solution for me.

Use case: Perform Use ADModule (Get-AD* cmdlets) to obtain AD Objects and Non-Default/Extended attribute properties within a script that runs from a single server and must query against multiple trusted domains. This means the -Server parameter must be utilized.

Problem: There is a bug in the ADModule that causes a slow/performance penalty when using the -Server parameter with Get-AD* Cmdlets to obtain Non-Default/Extended attribute prop ties and traversing the results from within a standard Foreach loop. This penalty does not occur when using the | Foreach-Object pipeline.

Solution 1: Utilize '| Foreach-Object' pipeline with '| Where-Object' Clause to allow for clean breaking/exiting from within the pipeline.

Example:

$break = $false
Get-ADGroup -Filter * PipelineVariable adObject -Properties groupType -Server <Your-DC> | Where-Object {-not $break} | ForEach-Object { 
    try{ ...}
    catch{ if($ExceptionYouWantToBreakFrom){ $break = $true}
}
  • Pros: Very Fast
  • Cons: Must account for 30min default ADWS Query Timeout (MaxEnumContextExpiration) when performing long running pipeline processing with ADModule cmdlet. Potentially, you can wrap your call in some 'Retry' logic that catches the timeout exception and retries your call.

Solution 2: Utilize standard Foreach loop and force eager loading of non-default/extended properties from the pipeline.

Example:

Get-ADGroup -Filter * -OutVariable allObjects -Properties groupType -Server <Your-DC> | % { $_.null } | out-null
Foreach($adObject in $allObjects){
  ...
}
  • Pros: Fairly Fast. Don't need to worry about 30min default ADWS Query Timeout (MaxEnumContextExpiration)
  • Cons: Looks ugly. Some other minor time penalties when making additional Get-AD* cmdlet calls from within the Foreach loop.

Upvotes: 0

strongline
strongline

Reputation: 170

For anyone runs into this 2 year after original question, this is expected behavior of AD Module. Get-ADUser returns some attributes but omits the other (I have no idea what attributes are returned at first run, understandably it should be those bare minimum attributes, or attributes that don't put too much performance hit on DCs). It won't retrieve those missing attributes until you actually call for it.

Upvotes: 0

Robotitude
Robotitude

Reputation: 31

I have not observed the ActiveDirectory module calling Get-ADUser when the results are referenced in an array. You may be running into a memory issue. I recommend checking the memory allocation settings for PowerShell, and compare with the actual memory utilization when your script is executing.

#Start the Win RM service because it is required for the subsequent command.
Get-Service -Name WinRM | Start-Service

#Get the Max Memory Per Shell in MB
$memLimitPerShellMB = Get-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB

#Write value to console.
$memLimitPerShellMB.Value

#Check PowerShell memory utilization. Capture this information in a separate shell while your other, heavier, process is running.
get-process | Where-Object ProcessName -like "*PowerShell*"

#If needed, the MaxMemoryPerShellMB value can be adjusted using this command:
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB <NEW VALUE>

Upvotes: 0

Related Questions