Failure to enumerate AD Group's members using C#

390 views Asked by At

Could you please help me? I am trying to enumerate an AD group's members from a remote machine by using the following code:

using (var entry = new DirectoryEntry(..
{
    foreach (object member in (IEnumerable)entry.Invoke("Members", null))
    {

This code works well except for one environment.

In this specific env, when I try to enumerate the members of an AD group, it throws the following exception:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.

System.ArgumentException: Value does not fall within the expected range. --- End of inner exception stack trace ---

at System.DirectoryServices.DirectoryEntry.Invoke(String methodName, Object[] args)

I run the same commands via power shell (script copied below) and got the same error:

Exception calling "Invoke" with "2" argument(s): "Value does not fall within the expected range.

At C:\Temp\PSTest_AD_Group_Members2.ps1:23 char:5

  • $members = $DirectoryEntry.Invoke("Members", $null)
  • CategoryInfo : NotSpecified: (:) [], MethodInvocationException
  • FullyQualifiedErrorId : DotNetMethodTargetInvocation

Please note that

  1. I tried reproduce it against several DCs in different domains unsuccessfully. It only happens in one specific environment
  2. I tried searching for this exception online but did not find anything relevant
  3. I am using a domain admin to run this code
  4. The code is running on Windows Server 2016 with latest updates
  5. I have a PowerShell script that produce the same behavior (copied below)
  6. I see no relevant entry in the event viewer indicating that something went wrong on both the source and target machines

Can someone help me understand why this code fail to get the AD group's members only in that one specific environment?

Is there a way on the DC side to understand what went wrong? perhaps a DC log for incoming/attempted commands?

Thanks for your help

=========================== powershell script ==============================

Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$cred = Get-Credential
$domain = "<domain name>"
$groupname = "<group name>"
$results = "<result csv file path>"

cls

$ctx = New-Object 'DirectoryServices.AccountManagement.PrincipalContext' ([DirectoryServices.AccountManagement.ContextType]::Domain, $domain, $cred.UserName, $cred.GetNetworkCredential().Password)
$timing = Measure-Command {$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($ctx, $groupname)}
$props = [ordered]@{
    'Type' = $group.StructuralObjectClass
    'Name' = $group.SamAccountName
    'SID' = $group.sid
    'RetrivedIn(s)' = $timing.TotalSeconds
    'Retrievedcount' = $group.count
    'UserPrincipalName' = $group.UserPrincipalName
}
New-Object -TypeName PSObject -Property $props | Export-Csv -Path $results -NoTypeInformation

$timing = Measure-Command {
    $DirectoryEntry = New-Object System.DirectoryServices.DirectoryEntry ("LDAPS://$($group.DistinguishedName)", $cred.UserName, $cred.GetNetworkCredential().Password)
    
$members = $DirectoryEntry.Invoke("Members")
}

$props = [ordered]@{
    'Type' = $members.gettype().name
    'Name' = "GroupDirectoryEntry"
    'SID' = "n/a"
    'RetrivedIn(s)' = $timing.TotalSeconds
    'Retrievedcount' = ($members | Measure-Object).count
    'UserPrincipalName' = "n/a"
}
New-Object -TypeName PSObject -Property $props | Export-Csv -Path $results -Append -NoTypeInformation

$members |
ForEach-Object {
    $bytesSid = $_.gettype().InvokeMember("objectSid","GetProperty",$null,$_,$null)
    $sid = New-Object System.Security.Principal.SecurityIdentifier ($bytesSid, 0)
    $timing = Measure-Command {$acct = [DirectoryServices.AccountManagement.Principal]::FindByIdentity($ctx, 4, $sid.value)}
    $props = [ordered]@{
        'Type' = $acct.StructuralObjectClass
        'Name' = $acct.SamAccountName
        'SID' = $acct.Sid
        'RetrivedIn(s)' = $timing.TotalSeconds
        'Retrievedcount' = $acct.count
        'UserPrincipalName' = $acct.UserPrincipalName
    }
    New-Object -TypeName PSObject -Property $props | Export-Csv -Path $results -Append -NoTypeInformation
}
============================= end of powershell script ================================
2

There are 2 answers

0
adied On BEST ANSWER

I find out what was the problem. the troubled environment has a customization in its AD environment due to the use of a third-party product that prepares its active directory for support. As part of the integration, it run an schema update that adds a "members" attribute to their AD schema. this is causing a conflict when we invoke members on the directory entry.

1
Athanasios Kataras On

Why not use GroupPrincipal instead of reflection?

It would be as simple as this:

PrincipalContext ctx = new PrincipalContext(ContextType.Domain,                                                                      
                                            "fabrikam.com",   
                                            "DC=fabrikam,DC=com",   
                                            "administrator",   
                                            "SecretPwd123");  

GroupPrincipal grp = GroupPrincipal.FindByIdentity(ctx,   
                                                   IdentityType.Name,   
                                                   "Domain Admins");  

if (grp != null)  
{  
    foreach (Principal p in grp.GetMembers(true))  
    {  
         Console.WriteLine(p.Name);  
    }  
    grp.Dispose();  
}  

ctx.Dispose();

The error is pretty explanatory:

target of an invocation. That is the Members function

System.ArgumentException: Value does not fall within the expected range This most probably refers to the null value.