Scroll Top
Evotec Services sp. z o.o., ul. Drozdów 6, Mikołów, 43-190, Poland

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

PowerShellBlack

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.

Posty powiązane