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.

  1. Create a blank CSV in your favorite spreadsheet editor, for example, Microsoft Excel or Google Sheets
  2. 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,Enabled

Critical 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 OrganizationalUnitPath to 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 Enabled column)
    • More complex OrganizationalUnitPath structures

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 -WhatIf for 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" -WhatIf

Execute 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.

Elsewhere On TurboGeek:  WinRM for Windows Server Nano: Common Issues & Fixes

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
    The OrganizationalUnitPath must be a valid DN (and quoted in the CSV). Example: "OU=Users,OU=Example,DC=com".
  • Enabled parsing error
    Use TRUE/FALSE, Yes/No, or 1/0. Avoid random values like Enabled, T, F.
  • User already exists
    The script will skip existing SamAccountName values. 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.

Richard.Bailey

Richard Bailey, a seasoned tech enthusiast, combines a passion for innovation with a knack for simplifying complex concepts. With over a decade in the industry, he's pioneered transformative solutions, blending creativity with technical prowess. An avid writer, Richard's articles resonate with readers, offering insightful perspectives that bridge the gap between technology and everyday life. His commitment to excellence and tireless pursuit of knowledge continues to inspire and shape the tech landscape.

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

Translate »