How to Create Multiple Active Directory users in PowerShell
This is one of the most common tasks system admins have to perform, create new user accounts in Active Directory. Adding one or two users is fine, but what happens when you need to add 1000 users, and you have a deadline of the end of today? Fear not! PowerShell has a great Import-CSV feature that takes the content from a CSV and imports it. System Admins can put in place a requirement for managers to ensure the spreadsheet is completed before onboarding new users.
This procedure will explain how to structure the CSV file. How to create a new user for every entry on the CSV. Saving you hours and the headache of having to do it manually. Ready? Lets go!
Prerequisites
Environment
- A domain-joined Windows host (or management server) with RSAT installed.
- The ActiveDirectory PowerShell module available (
Import-Module ActiveDirectory). - An account with rights to create users in the target OUs.
Operational
- A standardized onboarding spreadsheet/CSV template enforced for managers/HR (reduces rework and errors).
- A controlled location for the CSV (access-controlled share, audited).

Step 1 – How to Format the CSV file.
A CSV file is a simple text file where data is organized in rows and columns, with commas separating each piece of information. It’s a versatile way to store and share tabular data between different software programs. PowerShell can read CSV files and use the data to complete tasks!
CSV Template (Core Information for AD User Accounts)
Here’s a CSV structure that captures the essential details needed to create new Active Directory user accounts. You can customize this further based on your specific requirements.
- Create a blank CSV in your favorite spreadsheet editor, for example, Microsoft Excel or Google Sheets
- Use the following headings in your CSV. Usually Line 1. Make sure each parameter is in its own column. (See picture for example)
FirstName,LastName,DisplayName,SamAccountName,UserPrincipalName,Password,OrganizationalUnitPath,EnabledCritical formatting rule: Distinguished Names must be quoted
An OU path (Distinguished Name) contains commas, so it must be wrapped in quotes or it will split across columns and break the import.
Here is some example data.
FirstName,LastName,DisplayName,SamAccountName,UserPrincipalName,OrganizationalUnitPath,Enabled,Password
John,Doe,"John Doe",jdoe,jdoe@example.com,"OU=Users,OU=Example,DC=com",TRUE,"TempP@ssw0rd!23"
Jane,Smith,"Jane Smith",jsmith,jsmith@example.com,"OU=Users,OU=Example,DC=com",FALSE,"TempP@ssw0rd!23"To get it to work, copy and paste the example data into Excel and use the Split Text to Column feature. Use “comma” as a Delimiter:

Picture Example:

