Site icon Windows Active Directory

How to find contacts & manager relationships with PowerShell

When admins say “get the manager,” “find contacts in AD,” or “list group members,” they often sound like simple one-liners. In Active Directory, they are related tasks, but they do not all operate on the same object type or the same attribute model. That is why quick scripts often work for one case and then fail when you reuse them for another. The manager attribute is a distinguished-name link to another directory object, directReports is the backlink generated from that relationship, contacts are their own object class rather than user accounts, and group membership queries behave differently depending on whether you want direct members, recursive leaf members, or the raw member DNs stored on the group.

For a working reference on a second screen, keep one rule in mind: use the cmdlet that matches the object you are querying. Get-ADUser is for user objects, Get-ADGroupMember is for group membership expansion, and Get-ADObject is the generic fallback when you need to query contacts or resolve mixed object types. Also remember that both Get-ADUser and Get-ADObject return only a default property set unless you explicitly request more with -Properties.

Before running any of the examples below, make sure the Active Directory module is available. Microsoft’s current documentation still treats the AD module as part of the ActiveDirectory PowerShell module and RSAT tooling, with Import-Module ActiveDirectory as the basic starting point. (Microsoft Learn)

Import-Module ActiveDirectory

Why the “manager” field is not just a display string

The manager attribute does not store a friendly display name. It stores the distinguished name of the manager object. That is why scripts that simply select manager often return a value like CN=Jane Smith,OU=Users,DC=contoso,DC=com instead of just Jane Smith. Active Directory maintains the reverse relationship through directReports, which is a backlink generated from users whose manager points to that object.

The safest pattern is to retrieve the user, read the manager DN, and then resolve that DN into a second Get-ADUser call.

$user = Get-ADUser -Identity "jdoe" -Properties manager,displayName,mail

if ($user.Manager) {
    $manager = Get-ADUser -Identity $user.Manager -Properties displayName,mail,title
    [PSCustomObject]@{
        UserSamAccountName = $user.SamAccountName
        UserDisplayName    = $user.DisplayName
        ManagerDisplayName = $manager.DisplayName
        ManagerMail        = $manager.Mail
        ManagerTitle       = $manager.Title
    }
}

That pattern is more reliable than regex-parsing the CN out of the DN, because it resolves the actual object and lets you retrieve additional manager attributes cleanly. It also matches how the schema defines the attribute: the stored value is a DN, not a formatted name.

If all you need is the manager’s display name in one line, this is the compact version:

$u = Get-ADUser -Identity "jdoe" -Properties manager
if ($u.Manager) {
    (Get-ADUser -Identity $u.Manager -Properties displayName).DisplayName
}

Export users and their manager names to CSV

For reporting, query the user set first, include the manager property, then resolve each manager DN inside a calculated property. Use SearchBase whenever possible so the script targets the OU you actually care about instead of the whole default naming context. Microsoft’s filter documentation explicitly supports SearchBase and SearchScope for narrowing filter-driven queries.

Get-ADUser -Filter * `
    -SearchBase "OU=Employees,DC=contoso,DC=com" `
    -Properties displayName,mail,department,manager |
Select-Object `
    SamAccountName,
    DisplayName,
    Mail,
    Department,
    @{Name='ManagerDisplayName';Expression={
        if ($_.Manager) {
            (Get-ADUser -Identity $_.Manager -Properties displayName).DisplayName
        }
    }},
    @{Name='ManagerMail';Expression={
        if ($_.Manager) {
            (Get-ADUser -Identity $_.Manager -Properties mail).Mail
        }
    }} |
Export-Csv "C:\Reports\Users-Managers.csv" -NoTypeInformation

If you want the reporting chain rather than only the immediate manager, follow the manager DN repeatedly until there is no next value. Microsoft Q&A examples use this same DN-resolution pattern for walking the hierarchy upward.

$username = "jdoe"
$user = Get-ADUser -Identity $username -Properties manager,displayName

$chain = @()
while ($user.Manager) {
    $user = Get-ADUser -Identity $user.Manager -Properties manager,displayName,title
    $chain += [PSCustomObject]@{
        DisplayName = $user.DisplayName
        Title       = $user.Title
    }
}

$chain

If you need the reverse relationship, query directReports on the manager object instead of scanning the whole directory.

$manager = Get-ADUser -Identity "jsmith" -Properties directReports

$manager.DirectReports |
ForEach-Object {
    Get-ADUser -Identity $_ -Properties displayName,mail,department
} |
Select-Object SamAccountName,DisplayName,Mail,Department

That works because directReports is the backlink for manager, so it is populated from users whose manager references the manager object.

How to find contacts in Active Directory

This is the second place scripts often go wrong. A contact is not the same thing as a user account. In the schema, the contact class is its own class, derived from organizationalPerson, and Microsoft’s LDAP filter examples treat contacts distinctly from users. That means Get-ADUser is the wrong default cmdlet for contact discovery; use Get-ADObject when querying contact objects.

A simple contact search looks like this:

Get-ADObject -Filter 'ObjectClass -eq "contact"' `
    -SearchBase "OU=Contacts,DC=contoso,DC=com" `
    -Properties displayName,mail,targetAddress,company |
Select-Object Name,DisplayName,Mail,targetAddress,Company,ObjectClass

If you want a tighter LDAP-style query, especially when filtering on mail-enabled contacts, use -LDAPFilter:

Get-ADObject `
    -LDAPFilter '(&(objectCategory=person)(objectClass=contact)(mail=*))' `
    -SearchBase "OU=Contacts,DC=contoso,DC=com" `
    -Properties displayName,mail,targetAddress |
