Bulk User Management
Create, modify, or disable multiple Active Directory users from CSV
Table of Contents
Automatically manage multiple Active Directory users through CSV import with comprehensive error handling and logging.
Overview
This script processes CSV files to create, modify, or disable multiple Active Directory user accounts in bulk. It handles common user properties, group membership, organizational units, and includes robust error handling with detailed logging.
Use Case: When onboarding multiple employees, updating user properties across departments, or offboarding users in bulk.
Platform: Windows Server 2012 R2+, Active Directory environment Requirements: Administrator privileges, Active Directory PowerShell module Execution Time: Varies by number of users (typically 5-10 seconds per user)
The Script
1<#
2.SYNOPSIS
3 Bulk Active Directory user management from CSV
4
5.DESCRIPTION
6 Creates, modifies, or disables multiple AD users based on CSV input.
7 Handles user properties, group membership, OU placement, and includes
8 comprehensive error handling and logging.
9
10.PARAMETER CsvPath
11 Path to the CSV file containing user data
12
13.PARAMETER Action
14 Action to perform: Create, Modify, or Disable
15
16.PARAMETER LogPath
17 Path for the log file (default: current directory)
18
19.PARAMETER WhatIf
20 Show what would happen without making changes
21
22.EXAMPLE
23 .\Manage-BulkADUsers.ps1 -CsvPath "users.csv" -Action Create
24 Creates new users from the CSV file
25
26.EXAMPLE
27 .\Manage-BulkADUsers.ps1 -CsvPath "users.csv" -Action Modify -WhatIf
28 Shows what modifications would be made without applying them
29
30.EXAMPLE
31 .\Manage-BulkADUsers.ps1 -CsvPath "offboard.csv" -Action Disable
32 Disables users listed in the CSV file
33
34.NOTES
35 Author: glyph.sh
36 Requires: Active Directory PowerShell module, Administrator privileges
37 Reference: https://glyph.sh/kb/active-directory-management/
38
39 CSV Format:
40 FirstName,LastName,SamAccountName,Email,OU,Groups,Department,Title,Manager
41#>
42
43[CmdletBinding(SupportsShouldProcess)]
44param(
45 [Parameter(Mandatory = $true)]
46 [ValidateScript({Test-Path $_})]
47 [string]$CsvPath,
48
49 [Parameter(Mandatory = $true)]
50 [ValidateSet('Create', 'Modify', 'Disable')]
51 [string]$Action,
52
53 [string]$LogPath = ".\BulkUserManagement_$(Get-Date -Format 'yyyyMMdd_HHmmss').log",
54
55 [switch]$WhatIf
56)
57
58#Requires -Modules ActiveDirectory
59#Requires -RunAsAdministrator
60
61# Initialize logging
62function Write-Log {
63 param(
64 [string]$Message,
65 [ValidateSet('Info', 'Success', 'Warning', 'Error')]
66 [string]$Level = 'Info'
67 )
68
69 $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
70 $logMessage = "[$timestamp] [$Level] $Message"
71
72 # Console output with colors
73 switch ($Level) {
74 'Success' { Write-Host $logMessage -ForegroundColor Green }
75 'Warning' { Write-Host $logMessage -ForegroundColor Yellow }
76 'Error' { Write-Host $logMessage -ForegroundColor Red }
77 default { Write-Host $logMessage -ForegroundColor White }
78 }
79
80 # File output
81 Add-Content -Path $LogPath -Value $logMessage
82}
83
84# Validate AD user
85function Test-ADUserExists {
86 param([string]$SamAccountName)
87 try {
88 $null = Get-ADUser -Identity $SamAccountName -ErrorAction Stop
89 return $true
90 }
91 catch {
92 return $false
93 }
94}
95
96# Validate OU
97function Test-OUExists {
98 param([string]$OU)
99 try {
100 $null = Get-ADOrganizationalUnit -Identity $OU -ErrorAction Stop
101 return $true
102 }
103 catch {
104 return $false
105 }
106}
107
108# Create new AD user
109function New-BulkADUser {
110 param($UserData)
111
112 $samAccountName = $UserData.SamAccountName
113
114 if (Test-ADUserExists -SamAccountName $samAccountName) {
115 Write-Log "User $samAccountName already exists - skipping" -Level Warning
116 return $false
117 }
118
119 # Validate OU
120 if ($UserData.OU -and -not (Test-OUExists -OU $UserData.OU)) {
121 Write-Log "OU does not exist: $($UserData.OU) - skipping user $samAccountName" -Level Error
122 return $false
123 }
124
125 # Build user parameters
126 $userParams = @{
127 SamAccountName = $samAccountName
128 UserPrincipalName = $UserData.Email
129 Name = "$($UserData.FirstName) $($UserData.LastName)"
130 GivenName = $UserData.FirstName
131 Surname = $UserData.LastName
132 DisplayName = "$($UserData.FirstName) $($UserData.LastName)"
133 EmailAddress = $UserData.Email
134 Enabled = $true
135 ChangePasswordAtLogon = $true
136 AccountPassword = (ConvertTo-SecureString "TempP@ssw0rd!" -AsPlainText -Force)
137 }
138
139 # Add optional parameters
140 if ($UserData.OU) { $userParams['Path'] = $UserData.OU }
141 if ($UserData.Department) { $userParams['Department'] = $UserData.Department }
142 if ($UserData.Title) { $userParams['Title'] = $UserData.Title }
143 if ($UserData.Manager) { $userParams['Manager'] = $UserData.Manager }
144
145 try {
146 if ($WhatIf) {
147 Write-Log "Would create user: $samAccountName" -Level Info
148 }
149 else {
150 New-ADUser @userParams -ErrorAction Stop
151 Write-Log "Created user: $samAccountName" -Level Success
152
153 # Add to groups if specified
154 if ($UserData.Groups) {
155 $groups = $UserData.Groups -split ';'
156 foreach ($group in $groups) {
157 try {
158 Add-ADGroupMember -Identity $group.Trim() -Members $samAccountName -ErrorAction Stop
159 Write-Log " Added $samAccountName to group: $group" -Level Success
160 }
161 catch {
162 Write-Log " Failed to add to group $group : $($_.Exception.Message)" -Level Error
163 }
164 }
165 }
166 }
167 return $true
168 }
169 catch {
170 Write-Log "Failed to create user $samAccountName : $($_.Exception.Message)" -Level Error
171 return $false
172 }
173}
174
175# Modify existing AD user
176function Update-BulkADUser {
177 param($UserData)
178
179 $samAccountName = $UserData.SamAccountName
180
181 if (-not (Test-ADUserExists -SamAccountName $samAccountName)) {
182 Write-Log "User $samAccountName does not exist - skipping" -Level Warning
183 return $false
184 }
185
186 try {
187 # Build modification parameters
188 $modifyParams = @{}
189
190 if ($UserData.FirstName) { $modifyParams['GivenName'] = $UserData.FirstName }
191 if ($UserData.LastName) { $modifyParams['Surname'] = $UserData.LastName }
192 if ($UserData.Email) {
193 $modifyParams['EmailAddress'] = $UserData.Email
194 $modifyParams['UserPrincipalName'] = $UserData.Email
195 }
196 if ($UserData.Department) { $modifyParams['Department'] = $UserData.Department }
197 if ($UserData.Title) { $modifyParams['Title'] = $UserData.Title }
198 if ($UserData.Manager) { $modifyParams['Manager'] = $UserData.Manager }
199
200 # Update display name if first or last name changed
201 if ($UserData.FirstName -or $UserData.LastName) {
202 $user = Get-ADUser -Identity $samAccountName -Properties GivenName, Surname
203 $firstName = if ($UserData.FirstName) { $UserData.FirstName } else { $user.GivenName }
204 $lastName = if ($UserData.LastName) { $UserData.LastName } else { $user.Surname }
205 $modifyParams['DisplayName'] = "$firstName $lastName"
206 }
207
208 if ($modifyParams.Count -gt 0) {
209 if ($WhatIf) {
210 Write-Log "Would modify user: $samAccountName" -Level Info
211 }
212 else {
213 Set-ADUser -Identity $samAccountName @modifyParams -ErrorAction Stop
214 Write-Log "Modified user: $samAccountName" -Level Success
215 }
216 }
217
218 # Handle group membership
219 if ($UserData.Groups -and -not $WhatIf) {
220 $groups = $UserData.Groups -split ';'
221 foreach ($group in $groups) {
222 try {
223 $groupName = $group.Trim()
224 $isMember = Get-ADGroupMember -Identity $groupName -ErrorAction Stop |
225 Where-Object { $_.SamAccountName -eq $samAccountName }
226
227 if (-not $isMember) {
228 Add-ADGroupMember -Identity $groupName -Members $samAccountName -ErrorAction Stop
229 Write-Log " Added $samAccountName to group: $groupName" -Level Success
230 }
231 }
232 catch {
233 Write-Log " Failed to add to group $groupName : $($_.Exception.Message)" -Level Error
234 }
235 }
236 }
237
238 # Move to new OU if specified
239 if ($UserData.OU -and -not $WhatIf) {
240 if (Test-OUExists -OU $UserData.OU) {
241 $currentOU = (Get-ADUser -Identity $samAccountName -Properties DistinguishedName).DistinguishedName -replace '^CN=.+?,'
242 if ($currentOU -ne $UserData.OU) {
243 Move-ADObject -Identity (Get-ADUser -Identity $samAccountName).DistinguishedName -TargetPath $UserData.OU -ErrorAction Stop
244 Write-Log " Moved $samAccountName to OU: $($UserData.OU)" -Level Success
245 }
246 }
247 else {
248 Write-Log " OU does not exist: $($UserData.OU)" -Level Error
249 }
250 }
251
252 return $true
253 }
254 catch {
255 Write-Log "Failed to modify user $samAccountName : $($_.Exception.Message)" -Level Error
256 return $false
257 }
258}
259
260# Disable AD user
261function Disable-BulkADUser {
262 param($UserData)
263
264 $samAccountName = $UserData.SamAccountName
265
266 if (-not (Test-ADUserExists -SamAccountName $samAccountName)) {
267 Write-Log "User $samAccountName does not exist - skipping" -Level Warning
268 return $false
269 }
270
271 try {
272 $user = Get-ADUser -Identity $samAccountName -Properties Enabled
273
274 if (-not $user.Enabled) {
275 Write-Log "User $samAccountName already disabled - skipping" -Level Warning
276 return $false
277 }
278
279 if ($WhatIf) {
280 Write-Log "Would disable user: $samAccountName" -Level Info
281 }
282 else {
283 Disable-ADAccount -Identity $samAccountName -ErrorAction Stop
284 Write-Log "Disabled user: $samAccountName" -Level Success
285
286 # Optionally add description for audit trail
287 $description = "Disabled on $(Get-Date -Format 'yyyy-MM-dd') by bulk script"
288 Set-ADUser -Identity $samAccountName -Description $description -ErrorAction SilentlyContinue
289 }
290
291 return $true
292 }
293 catch {
294 Write-Log "Failed to disable user $samAccountName : $($_.Exception.Message)" -Level Error
295 return $false
296 }
297}
298
299# Main script execution
300Write-Host "========================================" -ForegroundColor Cyan
301Write-Host " Bulk AD User Management Script" -ForegroundColor Cyan
302Write-Host "========================================" -ForegroundColor Cyan
303Write-Host ""
304
305Write-Log "Script started - Action: $Action"
306Write-Log "CSV file: $CsvPath"
307Write-Log "Log file: $LogPath"
308
309if ($WhatIf) {
310 Write-Log "Running in WhatIf mode - no changes will be made" -Level Warning
311}
312
313# Import CSV
314Write-Log "Importing CSV data..."
315try {
316 $users = Import-Csv -Path $CsvPath -ErrorAction Stop
317 Write-Log "Loaded $($users.Count) users from CSV" -Level Success
318}
319catch {
320 Write-Log "Failed to import CSV: $($_.Exception.Message)" -Level Error
321 exit 1
322}
323
324# Validate required columns
325$requiredColumns = @('SamAccountName')
326if ($Action -eq 'Create') {
327 $requiredColumns += @('FirstName', 'LastName', 'Email')
328}
329
330$csvColumns = $users[0].PSObject.Properties.Name
331$missingColumns = $requiredColumns | Where-Object { $_ -notin $csvColumns }
332
333if ($missingColumns) {
334 Write-Log "CSV is missing required columns: $($missingColumns -join ', ')" -Level Error
335 exit 1
336}
337
338# Process users
339Write-Host ""
340Write-Log "Processing users..."
341Write-Host ""
342
343$successCount = 0
344$failCount = 0
345$skipCount = 0
346
347foreach ($user in $users) {
348 $result = switch ($Action) {
349 'Create' { New-BulkADUser -UserData $user }
350 'Modify' { Update-BulkADUser -UserData $user }
351 'Disable' { Disable-BulkADUser -UserData $user }
352 }
353
354 if ($result) {
355 $successCount++
356 }
357 elseif ($result -eq $false) {
358 $skipCount++
359 }
360 else {
361 $failCount++
362 }
363}
364
365# Summary
366Write-Host ""
367Write-Host "========================================" -ForegroundColor Cyan
368Write-Host " Bulk User Management Complete" -ForegroundColor Cyan
369Write-Host "========================================" -ForegroundColor Cyan
370Write-Host ""
371
372Write-Log "Summary:" -Level Info
373Write-Log " Total users processed: $($users.Count)" -Level Info
374Write-Log " Successful: $successCount" -Level Success
375Write-Log " Skipped: $skipCount" -Level Warning
376Write-Log " Failed: $failCount" -Level $(if ($failCount -gt 0) { 'Error' } else { 'Info' })
377Write-Host ""
378Write-Log "Log file saved to: $LogPath" -Level Info
379Write-Host ""Usage
Creating New Users
1.\Manage-BulkADUsers.ps1 -CsvPath "new_users.csv" -Action CreateModifying Existing Users
1.\Manage-BulkADUsers.ps1 -CsvPath "update_users.csv" -Action ModifyDisabling Users
1.\Manage-BulkADUsers.ps1 -CsvPath "offboard_users.csv" -Action DisablePreview Changes (WhatIf)
1.\Manage-BulkADUsers.ps1 -CsvPath "users.csv" -Action Create -WhatIfCSV Format
For Creating Users
1FirstName,LastName,SamAccountName,Email,OU,Groups,Department,Title,Manager
2John,Doe,jdoe,john.doe@company.com,"OU=Users,OU=IT,DC=company,DC=com","IT-Staff;All-Users",IT,"System Administrator",jsmith
3Jane,Smith,jsmith,jane.smith@company.com,"OU=Users,OU=Sales,DC=company,DC=com","Sales-Team;All-Users",Sales,"Sales Manager",For Modifying Users
1SamAccountName,Email,Department,Title,Groups
2jdoe,john.doe@newdomain.com,Engineering,"Senior Engineer","Engineering-Team;Project-Leads"
3jsmith,jane.smith@newdomain.com,Sales,"VP of Sales","Sales-Team;Management"For Disabling Users
1SamAccountName
2jdoe
3oldemployee
4contractor1CSV Column Reference
| Column | Required For | Description |
|---|---|---|
FirstName | Create | User’s first name |
LastName | Create | User’s last name |
SamAccountName | All | User’s login name (unique) |
Email | Create | Email address (also used as UPN) |
OU | Optional | Full distinguished name of target OU |
Groups | Optional | Semicolon-separated group names |
Department | Optional | Department name |
Title | Optional | Job title |
Manager | Optional | Manager’s SamAccountName |
Features
Error Handling
- Validates CSV structure before processing
- Checks if users/OUs/groups exist
- Continues processing if individual users fail
- Detailed error messages in log file
Logging
- Timestamped log entries
- Color-coded console output
- Persistent log files with summary statistics
- Tracks successes, failures, and skips
Safety Features
- WhatIf mode for previewing changes
- Validation of all AD objects before modification
- Skips duplicate operations
- Requires administrator privileges
User Properties Managed
- Basic information (name, email, UPN)
- Organizational data (department, title, manager)
- Group membership (multiple groups supported)
- OU placement and movement
- Account status (enabled/disabled)
Common Use Cases
Onboarding New Employees
- Prepare CSV with new hire information
- Run in WhatIf mode to verify
- Execute creation script
- New users created with temporary password
Department Reorganization
- Export current users to CSV
- Update department and OU columns
- Run modify action
- Users moved and properties updated
Employee Offboarding
- List users to disable in CSV
- Run disable action
- Accounts disabled with audit timestamp
After Running
For New Users
- Users created with temporary password:
TempP@ssw0rd! - Users must change password at first logon
- Verify users added to correct groups
- Confirm OU placement
For Modified Users
- Review log for any failed modifications
- Verify group memberships updated
- Check OU moves completed successfully
For Disabled Users
- Confirm accounts disabled in AD
- Review audit description added to users
- Consider moving to disabled users OU
- Plan for mailbox archival if needed
Troubleshooting
Common Issues
CSV Import Fails
1# Verify CSV format
2Import-Csv -Path "users.csv" | Format-TableGroup Not Found
- Ensure group names match exactly (case-sensitive)
- Use group name, not distinguished name
- Verify groups exist in AD
OU Path Invalid
- Use full distinguished name format
- Example:
OU=Users,OU=IT,DC=company,DC=com - Test with:
Get-ADOrganizationalUnit -Identity "OU=..."
Permission Denied
- Run PowerShell as Administrator
- Verify AD permissions for user creation/modification
- Check delegation on target OUs
See Also
Download
1# Download the script
2Invoke-WebRequest -Uri "https://glyph.sh/scripts/Manage-BulkADUsers.ps1" -OutFile "Manage-BulkADUsers.ps1"
3
4# Download sample CSV template
5Invoke-WebRequest -Uri "https://glyph.sh/scripts/bulk-users-template.csv" -OutFile "users-template.csv"
6
7# Run it
8.\Manage-BulkADUsers.ps1 -CsvPath "users.csv" -Action Create