Programmatic AD User/Group Enumeration Without DC Access: LDAP Queries & PowerShell Alternatives


3 views

Many system administrators face scenarios where they need to inspect Active Directory structures without having Remote Desktop access to Domain Controllers. This limitation often occurs in:

  • Managed service provider environments
  • Large enterprises with segregated access
  • Security-restricted networks

The Lightweight Directory Access Protocol (LDAP) provides the most robust method for querying AD remotely. Here's a basic PowerShell example that lists all users:

# PowerShell LDAP query for all enabled users
$searcher = [ADSISearcher]"(&(objectCategory=user)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
$searcher.FindAll() | ForEach-Object {
    [PSCustomObject]@{
        Name = $_.Properties.name
        SAMAccountName = $_.Properties.samaccountname
        DistinguishedName = $_.Properties.distinguishedname
    }
}

For visualizing group hierarchies, recursive queries work best. This script builds a tree structure:

function Get-ADGroupTree {
    param(
        [string]$GroupName,
        [int]$Depth = 0
    )
    
    $prefix = " " * $Depth * 4
    Write-Output "${prefix}├─ $GroupName"
    
    $members = Get-ADGroupMember -Identity $GroupName -Recursive | 
               Where-Object {$_.objectClass -eq 'group'}
    
    foreach ($member in $members) {
        Get-ADGroupTree -GroupName $member.Name -Depth ($Depth + 1)
    }
}

# Usage example:
Get-ADGroupTree -GroupName "Domain Admins"

When working from non-Windows systems or without admin rights:

  • AD Explorer: Sysinternals tool that connects remotely
  • ldapsearch: Native Linux/MacOS command-line tool
  • Softerra LDAP Browser: Graphical interface for complex queries

Always remember:

  1. Use encrypted LDAPS (port 636) instead of plain LDAP
  2. Implement proper error handling to avoid information leakage
  3. Request only necessary attributes to reduce network traffic

For cross-platform solutions, Python's ldap3 module works well:

import ldap3
from ldap3 import SUBTREE

server = ldap3.Server('ldap://your.domain.controller')
conn = ldap3.Connection(server, user='DOMAIN\\user', password='password', auto_bind=True)

conn.search(search_base='DC=domain,DC=com',
            search_filter='(objectClass=group)',
            search_scope=SUBTREE,
            attributes=['memberOf', 'member'])

for entry in conn.entries:
    print(f"Group: {entry.cn}")
    print(f"Members: {entry.member}")

When you lack direct RDP access to Domain Controllers but need to audit Active Directory structures, you'll need to leverage LDAP queries from a domain-joined machine. This approach works because AD fundamentally operates as an LDAP directory service.

For developers, these are the most practical options:

  • PowerShell with ActiveDirectory module (requires RSAT or Windows 10/11)
  • AD Explorer (Sysinternals) - GUI alternative to ADUC
  • LDAP command-line utilities (ldp.exe, ldifde)
  • .NET DirectoryServices for custom applications

Here's a complete script to dump all groups with members in tree format:


# Import required module (install RSAT if missing)
Import-Module ActiveDirectory

# Recursive function to build group hierarchy
function Get-ADGroupTree {
    param($GroupDN, $IndentLevel=0)
    
    $group = Get-ADGroup -Identity $GroupDN -Properties Members
    Write-Output ("  " * $IndentLevel + "+ $($group.Name)")
    
    foreach ($member in $group.Members) {
        try {
            $memberObj = Get-ADObject -Identity $member -Properties ObjectClass
            if ($memberObj.ObjectClass -eq 'group') {
                Get-ADGroupTree -GroupDN $member -IndentLevel ($IndentLevel + 1)
            } else {
                $user = Get-ADUser -Identity $member -Properties DisplayName
                Write-Output ("  " * ($IndentLevel + 1) + "- $($user.DisplayName)")
            }
        } catch {
            Write-Output ("  " * ($IndentLevel + 1) + "! Error resolving: $member")
        }
    }
}

# Get all groups and process
$allGroups = Get-ADGroup -Filter * -Properties Members
foreach ($group in $allGroups) {
    Get-ADGroupTree -GroupDN $group.DistinguishedName
}

For applications requiring deeper integration:


using System.DirectoryServices;

public class ADExplorer {
    public static void EnumerateGroups(string domainPath) {
        using (DirectoryEntry entry = new DirectoryEntry(domainPath)) {
            using (DirectorySearcher searcher = new DirectorySearcher(entry)) {
                searcher.Filter = "(objectCategory=group)";
                searcher.PropertiesToLoad.Add("member");
                searcher.PropertiesToLoad.Add("name");

                foreach (SearchResult result in searcher.FindAll()) {
                    Console.WriteLine($"Group: {result.Properties["name"][0]}");
                    
                    if (result.Properties.Contains("member")) {
                        foreach (string member in result.Properties["member"]) {
                            Console.WriteLine($"  |- {GetObjectName(member, domainPath)}");
                        }
                    }
                }
            }
        }
    }

    private static string GetObjectName(string dn, string domainPath) {
        using (DirectoryEntry entry = new DirectoryEntry($"LDAP://{dn}", domainPath)) {
            return entry.Properties["name"].Value?.ToString() ?? dn;
        }
    }
}

When implementing these solutions:

  • Always run with least-privileged accounts
  • Cache results when possible to reduce AD queries
  • Handle large directories with paging (SizeLimit/LDAP_SIZELIMIT_EXCEEDED)
  • Consider implementing rate limiting for automated tools

Frequent challenges and solutions:


# Error: "Unable to find a default server"
# Solution: Specify domain explicitly
Get-ADGroup -Server "dc01.yourdomain.com" -Filter *

# Error: Insufficient permissions
# Solution 1: Request proper access
# Solution 2: Use credential delegation
$cred = Get-Credential
Get-ADGroup -Credential $cred -Filter *