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

Lang: powershell
  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

Lang: powershell
1.\Manage-BulkADUsers.ps1 -CsvPath "new_users.csv" -Action Create

Modifying Existing Users

Lang: powershell
1.\Manage-BulkADUsers.ps1 -CsvPath "update_users.csv" -Action Modify

Disabling Users

Lang: powershell
1.\Manage-BulkADUsers.ps1 -CsvPath "offboard_users.csv" -Action Disable

Preview Changes (WhatIf)

Lang: powershell
1.\Manage-BulkADUsers.ps1 -CsvPath "users.csv" -Action Create -WhatIf

CSV Format

For Creating Users

Lang: csv
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

Lang: csv
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

Lang: csv
1SamAccountName
2jdoe
3oldemployee
4contractor1

CSV Column Reference

ColumnRequired ForDescription
FirstNameCreateUser’s first name
LastNameCreateUser’s last name
SamAccountNameAllUser’s login name (unique)
EmailCreateEmail address (also used as UPN)
OUOptionalFull distinguished name of target OU
GroupsOptionalSemicolon-separated group names
DepartmentOptionalDepartment name
TitleOptionalJob title
ManagerOptionalManager’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

  1. Prepare CSV with new hire information
  2. Run in WhatIf mode to verify
  3. Execute creation script
  4. New users created with temporary password

Department Reorganization

  1. Export current users to CSV
  2. Update department and OU columns
  3. Run modify action
  4. Users moved and properties updated

Employee Offboarding

  1. List users to disable in CSV
  2. Run disable action
  3. 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

Lang: powershell
1# Verify CSV format
2Import-Csv -Path "users.csv" | Format-Table

Group 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

Lang: powershell
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