Why Build Our Own Enumeration Tools?
In Active Directory (AD) environments, enumeration is one of the first things a red teamer does after gaining initial access. There are many built-in tools for this job, such as PowerShell cmdlets like Get-ADUser. However, these cmdlets are part of Remote Server Administration Tools (RSAT) and are usually installed by default only on Domain Controllers, not on regular client machines.
From an attacker’s perspective, this is a problem. On a compromised workstation with basic domain user privileges, RSAT tools are often unavailable.
That is why, in this section, we build our own lightweight enumeration scripts using PowerShell and native .NET classes. This helps us:
- Enumerate Active Directory with no admin rights
- Avoid relying on noisy or unavailable pre-built tools
- Understand how popular red team tools work under the hood
How Active Directory Enumeration Really Works
Active Directory enumeration is fundamentally based on LDAP (Lightweight Directory Access Protocol).
Whenever a domain-joined machine searches for users, groups, or computers, it communicates with Active Directory using LDAP queries. LDAP is simply a protocol designed to interact with directory services.
LDAP is not exclusive to Active Directory. Many other directory services also use LDAP.
ADSI: The Bridge Between PowerShell and LDAP
To communicate with Active Directory over LDAP in Windows, we use ADSI (Active Directory Service Interfaces).
- ADSI acts as an LDAP provider
- It is built on top of COM interfaces
- It allows PowerShell and .NET to query AD objects directly
To use ADSI, we need an LDAP path, also called an ADsPath, which has the following format:
LDAP://HostName[:PortNumber][/DistinguishedName]
This path has three main components:
- HostName: The HostName in our LDAP path can be a computer name, IP address or a domain name. In this example, our target domain is
example.com. - PortNumber (optional)
- Distinguished Name (DN)
Choosing the Right Domain Controller (PDC)
In a domain, there can be multiple Domain Controllers (DCs). From a red team perspective, accuracy matters. We want to query the DC that holds the most up-to-date information.
That DC is the Primary Domain Controller (PDC):
- Each domain has exactly one PDC
- It holds the PdcRoleOwner role
- Querying it reduces replication delays and inconsistencies
Understanding Distinguished Names (DN)
Every object in Active Directory has a Distinguished Name (DN). This name uniquely identifies the object and also describes its position in the AD hierarchy.
Example DN:
CN=john,CN=Users,DC=example,DC=com
Let’s break this down:
- CN (Common Name): Identifies an object within its container.
- CN=Users: The container where the user object is stored (the parent container).
- CN=john: The user object itself, located at the lowest level of the hierarchy.
- DC (Domain Component): Represents the domain and forms the top of the LDAP tree. In this case:
example.com
LDAP requires this strict naming structure to function correctly.
Finding the PDC with .NET Classes
The .NET Framework provides classes specifically designed to interact with Active Directory. One of them is located in the namespace: System.DirectoryServices.ActiveDirectory
We use the Domain class to identify the PDC for the current user’s domain.
PS C:\Users\john>[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
Forest : example.com
DomainControllers : {DC1.example.com}
Children : {}
DomainMode : Unknown
DomainModeLevel : 7
Parent :
PdcRoleOwner : DC1.example.com
This confirms that DC1.example.com is the PDC we should target.
Bypassing PowerShell Execution Policy
Before running custom scripts, we need to bypass PowerShell’s execution policy:
PS C:\Users\john> powershell -ep bypass
This is a common red team technique and does not require administrator privileges.
Dynamically Building the LDAP Path
Next, we create a script that dynamically constructs the full LDAP path required for enumeration.
PowerShell Script
# Get the name of the Primary Domain Controller (PDC) for the current domain
$PDC = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().PdcRoleOwner.Name
# Retrieve the Distinguished Name (DN) of the domain by querying the root of the AD hierarchy
$DN = ([adsi]'').distinguishedName
# Build the full LDAP path using the PDC and the domain DN
$LDAP = "LDAP://$PDC/$DN"
# Print the final LDAP path
$LDAP
Execution result
PS C:\Users\john> .\enumeration.ps1
LDAP://DC1.example.com/DC=example,DC=com
At this point, we have everything needed to query Active Directory.
Enumerating All Domain Users
Now we move from discovery to actual enumeration.
To query Active Directory objects, we rely on two important .NET classes from the System.DirectoryServices namespace:
- DirectoryEntry: Represents a single node or object in Active Directory and serves as the connection point to LDAP.
- DirectorySearcher: Executes LDAP queries against Active Directory and returns matching objects based on search filters.
User Enumeration Script
The following script enumerates all user objects in the domain and prints all their properties.
# Get the current domain object
$domainObj = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
# Extract the PDC hostname
$PDC = $domainObj.PdcRoleOwner.Name
# Get the domain Distinguished Name
$DN = ([adsi]'').distinguishedName
# Build the LDAP path
$LDAP = "LDAP://$PDC/$DN"
# Create a DirectoryEntry object as the LDAP connection point
$direntry = New-Object System.DirectoryServices.DirectoryEntry($LDAP)
# Create a DirectorySearcher object to run LDAP queries
$dirsearcher = New-Object System.DirectoryServices.DirectorySearcher($direntry)
# Filter results to only user objects using samAccountType
$dirsearcher.filter = "samAccountType=805306368"
# Execute the search and retrieve all matching objects
$result = $dirsearcher.FindAll()
# Loop through each user object
Foreach ($obj in $result)
{
# Loop through all properties of the current user object
Foreach ($prop in $obj.Properties)
{
$prop
}
# Visual separator between user objects
Write-Host "-------------------------------"
}
Understanding the Filter Logic
The enumeration is filtered using: samAccountType = 805306368This value corresponds to objects that start with 0x30000000, which represents user accounts in Active Directory.
- The outer loop iterates over each user object
- The inner loop extracts and prints every property for that user
Conclusion
Manual enumeration with PowerShell and native .NET classes demonstrates how reliable and stealthy Active Directory discovery can be achieved without relying on RSAT or heavyweight tooling. By understanding LDAP fundamentals, ADSI, Distinguished Names, and how to accurately target the PDC, red teamers gain precise and real-world visibility into domain environments using minimal privileges. From a red team perspective, mastering these techniques is essential for operating effectively on constrained hosts while maintaining control and awareness of what is queried and how.
Key Takeaways
- Building custom enumeration scripts removes dependency on RSAT and noisy built-in tools
- LDAP is the core mechanism behind all Active Directory enumeration
- ADSI and .NET classes provide direct, flexible, and reliable access to AD objects
- Targeting the PDC improves accuracy by avoiding replication delays
- Proper use of Distinguished Names is critical for successful LDAP queries
- Manual enumeration helps you understand and replicate how mature red team tools work
Mastering manual AD enumeration is a foundational skill that pays off in real-world engagements, especially when stealth and OPSEC matter. Practice these techniques in controlled lab environments, always operate in authorized and ethical contexts, and experiment with chaining this approach with other enumeration and post-exploitation techniques covered earlier to build a complete and effective red team workflow.