Important Notes:
- Passwords: The passwords in this template are for demonstration purposes only. In a real-world scenario, NEVER use weak or easily guessable passwords. Always generate strong, unique passwords for each user and store them securely.
- Organizational Unit Path: Adjust the
OrganizationalUnitPathto match your Active Directory structure. - Data Variety: For more thorough testing, you might want to include additional variations in the data, such as:
- Different domain names in the
UserPrincipalName - A mix of enabled and disabled users (TRUE/FALSE in the
Enabledcolumn) - More complex
OrganizationalUnitPathstructures
- Different domain names in the
Remember: This template is designed to give you a starting point for testing. Adapt and expand it to suit your specific testing requirements.
Step 2 – Create PowerShell Script
Open PowerShell ISE as Administrator:
- Search for “PowerShell ISE” in your Start menu.
- Right-click on “Windows PowerShell ISE” and select “Run as administrator”.
Open the Script in PowerShell ISE:
- In PowerShell ISE, click on “File” -> “Open…”
- Navigate to the directory where you saved the PowerShell script and open it.
Use this code. Ensure you read the notes in green and adapt to your local environment.
This version:
- Validates required fields
- Verifies OU existence
- Skips users that already exist (idempotent behavior)
- Correctly converts “Enabled” strings to real booleans
- Supports
-WhatIffor safe testing - Logs results to a timestamped CSV
<#
.SYNOPSIS
Bulk create AD users from a CSV with validation, safe boolean parsing, and logging.
.REQUIREMENTS
- ActiveDirectory module (RSAT)
- Permissions to create users in target OUs
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path $_ })]
[string]$CsvPath,
# Optional: output log path (defaults next to CSV)
[string]$LogPath = $null
)
Import-Module ActiveDirectory -ErrorAction Stop
function Convert-ToBoolean {
param([Parameter(Mandatory)][string]$Value)
switch -Regex ($Value.Trim()) {
'^(?i:true|1|yes|y)$' { return $true }
'^(?i:false|0|no|n)$' { return $false }
default { throw "Invalid boolean value '$Value'. Use TRUE/FALSE, Yes/No, or 1/0." }
}
}
function New-RandomPassword {
# Simple generator (adjust to match your org policy)
# Produces: 16 chars with upper/lower/digit/symbol
$upper = "ABCDEFGHJKLMNPQRSTUVWXYZ"
$lower = "abcdefghijkmnopqrstuvwxyz"
$digits = "23456789"
$sym = "!@#$%^&*()-_=+?"
$all = ($upper + $lower + $digits + $sym).ToCharArray()
$rand = New-Object System.Random
$pw = @(
$upper[$rand.Next($upper.Length)]
$lower[$rand.Next($lower.Length)]
$digits[$rand.Next($digits.Length)]
$sym[$rand.Next($sym.Length)]
) + (1..12 | ForEach-Object { $all[$rand.Next($all.Length)] })
-join ($pw | Sort-Object { $rand.Next() })
}
$users = Import-Csv -Path $CsvPath
if (-not $LogPath) {
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$LogPath = Join-Path (Split-Path $CsvPath -Parent) "ad-user-import-$timestamp.log.csv"
}
$results = foreach ($u in $users) {
$result = [ordered]@{
SamAccountName = $u.SamAccountName
UserPrincipalName = $u.UserPrincipalName
OU = $u.OrganizationalUnitPath
Enabled = $u.Enabled
Status = "Pending"
Message = ""
}
try {
# Basic required-field validation
foreach ($field in @("FirstName","LastName","DisplayName","SamAccountName","UserPrincipalName","OrganizationalUnitPath","Enabled")) {
if ([string]::IsNullOrWhiteSpace($u.$field)) {
throw "Missing required field: $field"
}
}
# Convert Enabled safely (DO NOT use [bool]"FALSE" — it becomes $true because it's a non-empty string)
$enabledBool = Convert-ToBoolean -Value $u.Enabled
# Ensure OU exists (Distinguished Name)
$null = Get-ADObject -Identity $u.OrganizationalUnitPath -ErrorAction Stop
# Idempotency check (skip if user already exists)
$existing = Get-ADUser -Filter "SamAccountName -eq '$($u.SamAccountName)'" -ErrorAction SilentlyContinue
if ($existing) {
$result.Status = "Skipped"
$result.Message = "User already exists"
[pscustomobject]$result
continue
}
# Password handling: prefer generating if not provided
$plainPassword = if (-not [string]::IsNullOrWhiteSpace($u.Password)) { $u.Password } else { New-RandomPassword }
$securePassword = ConvertTo-SecureString $plainPassword -AsPlainText -Force
$newUserParams = @{
Name = $u.DisplayName
DisplayName = $u.DisplayName
GivenName = $u.FirstName
Surname = $u.LastName
SamAccountName = $u.SamAccountName
UserPrincipalName = $u.UserPrincipalName
Path = $u.OrganizationalUnitPath
AccountPassword = $securePassword
Enabled = $enabledBool
ChangePasswordAtLogon = $true
}
if ($PSCmdlet.ShouldProcess($u.SamAccountName, "Create AD user")) {
New-ADUser @newUserParams -ErrorAction Stop
}
$result.Status = "Created"
$result.Message = "Success"
}
catch {
$result.Status = "Failed"
$result.Message = $_.Exception.Message
}
[pscustomobject]$result
}
$results | Export-Csv -Path $LogPath -NoTypeInformation
Write-Host "Completed. Log written to: $LogPath"
Step 3 — Run it safely (then run it for real)
Test first (no changes)
.\New-AdUsersFromCsv.ps1 -CsvPath "C:\Path\To\users.csv" -WhatIfExecute for real
.\New-AdUsersFromCsv.ps1 -CsvPath "C:\Path\To\users.csv"The script writes a results log CSV (Created / Skipped / Failed) so you can report outcomes and retry failures cleanly.
View Output and Errors:
- The output and any error messages will be displayed in the PowerShell ISE console pane at the bottom.
Common failure causes and how to fix them
- “Missing required field …”
Your CSV headers don’t match, or there are blank cells. Fix the source data. - OU not found / invalid identity
TheOrganizationalUnitPathmust be a valid DN (and quoted in the CSV). Example:"OU=Users,OU=Example,DC=com". - Enabled parsing error
UseTRUE/FALSE,Yes/No, or1/0. Avoid random values likeEnabled,T,F. - User already exists
The script will skip existingSamAccountNamevalues. If you need “update” behavior, that should be a separate, explicit workflow (safer than accidental overwrites).
Security and governance recommendations
- Treat onboarding CSVs as sensitive (they often contain PII and may contain passwords).
- Prefer generated passwords + force change at next logon.
- Run under least privilege: delegated rights only to required OUs.
- Keep logs for audit, but don’t log plaintext passwords.

Recent Comments