PowerShell

Get-ADObject : The server has returned the following error: invalid enumeration context.

In the last weeks, I'm working on a PowerShell module that the main goal is to work on gathering and fixing GPOs. I've been testing my module a lot of times on my test environment, and it worked fine till the moment I run it on production, and it started to fail pretty quickly. The difference between my environment and production is 25 GPOs vs. 5000 GPOs. The error I was getting:

Get-ADObject : The server has returned the following error: invalid enumeration context.
At C:\Users\Administrator\Documents\WindowsPowerShell\Modules\GPOZaurr\0.0.26\GPOZaurr.psm1:1987 char:13
+ Get-ADObject @Splat -Properties DisplayName, Name, Create …
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Get-ADObject], ADException
+ FullyQualifiedErrorId : The server has returned the following error: invalid enumeration context.,Microsoft.ActiveDirectory.Management.Commands.GetADObject

The error doesn't tell much but I've seen the error in multiple other situations mainly when using Get-ADUser, Get-ADComputer, or Get-ADGroup, but I'm confident that it touches all AD cmdlets. The errors would be very similar:

Get-ADUser: The server has returned the following error: invalid enumeration context.

Get-ADComputer: The server has returned the following error: invalid enumeration context.

Get-ADGroup: The server has returned the following error: invalid enumeration context.

The server has returned the following error: invalid enumeration context.

The issue comes from using ForEach-Object on AD cmdlets. While ForEach-Object is excellent and allows you to execute action over the pipeline as objects are read, it causes Get-ADObject to timeout if actions taken during pipeline are taking a too long time. A single query has a time limit. If you start a query on Get-ADObject that executes multiple operations on every object from the pipeline, the time adds up and the query timeouts.

Get-ADObject @Splat -Properties DisplayName, Name, Created, Modified, gPCFileSysPath, gPCFunctionalityVersion, gPCWQLFilter, gPCMachineExtensionNames, Description, CanonicalName, DistinguishedName | ForEach-Object -Process {
    $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToDomainCN
    $Output = [ordered]@{ }
    $Output['DisplayName'] = $_.DisplayName
    $Output['DomainName'] = $DomainCN
    $Output['Description'] = $_.Description
    $Output['GUID'] = $_.Name -replace '{' -replace '}'
    $Output['Path'] = $_.gPCFileSysPath
    $Output['FunctionalityVersion'] = $_.gPCFunctionalityVersion
    $Output['Created'] = $_.Created
    $Output['Modified'] = $_.Modified
    $Output['GPOCanonicalName'] = $_.CanonicalName
    $Output['GPODomainDistinguishedName'] = ConvertFrom-DistinguishedName -DistinguishedName $_.DistinguishedName -ToDC
    $Output['GPODistinguishedName'] = $_.DistinguishedName
    [PSCustomObject] $Output
}

So while small query like above works great, because it's just doing some small conversion of an object – using it nested to another function with another ForEach-Object and checking for more values makes AD Cmdlets timeout. My original query looks like below

Get-GPOZaurrAD @getGPOZaurrADSplat | ForEach-Object -Process {
    Write-Verbose "Get-GPOZaurrOwner - Processing GPO: $($_.DisplayName) from domain: $($_.DomainName)"
    $ACL = Get-ADACLOwner -ADObject $_.GPODistinguishedName -Resolve -ADAdministrativeGroups $ADAdministrativeGroups
    $Object = [ordered] @{
        DisplayName       = $_.DisplayName
        DomainName        = $_.DomainName
        GUID              = $_.GUID
        DistinguishedName = $_.GPODistinguishedName
        Owner             = $ACL.OwnerName
        OwnerSid          = $ACL.OwnerSid
        OwnerType         = $ACL.OwnerType
    }
    if ($IncludeSysvol) {
        $FileOwner = Get-FileOwner -JustPath -Path $_.Path -Resolve
        $Object['SysvolOwner'] = $FileOwner.OwnerName
        $Object['SysvolSid'] = $FileOwner.OwnerSid
        $Object['SysvolType'] = $FileOwner.OwnerType
        $Object['SysvolPath'] = $_.Path
    }
    [PSCUstomObject] $Object
}

