
Bulk-Update AD User Attributes from a CSV
Before you run this
This guide reads a CSV of users and writes two attributes back to Active Directory for each one: Title and Department. Nothing is created or deleted — existing user objects have those two fields overwritten with the values in your file. That overwrite is a real change: if the CSV has a wrong value in a row, that user's title or department is now wrong until you fix it. There is no built-in "undo," so the safety step below is to export the current values first so you can put them back.
You need domain rights to modify those user objects — typically an account delegated write access to the target OU, or Domain Admin in a small shop. Run it from a machine that has the ActiveDirectory PowerShell module installed (RSAT on a workstation, or the AD DS role tools on a domain controller / management server), in an elevated PowerShell session running as a user with those rights. I'm assuming Windows Server 2019/2022 or Windows 10/11 with RSAT, Windows PowerShell 5.1, and a standard on-prem AD domain. If you're synced to Entra ID / Microsoft 365 with Azure AD Connect, these attributes will sync outward on the next cycle — factor that in.
Read the script before running it. Test it against one throwaway user (make a test account, point the CSV at only that user, confirm it does what you expect) before you point it at hundreds of rows. Every write in this guide runs with -WhatIf first — a dry run that shows what would change without changing anything. Don't remove -WhatIf until the dry-run output looks correct.
What I assume about your environment
- Domain-joined management box, PowerShell 5.1,
Import-Module ActiveDirectoryworks. - You identify each user by SamAccountName (the pre-Windows 2000 logon name). If your source spreadsheet keys on UPN or employee ID instead, I note that below.
- The account you're logged in as can modify the target users.
Confirm the module loads:
Import-Module ActiveDirectory
Get-Command Set-ADUser # should return the cmdlet, proving the module is present
Step 1 — Prepare the CSV
Keep the header row exactly as below. One row per user. Save as UTF-8 CSV, e.g. C:\path\to\updates.csv:
SamAccountName,Title,Department
jsmith,Senior Analyst,Finance
adoe,Support Technician,IT
bwong,Office Manager,Operations
SamAccountName is the key I'll match on. Title and Department are the values to write. If a cell is blank, decide deliberately what you want — see the note in Step 3, because a blank does not mean "leave alone" unless you handle it.
Load it and eyeball it before doing anything else:
$users = Import-Csv -Path 'C:\path\to\updates.csv' # replace with your real path
$users | Format-Table -AutoSize # sanity-check the parsed rows
$users.Count # does the count match your file?
Step 2 — Back up the current values (your undo)
Before writing anything, export what those users have now. This file is how you roll back if a value goes in wrong.
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$backup = "C:\path\to\ad-attr-backup-$stamp.csv" # replace path
$users | ForEach-Object {
Get-ADUser -Identity $_.SamAccountName -Properties Title, Department |
Select-Object SamAccountName, Title, Department
} | Export-Csv -Path $backup -NoTypeInformation -Encoding UTF8
Write-Host "Backup written to $backup"
Open that backup and confirm it lists your users with their existing title/department. If any user throws a "cannot find an object" error here, your SamAccountName in the CSV doesn't match AD — fix the CSV before continuing rather than pushing bad keys forward.
Step 3 — Dry run with -WhatIf
Set-ADUser accepts -Title and -Department as named parameters, and supports -WhatIf. Run this exactly as written first — it changes nothing and prints what it would do:
foreach ($u in $users) {
# Skip rows that are missing the key entirely
if ([string]::IsNullOrWhiteSpace($u.SamAccountName)) {
Write-Warning "Row skipped: no SamAccountName."
continue
}
Set-ADUser -Identity $u.SamAccountName `
-Title $u.Title `
-Department $u.Department `
-WhatIf
}
Read the output. Each line should name the right user and the change you intend.
About blank cells: with the script above, a blank Title in the CSV writes an empty title, clearing whatever was there. If your intent is "only update non-empty cells and leave the rest untouched," gate each attribute so blanks are skipped:
foreach ($u in $users) {
if ([string]::IsNullOrWhiteSpace($u.SamAccountName)) { continue }
# Build only the attributes that have a value in this row
$set = @{}
if (-not [string]::IsNullOrWhiteSpace($u.Title)) { $set.Title = $u.Title }
if (-not [string]::IsNullOrWhiteSpace($u.Department)) { $set.Department = $u.Department }
if ($set.Count -gt 0) {
Set-ADUser -Identity $u.SamAccountName @set -WhatIf
}
}
Pick the behaviour you actually want and stick with that version. Don't mix the two.
If you key on something other than SamAccountName, replace -Identity $u.SamAccountName with a lookup — for example resolve a UPN first with Get-ADUser -Filter "UserPrincipalName -eq '$($u.UPN)'" and pipe the result into Set-ADUser. Check the exact -Filter syntax on Microsoft Learn for Get-ADUser before relying on it; quoting rules there catch people out.
Step 4 — Run it for real
Once the -WhatIf output is correct, remove -WhatIf and run the same loop. I like to keep a transcript so I have a record of every write:
Start-Transcript -Path "C:\path\to\ad-update-$stamp.log" # replace path
foreach ($u in $users) {
if ([string]::IsNullOrWhiteSpace($u.SamAccountName)) {
Write-Warning "Row skipped: no SamAccountName."
continue
}
try {
Set-ADUser -Identity $u.SamAccountName `
-Title $u.Title `
-Department $u.Department `
-ErrorAction Stop
Write-Host "Updated $($u.SamAccountName)"
}
catch {
Write-Warning "FAILED $($u.SamAccountName): $($_.Exception.Message)"
}
}
Stop-Transcript
The try/catch means one bad row (a user that no longer exists, a permissions problem) is logged and skipped instead of halting the whole batch.
Step 5 — Verify
Read the values back out of AD and confirm they match your CSV:
$users | ForEach-Object {
Get-ADUser -Identity $_.SamAccountName -Properties Title, Department |
Select-Object SamAccountName, Title, Department
} | Format-Table -AutoSize
Spot-check a few against the spreadsheet. You can also open Active Directory Users and Computers, view a user's Organization tab, and see Title and Department there. Note that ADUC may need a refresh (F5) to show the new values.
If you sync to Entra ID, the change won't appear in Microsoft 365 until the next Azure AD Connect sync cycle.
Undo / rollback
Because Step 2 saved the previous values, rolling back is the same operation pointed at the backup file:
$restore = Import-Csv -Path 'C:\path\to\ad-attr-backup-YYYYMMDD-HHMMSS.csv' # your backup
foreach ($r in $restore) {
Set-ADUser -Identity $r.SamAccountName `
-Title $r.Title `
-Department $r.Department `
-WhatIf # dry-run the restore first, then remove -WhatIf
}
Run it with -WhatIf first, confirm it's putting the original values back, then run it for real.
One caveat: if a user's original Title or Department was empty, the backup CSV holds a blank cell, and restoring it writes an empty value back — which is the correct original state. That's fine, just don't be surprised by empty fields in the restore output.
For the authoritative parameter list and syntax, see Microsoft Learn for Set-ADUser, Get-ADUser, and Import-Csv rather than trusting attribute names from memory — if you need to write an attribute that isn't a named parameter, the -Replace/-Clear hashtable approach is documented there.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
