Published on

How to Get a Contacts Report in Active Directory (PowerShell Script)

Contacts are the quiet corner of Active Directory nobody audits. They don't log on, they don't show up in most user reports because they're a different object class entirely, and they tend to accumulate — an external vendor added to an address book years ago, a distribution list entry for someone who left the partner company. A clean directory needs them checked too.

The quick answer

Get-ADObject -Filter {ObjectClass -eq 'contact'} -Properties mail, telephoneNumber |
    Select-Object Name, mail, telephoneNumber

A more useful reporting script

#requires -Modules ActiveDirectory
<#
.SYNOPSIS
    Reports contact objects (mail contacts) in Active Directory.
#>

[CmdletBinding()]
param(
    [string]$SearchBase,
    [string]$OutputCsv
)

Import-Module ActiveDirectory -ErrorAction Stop

$params = @{
    Filter     = { ObjectClass -eq 'contact' }
    Properties = @('mail', 'telephoneNumber', 'whenCreated', 'DistinguishedName')
}
if ($SearchBase) { $params['SearchBase'] = $SearchBase }

$contacts = Get-ADObject @params |
    Select-Object Name,
                  @{Name = 'Email'; Expression = { $_.mail } },
                  @{Name = 'Phone'; Expression = { $_.telephoneNumber } },
                  whenCreated,
                  DistinguishedName |
    Sort-Object Name

if (-not $contacts) {
    Write-Host "No contact objects found." -ForegroundColor Yellow
    return
}

Write-Host "Found $(@($contacts).Count) contact(s)." -ForegroundColor Cyan
$contacts | Format-Table Name, Email, Phone, whenCreated -AutoSize

$noEmailCount = @($contacts | Where-Object { -not $_.Email }).Count
if ($noEmailCount -gt 0) {
    Write-Host "$noEmailCount contact(s) have no email address set." -ForegroundColor Yellow
}

if ($OutputCsv) {
    $contacts | Export-Csv -Path $OutputCsv -NoTypeInformation
    Write-Host "Exported results to $OutputCsv" -ForegroundColor Green
}

Why Get-ADUser won't show you these

Contacts are their own object class (contact), distinct from user — no logon name, no password, no ability to authenticate. They exist purely as directory entries, which is exactly why Get-ADUser skips them entirely and you need Get-ADObject with an explicit ObjectClass filter instead.

A contact with no email address is usually a leftover

A contact's entire purpose is being reachable by email — one with no mail attribute set can't actually do that, which almost always means the setup was never finished or the contact is stale. The script's summary line flags this count directly rather than making you scan the whole list for blanks.

Where the manual approach runs out of road

A one-off script is fine for a single check. It starts to hurt once you actually need to run this regularly:

  • No scheduling. Cron/Task Scheduler can run the script, but now you own the scheduling, the credentials it runs as, and what happens when it silently fails.
  • No history. A CSV export is a snapshot. Was this the same five accounts as last month, or a growing list? A single export can't tell you.
  • No distribution. Getting the report to the right people (security, IT ops, compliance) on a schedule means building that plumbing yourself.
  • Multi-domain/multi-forest pain. Run it once per domain, reconcile the results yourself, and hope naming/OU conventions are consistent across all of them.
  • No alerting. If something changes unexpectedly between runs — an account re-enabled, a privileged group gaining a member — nothing tells you until you happen to run the script again.

None of that is a PowerShell problem. It's what turns a script into a product.

What SysFlint AD does instead

Our SysFlint AD runs the same kind of discovery shown above automatically, on a schedule, entirely inside your own network. No agents on domain controllers, no data leaving your environment. It's free, forever.

  • Scheduled reports by email Daily, weekly, or monthly runs delivered to the right inbox automatically. No Task Scheduler job for you to babysit.
  • Multi-domain and forest coverage Enumerate every domain in the forest in one pass and get one consolidated report instead of one per domain.

If you're currently doing this with a script on a scheduled task, here's the honest comparison between the two, or go straight to the download page.

Get this script, and the rest of them

The script above is part of awesome-it-scripts, SysFlint's free, open-source library of PowerShell scripts for Active Directory and other IT-admin tasks — no signup, no catch, MIT licensed. Grab this one directly from active-directory/Get-ADContactsReport.ps1, or browse the whole thing.

Find every disabled/locked-out/stale/privileged-account script we've published (plus the FAQ that goes with each one) in the repo's active-directory folder. Star it if it's useful — new scripts land there regularly.