A Get-GPOZaurrAD function which is a wrapper around  Get-ADObject. It queries AD, and for each object returned, it checks the GPO Owner and SysVol Owner. This process taking about 1 second per each check, makes the connection to Active Directory timeout. Of course, during my tests on 25 GPOs, the timeout won't happen, but as you imagine querying 5000 AD objects, each taking 1 second it means a single Get-ADObject query would require around 83 minutes to finish.

Dealing with the problem

There are 2 ways to deal with this problem. The easiest ones are to simply save query first to variable and then either use ForEach-Object or foreach to gather results.

$Objects = Get-GPOZaurrAD @getGPOZaurrADSplat
$Objects | ForEach-Object -Process {
    Write-Verbose "Get-GPOZaurrOwner - Processing GPO: $($_.DisplayName) from domain: $($_.DomainName)"
    $ACL = Get-ADACLOwner -ADObject $_.GPODistinguishedName -Resolve -ADAdministrativeGroups $ADAdministrativeGroups
    $Object = [ordered] @{
        DisplayName       = $_.DisplayName
        DomainName        = $_.DomainName
        GUID              = $_.GUID
        DistinguishedName = $_.GPODistinguishedName
        Owner             = $ACL.OwnerName
        OwnerSid          = $ACL.OwnerSid
        OwnerType         = $ACL.OwnerType
    }
    if ($IncludeSysvol) {
        $FileOwner = Get-FileOwner -JustPath -Path $_.Path -Resolve
        $Object['SysvolOwner'] = $FileOwner.OwnerName
        $Object['SysvolSid'] = $FileOwner.OwnerSid
        $Object['SysvolType'] = $FileOwner.OwnerType
        $Object['SysvolPath'] = $_.Path
    }
    [PSCUstomObject] $Object
}

From my own testing if I am already saving data to variable I prefer the later

$Objects = Get-GPOZaurrAD @getGPOZaurrADSplat
foreach ($_ in $Objects) {
    Write-Verbose "Get-GPOZaurrOwner - Processing GPO: $($_.DisplayName) from domain: $($_.DomainName)"
    $ACL = Get-ADACLOwner -ADObject $_.GPODistinguishedName -Resolve -ADAdministrativeGroups $ADAdministrativeGroups
    $Object = [ordered] @{
        DisplayName       = $_.DisplayName
        DomainName        = $_.DomainName
        GUID              = $_.GUID
        DistinguishedName = $_.GPODistinguishedName
        Owner             = $ACL.OwnerName
        OwnerSid          = $ACL.OwnerSid
        OwnerType         = $ACL.OwnerType
    }
    if ($IncludeSysvol) {
        $FileOwner = Get-FileOwner -JustPath -Path $_.Path -Resolve
        $Object['SysvolOwner'] = $FileOwner.OwnerName
        $Object['SysvolSid'] = $FileOwner.OwnerSid
        $Object['SysvolType'] = $FileOwner.OwnerType
        $Object['SysvolPath'] = $_.Path
    }
    [PSCUstomObject] $Object
}

Alternatively, you can change Active Directory Web Services timeout to a higher value. The second option has two significant issues. One, you need to be in charge of your Active Directory environment, which may be hard enough in environments you would hit this issue. Also, your scripts would only work on AD that has higher values set, hitting errors as soon as you try to use it on similar scale environments that don't have adjusted timeout settings. Second, Microsoft discourages this change. You can read about it in the following article What's New in AD DS: Active Directory Web Services.

Changing the default value of this parameter is strongly discouraged. Most of the search results are returned within 30 minutes.

This means that a single call for Get-ADObject, Get-ADUser, Get-ADComputer (and so on) has to finish in 30 minutes or less. Using ForEach-Object, due to its nature, can impact the time it takes to deliver all records; therefore, you should use it with caution. Knowing this, I opted for the first approach – making sure I can use my scripts anywhere I want to.

This post was last modified on May 15, 2020 17:38

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

2 weeks ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

5 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

7 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

8 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

9 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

9 months ago