Managing Active Directory manually through the GUI works for a handful of users, but it becomes unsustainable when you need to onboard 50 employees, audit thousands of accounts for compliance, or disable departed users across multiple OUs. PowerShell transforms Active Directory administration from a repetitive, error-prone chore into a fast, repeatable, and auditable process. Every action becomes a script you can review, test, and schedule.
This guide covers the essential PowerShell commands and scripts every Windows administrator needs for Active Directory automation — from querying users and managing groups to bulk provisioning from CSV files and generating compliance reports.
Prerequisites
Before you begin, ensure you have:
- Windows Server 2016+ with Active Directory Domain Services role, or a workstation joined to the domain
- RSAT Active Directory module installed
- PowerShell 5.1 or later (PowerShell 7 also supports the AD module)
- Appropriate AD permissions (Domain Admin for full operations, or delegated permissions for specific OUs)
Install the AD Module
On a Domain Controller:
Install-WindowsFeature RSAT-AD-PowerShell
On a Windows 10/11 workstation:
# PowerShell (elevated)
Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0
Verify the module is available:
Import-Module ActiveDirectory
Get-Module ActiveDirectory
Querying Active Directory
Find Users
The Get-ADUser cmdlet is your primary tool for querying user accounts:
# Get a specific user with all properties
Get-ADUser -Identity jsmith -Properties *
# Find all enabled users
Get-ADUser -Filter {Enabled -eq $true} -Properties DisplayName, EmailAddress |
Select-Object SamAccountName, DisplayName, EmailAddress
# Find users in a specific OU
Get-ADUser -Filter * -SearchBase "OU=Sales,DC=contoso,DC=com" |
Select-Object Name, SamAccountName
# Find users whose password is expiring within 7 days
$cutoff = (Get-Date).AddDays(7)
Get-ADUser -Filter {Enabled -eq $true -and PasswordNeverExpires -eq $false} `
-Properties PasswordLastSet, msDS-UserPasswordExpiryTimeComputed |
Where-Object { [datetime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed') -lt $cutoff } |
Select-Object Name, SamAccountName, @{N='ExpiryDate';E={[datetime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed')}}
Find Inactive Accounts
Locate accounts that haven’t logged in for 90 days:
$inactiveDays = 90
$cutoffDate = (Get-Date).AddDays(-$inactiveDays)
Get-ADUser -Filter {LastLogonDate -lt $cutoffDate -and Enabled -eq $true} `
-Properties LastLogonDate, Description, Department |
Select-Object SamAccountName, Name, LastLogonDate, Department |
Sort-Object LastLogonDate |
Export-Csv -Path "C:\Reports\InactiveUsers.csv" -NoTypeInformation
Write-Host "Report saved. Found $(($report | Measure-Object).Count) inactive accounts."
Query Group Memberships
# List all members of a group
Get-ADGroupMember -Identity "Domain Admins" -Recursive |
Select-Object Name, SamAccountName, objectClass
# Find all groups a user belongs to
Get-ADUser -Identity jsmith -Properties MemberOf |
Select-Object -ExpandProperty MemberOf |
ForEach-Object { (Get-ADGroup $_).Name }
# Find empty groups
Get-ADGroup -Filter * -Properties Members |
Where-Object { $_.Members.Count -eq 0 } |
Select-Object Name, GroupScope, GroupCategory
Automating User Provisioning
Create a Single User
$password = ConvertTo-SecureString "T3mp@P@ss2026!" -AsPlainText -Force
New-ADUser -Name "Jane Smith" `
-GivenName "Jane" `
-Surname "Smith" `
-SamAccountName "janesmith" `
-UserPrincipalName "janesmith@contoso.com" `
-Path "OU=Sales,OU=Users,DC=contoso,DC=com" `
-AccountPassword $password `
-ChangePasswordAtLogon $true `
-Enabled $true `
-Department "Sales" `
-Title "Account Manager" `
-Office "Building A" `
-EmailAddress "janesmith@contoso.com"
Bulk User Creation from CSV
Create a CSV file (new-users.csv):
FirstName,LastName,Username,Department,Title,OU,Groups
Jane,Smith,jsmith,Sales,Account Manager,"OU=Sales,OU=Users,DC=contoso,DC=com","Sales Team;VPN Users"
Bob,Johnson,bjohnson,Engineering,Developer,"OU=Engineering,OU=Users,DC=contoso,DC=com","Dev Team;VPN Users;GitHub Access"
Maria,Garcia,mgarcia,HR,HR Analyst,"OU=HR,OU=Users,DC=contoso,DC=com","HR Team;Payroll Readers"
The provisioning script:
# Bulk-Create-ADUsers.ps1
param(
[Parameter(Mandatory=$true)]
[string]$CsvPath,
[string]$DefaultPassword = "Welcome@2026!",
[string]$LogPath = "C:\Logs\UserCreation_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
)
$ErrorActionPreference = "Continue"
Import-Module ActiveDirectory
$securePassword = ConvertTo-SecureString $DefaultPassword -AsPlainText -Force
$users = Import-Csv -Path $CsvPath
$created = 0
$failed = 0
foreach ($user in $users) {
$upn = "$($user.Username)@contoso.com"
$displayName = "$($user.FirstName) $($user.LastName)"
try {
# Check if user already exists
if (Get-ADUser -Filter {SamAccountName -eq $($user.Username)} -ErrorAction SilentlyContinue) {
$msg = "SKIP: User $($user.Username) already exists"
Write-Warning $msg
Add-Content -Path $LogPath -Value "$(Get-Date) - $msg"
continue
}
# Create the user
New-ADUser -Name $displayName `
-GivenName $user.FirstName `
-Surname $user.LastName `
-SamAccountName $user.Username `
-UserPrincipalName $upn `
-Path $user.OU `
-AccountPassword $securePassword `
-ChangePasswordAtLogon $true `
-Enabled $true `
-Department $user.Department `
-Title $user.Title `
-EmailAddress "$($user.Username)@contoso.com"
# Add to groups
if ($user.Groups) {
$groups = $user.Groups -split ";"
foreach ($group in $groups) {
Add-ADGroupMember -Identity $group.Trim() -Members $user.Username
}
}
$msg = "OK: Created $($user.Username) in $($user.OU)"
Write-Host $msg -ForegroundColor Green
Add-Content -Path $LogPath -Value "$(Get-Date) - $msg"
$created++
}
catch {
$msg = "FAIL: $($user.Username) - $($_.Exception.Message)"
Write-Host $msg -ForegroundColor Red
Add-Content -Path $LogPath -Value "$(Get-Date) - $msg"
$failed++
}
}
Write-Host "`nResults: $created created, $failed failed. Log: $LogPath"
Run the script:
.\Bulk-Create-ADUsers.ps1 -CsvPath "C:\Data\new-users.csv"
Bulk Modifications
Update User Attributes
# Update department for all users in an OU
Get-ADUser -Filter * -SearchBase "OU=Sales,DC=contoso,DC=com" |
Set-ADUser -Department "Sales & Marketing" -Company "Contoso Ltd"
# Set manager for a team
$manager = (Get-ADUser -Identity "manager01").DistinguishedName
Get-ADUser -Filter {Department -eq "Engineering"} |
Set-ADUser -Manager $manager
# Move users between OUs
Get-ADUser -Filter {Department -eq "Sales"} -SearchBase "OU=OldSales,DC=contoso,DC=com" |
Move-ADObject -TargetPath "OU=Sales,OU=Users,DC=contoso,DC=com"
Disable Departed Users
# Disable-DepartedUsers.ps1
param(
[Parameter(Mandatory=$true)]
[string]$CsvPath # CSV with a "Username" column
)
$disabledOU = "OU=Disabled Users,DC=contoso,DC=com"
$users = Import-Csv -Path $CsvPath
foreach ($user in $users) {
try {
$adUser = Get-ADUser -Identity $user.Username -Properties MemberOf
# Disable the account
Disable-ADAccount -Identity $user.Username
# Remove from all groups except Domain Users
$adUser.MemberOf | ForEach-Object {
Remove-ADGroupMember -Identity $_ -Members $user.Username -Confirm:$false
}
# Add a description with the disable date
Set-ADUser -Identity $user.Username `
-Description "Disabled $(Get-Date -Format 'yyyy-MM-dd') - Departed"
# Move to Disabled Users OU
$adUser | Move-ADObject -TargetPath $disabledOU
Write-Host "Disabled: $($user.Username)" -ForegroundColor Yellow
}
catch {
Write-Host "Error disabling $($user.Username): $($_.Exception.Message)" -ForegroundColor Red
}
}
Reporting Scripts
Active Directory Health Report
# AD-HealthReport.ps1
$domain = Get-ADDomain
$report = @()
# Domain info
Write-Host "Domain: $($domain.DNSRoot)" -ForegroundColor Cyan
Write-Host "Forest: $($domain.Forest)"
Write-Host "Domain Controllers: $(($domain.ReplicaDirectoryServers | Measure-Object).Count)"
# User statistics
$allUsers = Get-ADUser -Filter * -Properties Enabled, LastLogonDate, PasswordExpired, PasswordNeverExpires
$totalUsers = ($allUsers | Measure-Object).Count
$enabledUsers = ($allUsers | Where-Object { $_.Enabled -eq $true } | Measure-Object).Count
$disabledUsers = $totalUsers - $enabledUsers
$expiredPasswords = ($allUsers | Where-Object { $_.PasswordExpired -eq $true -and $_.Enabled -eq $true } | Measure-Object).Count
$neverExpire = ($allUsers | Where-Object { $_.PasswordNeverExpires -eq $true -and $_.Enabled -eq $true } | Measure-Object).Count
# Build HTML report
$html = @"
<html>
<head><style>
body { font-family: Segoe UI, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th { background: #0078D4; color: white; padding: 8px; text-align: left; }
td { border: 1px solid #ddd; padding: 8px; }
tr:nth-child(even) { background: #f2f2f2; }
.warn { color: #d83b01; font-weight: bold; }
</style></head>
<body>
<h1>Active Directory Health Report</h1>
<p>Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm')</p>
<h2>User Statistics</h2>
<table>
<tr><th>Metric</th><th>Count</th></tr>
<tr><td>Total Users</td><td>$totalUsers</td></tr>
<tr><td>Enabled</td><td>$enabledUsers</td></tr>
<tr><td>Disabled</td><td>$disabledUsers</td></tr>
<tr><td class="warn">Expired Passwords (Active)</td><td class="warn">$expiredPasswords</td></tr>
<tr><td class="warn">Password Never Expires</td><td class="warn">$neverExpire</td></tr>
</table>
</body></html>
"@
$html | Out-File "C:\Reports\AD-Health_$(Get-Date -Format 'yyyyMMdd').html"
Write-Host "Report saved to C:\Reports\" -ForegroundColor Green
Group Membership Audit
# Audit-PrivilegedGroups.ps1
$privilegedGroups = @(
"Domain Admins",
"Enterprise Admins",
"Schema Admins",
"Administrators",
"Account Operators",
"Backup Operators"
)
$results = foreach ($group in $privilegedGroups) {
$members = Get-ADGroupMember -Identity $group -Recursive -ErrorAction SilentlyContinue
foreach ($member in $members) {
[PSCustomObject]@{
Group = $group
Name = $member.Name
Account = $member.SamAccountName
ObjectClass = $member.objectClass
}
}
}
$results | Format-Table -AutoSize
$results | Export-Csv -Path "C:\Reports\PrivilegedGroups_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Scheduling AD Scripts
Create a Scheduled Task
Automate your scripts to run on a schedule:
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\AD-HealthReport.ps1"
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "07:00"
$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-DontStopOnIdleEnd `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask `
-TaskName "AD Weekly Health Report" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-RunLevel Highest `
-User "CONTOSO\svc-adreports" `
-Password "ServiceAccountP@ss!"
Troubleshooting Common Issues
”Unable to find a default server with Active Directory Web Services running”
This error means PowerShell cannot reach a domain controller:
# Test connectivity to DCs
Test-Connection -ComputerName DC01 -Count 2
# Specify a DC directly
Get-ADUser -Identity jsmith -Server "DC01.contoso.com"
# Check AD Web Services
Get-Service ADWS -ComputerName DC01
Slow Queries on Large Domains
# Bad: retrieves ALL properties for ALL users
Get-ADUser -Filter * -Properties * # Slow!
# Good: retrieve only what you need
Get-ADUser -Filter {Department -eq "Sales"} -Properties EmailAddress, LastLogonDate `
-ResultSetSize 1000
# Use -SearchBase to limit scope
Get-ADUser -Filter * -SearchBase "OU=Sales,DC=contoso,DC=com" -SearchScope Subtree
Permission Errors
# Check your current permissions
whoami /groups
# Test if you can read an OU
Get-ADOrganizationalUnit -Identity "OU=Sales,DC=contoso,DC=com" -ErrorAction Stop
# Run as a different credential
$cred = Get-Credential
Get-ADUser -Filter * -Credential $cred
PowerShell AD Quick Reference
| Cmdlet | Description |
|---|---|
Get-ADUser | Query user accounts with filters |
New-ADUser | Create a new user account |
Set-ADUser | Modify user attributes |
Remove-ADUser | Delete a user account |
Enable-ADAccount | Enable a disabled account |
Disable-ADAccount | Disable an account |
Unlock-ADAccount | Unlock a locked-out account |
Get-ADGroup | Query group objects |
Add-ADGroupMember | Add members to a group |
Remove-ADGroupMember | Remove members from a group |
Move-ADObject | Move objects between OUs |
Search-ADAccount | Find locked, expired, or inactive accounts |
Summary
PowerShell is the most powerful tool available for managing Active Directory at scale. You’ve learned how to query users and groups efficiently, automate user provisioning from CSV files with proper error handling, perform bulk modifications across OUs, generate HTML health reports, audit privileged group memberships, and schedule scripts for ongoing automation.
Build a library of tested scripts in a shared repository, log every automated action for audit trails, and always test against a small subset before running bulk operations in production. These practices transform AD management from reactive firefighting into proactive, documented infrastructure management.