Select-Object DisplayName,Mail,targetAddress

That LDAP form follows Microsoft’s documented filter syntax examples for contact searches.

If you are just trying to “find a contact by name,” this is usually enough:

Get-ADObject -Filter 'ObjectClass -eq "contact" -and Name -like "*Smith*"' `
    -Properties displayName,mail |
Select-Object Name,DisplayName,Mail

Because Get-ADObject only returns a default property set unless you request more, always specify the additional contact attributes you need for export or reporting.

Get members of an AD group

For the common “Get members of AD group” task, Get-ADGroupMember is still the standard cmdlet. Microsoft documents that it takes a group identity and returns principal objects representing users, computers, or groups. With -Recursive, it walks the membership hierarchy and returns members that do not themselves contain child objects. That last point matters: recursive output is not the same as “show me every nested group and every direct member exactly as stored.”

Get direct members:

Get-ADGroupMember -Identity "HR-Shared"

Get recursive leaf members:

Get-ADGroupMember -Identity "HR-Shared" -Recursive

Get names only:

Get-ADGroupMember -Identity "HR-Shared" |
Select-Object Name |
Sort-Object Name

Export recursive members to CSV:

Get-ADGroupMember -Identity "HR-Shared" -Recursive |
Select-Object Name,SamAccountName,ObjectClass,DistinguishedName |
Export-Csv "C:\Reports\HR-Shared-members.csv" -NoTypeInformation

The key nuance is that recursive expansion changes the semantics of the output. Without -Recursive, nested groups remain groups in the result. With -Recursive, PowerShell resolves down to members in the hierarchy that do not contain child objects. Microsoft’s documentation states that directly, and a Microsoft Q&A example shows why piping non-recursive results straight into Get-ADUser can fail: some of those returned objects are groups, not users.

When Get-ADGroupMember is not enough

If you want every direct member exactly as stored on the group, including mixed object types, read the group’s Members attribute and resolve each DN yourself. This is especially useful when you need to inspect objects that are not convenient to handle as ADPrincipal output alone. It also lines up with the schema behavior: the group object’s member attribute stores distinguished names, and Microsoft’s schema reference notes that these may reference user, group, and contact objects.

$props = 'Name','DisplayName','Mail','ObjectClass','DistinguishedName'

(Get-ADGroup -Identity "HR-Shared").Members |
ForEach-Object {
    Get-ADObject -Identity $_ -Properties $props
} |
Select-Object $props |
Export-Csv "C:\Reports\HR-Shared-direct-members.csv" -NoTypeInformation

This pattern is the better choice when you need direct members only, when you need to preserve nested group objects as nested group objects, or when the group contains object types outside the usual “user only” reporting flow.

Common errors and what they usually mean

Get-ADGroupMember : Cannot find an object with identity ... usually means the identity you passed does not resolve in the current naming context, the value is blank, or the downstream cmdlet expects a different object type than what you supplied. Microsoft Q&A examples show this happening when scripts feed group objects or empty values into user-specific commands rather than resolving the correct identity first.

A practical triage sequence is:

Get-ADGroup -Identity "HR-Shared"
Get-ADUser  -Identity "jdoe"
Get-ADObject -Identity "CN=Jane Smith,OU=Users,DC=contoso,DC=com"

If the first lookup fails, your group identity is wrong or you are targeting the wrong domain or server. If the group resolves but a later Get-ADUser fails on a DN taken from mixed membership output, you are probably trying to treat a group or contact like a user. In that case, switch to Get-ADObject for resolution. That is a first-principles fix: match the cmdlet to the real object class instead of forcing everything through Get-ADUser.

There is also a separate cross-forest edge case. Microsoft documents that Get-ADGroupMember does not work when a group has members in another forest and that forest does not have Active Directory Web Service running. Microsoft also has a KB for failures involving deleted foreign security principals in domain local groups across trusts, where the cmdlet can return internal or unspecified errors.

The shortest decision rule for admins

Use Get-ADUser when the object is definitely a user and you need user attributes like manager, mail, or department. Use Get-ADObject when you need to search contacts or resolve mixed object types. Use Get-ADGroupMember when you need group expansion, but remember that -Recursive returns leaf members, not an exact map of every nested container as stored. Use the group’s raw Members attribute plus Get-ADObject when you need precise direct-member resolution.

The deeper pattern is simple: Active Directory scripts break when they ignore object class and linked attributes. Once you treat manager as a DN link, directReports as a backlink, contacts as their own class, and group membership as either expansion output or raw member DNs depending on the task, the commands become predictable and your exports become much easier to trust.

Exit mobile version