ANAVEM
Reference
Languagefr
How to Bulk Import Users into Microsoft 365 with PowerShell and CSV

How to Bulk Import Users into Microsoft 365 with PowerShell and CSV

Learn to efficiently import hundreds of users into Microsoft 365 using Microsoft Graph PowerShell and CSV files with proper error handling and verification.

Emanuel DE ALMEIDAEmanuel DE ALMEIDA
3/16/2026 15 min 0
mediummicrosoft365 7 steps 15 min

Why Use PowerShell for Microsoft 365 Bulk User Import?

Managing user accounts in Microsoft 365 becomes challenging when dealing with dozens or hundreds of new employees. The web-based admin center works fine for individual users, but manually creating 50+ accounts is time-consuming and error-prone. PowerShell automation solves this problem by processing CSV files containing user data and creating accounts programmatically.

What Has Changed in Microsoft 365 User Management?

Microsoft deprecated the MSOnline and AzureAD PowerShell modules in 2024-2025, making Microsoft Graph PowerShell the only supported method for bulk operations. The new Microsoft Graph SDK (version 2.21.1 as of March 2026) provides enhanced bulk operation support, better error handling, and improved throttling management. This transition requires updating scripts from legacy cmdlets like New-MsolUser to modern Graph cmdlets like New-MgUser.

What Will You Accomplish in This Tutorial?

You'll learn to create a complete bulk import solution that handles hundreds of users efficiently. The process includes CSV validation, conflict detection, error handling, progress tracking, and automatic license assignment. By the end, you'll have a reusable PowerShell script that can import users with proper verification and detailed logging. This approach scales from small batches to enterprise-level imports while maintaining data integrity and providing comprehensive audit trails.

Implementation Guide

Full Procedure

01

Install Microsoft Graph PowerShell Module

First, install the Microsoft Graph PowerShell SDK, which replaced the deprecated MSOnline and AzureAD modules. Open PowerShell 7+ as administrator and run the installation command.

Install-Module Microsoft.Graph -Force -AllowClobber -Scope CurrentUser
Import-Module Microsoft.Graph.Users

The installation downloads the latest version (2.21.1 as of March 2026) with enhanced bulk operation support and improved throttling handling.

Pro tip: Use -Scope CurrentUser to avoid requiring administrator privileges for module installation.

Verification: Check the installed version:

Get-Module Microsoft.Graph -ListAvailable | Select-Object Name, Version

You should see version 2.21.1 or later listed.

02

Create the CSV File with Required Headers

Create a properly formatted CSV file with UTF-8 encoding. The file must include specific headers that match Microsoft Graph user parameters. Save this as users.csv:

UserPrincipalName,DisplayName,MailNickname,GivenName,Surname,PasswordProfile,UsageLocation,City,Department
user1@contoso.com,"John Smith","jsmith","John","Smith","TempPass123!","US","Seattle","IT"
user2@contoso.com,"Jane Doe","jdoe","Jane","Doe","TempPass123!","US","New York","HR"
user3@contoso.com,"Mike Johnson","mjohnson","Mike","Johnson","TempPass123!","US","Chicago","Finance"

Required columns include UserPrincipalName, DisplayName, MailNickname, and PasswordProfile. Optional fields like UsageLocation are crucial for license assignment later.

Warning: Passwords must meet your tenant's complexity requirements (minimum 8 characters with uppercase, lowercase, number, and symbol). Weak passwords will cause import failures.

Verification: Open the CSV file in a text editor to ensure UTF-8 encoding and proper comma separation without extra spaces.

03

Connect to Microsoft Graph with Required Permissions

Establish a connection to Microsoft Graph with the necessary permissions for user management. Run this command and complete the interactive authentication:

Connect-MgGraph -Scopes "User.ReadWrite.All"

This opens a browser window for authentication. Sign in with your Global Administrator or User Administrator account. The User.ReadWrite.All scope grants permission to create and modify users.

For automated scripts without interactive authentication, use certificate-based authentication:

# For automation (requires app registration)
Connect-MgGraph -ClientId "your-app-id" -TenantId "your-tenant-id" -CertificateThumbprint "your-cert-thumbprint"
Pro tip: If MFA blocks the connection, use device code authentication: Connect-MgGraph -UseDeviceAuthentication -Scopes "User.ReadWrite.All"

Verification: Confirm the connection and permissions:

Get-MgContext | Select-Object Scopes, Account, TenantId
04

Validate CSV Data and Check for Conflicts

Load the CSV file and validate that all required fields are present. This step prevents errors during the bulk import process:

# Load CSV data
$userData = Import-Csv -Path "C:\temp\users.csv" -Encoding UTF8

# Validate required fields
$requiredFields = @('UserPrincipalName', 'DisplayName', 'MailNickname', 'PasswordProfile')
foreach ($field in $requiredFields) {
    if ($field -notin ($userData[0].PSObject.Properties.Name)) {
        Write-Error "Missing required field: $field"
        exit 1
    }
}

Write-Host "Loaded $($userData.Count) users for import" -ForegroundColor Green

Next, check for existing users to prevent conflicts:

# Check for existing users
$conflicts = @()
foreach ($user in $userData) {
    try {
        $existingUser = Get-MgUser -Filter "userPrincipalName eq '$($user.UserPrincipalName)'" -ErrorAction SilentlyContinue
        if ($existingUser) {
            $conflicts += $user.UserPrincipalName
        }
    } catch {
        # User doesn't exist, continue
    }
}

if ($conflicts.Count -gt 0) {
    Write-Warning "Found $($conflicts.Count) existing users: $($conflicts -join ', ')"
}

Verification: Review the output to ensure all users are loaded and no critical conflicts exist.

05

Execute Bulk User Import with Error Handling

Run the bulk import process with comprehensive error handling and progress tracking. This script creates users one by one with detailed logging:

# Initialize tracking variables
$results = @()
$successCount = 0
$errorCount = 0

# Process each user
foreach ($user in $userData) {
    try {
        # Prepare password profile
        $passwordProfile = @{
            Password = $user.PasswordProfile
            ForceChangePasswordNextSignIn = $true
        }
        
        # Prepare user parameters
        $userParams = @{
            DisplayName = $user.DisplayName
            UserPrincipalName = $user.UserPrincipalName
            MailNickname = $user.MailNickname
            GivenName = $user.GivenName
            Surname = $user.Surname
            UsageLocation = $user.UsageLocation
            City = $user.City
            Department = $user.Department
            PasswordProfile = $passwordProfile
            AccountEnabled = $true
        }
        
        # Create the user
        $newUser = New-MgUser @userParams
        
        # Log success
        $results += [PSCustomObject]@{
            UPN = $user.UserPrincipalName
            Status = "Success"
            Id = $newUser.Id
            Error = ""
        }
        $successCount++
        Write-Host "✓ Created: $($user.DisplayName)" -ForegroundColor Green
        
        # Throttling delay to avoid API limits
        Start-Sleep -Seconds 2
        
    } catch {
        # Log failure
        $results += [PSCustomObject]@{
            UPN = $user.UserPrincipalName
            Status = "Failed"
            Id = ""
            Error = $_.Exception.Message
        }
        $errorCount++
        Write-Host "✗ Failed: $($user.UserPrincipalName) - $($_.Exception.Message)" -ForegroundColor Red
    }
}

# Export results
$results | Export-Csv -Path "C:\temp\import_results.csv" -NoTypeInformation
Write-Host "\nImport Summary: Success: $successCount | Failed: $errorCount" -ForegroundColor Cyan
Warning: Microsoft Graph has throttling limits. The 2-second delay prevents hitting the 50 users/hour limit for bulk operations.

Verification: Check the results CSV file and verify successful users in the Microsoft 365 admin center.

06

Assign Licenses to Successfully Created Users

After creating users, assign licenses to enable Microsoft 365 services. First, identify available license SKUs in your tenant:

# Get available licenses
$availableLicenses = Get-MgSubscribedSku | Select-Object SkuPartNumber, SkuId, ConsumedUnits, PrepaidUnits
$availableLicenses | Format-Table

# Get Microsoft 365 E3 license (example)
$e3Sku = (Get-MgSubscribedSku | Where-Object SkuPartNumber -eq "ENTERPRISEPACK").SkuId

Now assign licenses to successfully created users:

# Assign licenses to successful users
$successfulUsers = $results | Where-Object Status -eq "Success"
$licenseSuccessCount = 0
$licenseErrorCount = 0

foreach ($user in $successfulUsers) {
    try {
        # Assign license
        Set-MgUserLicense -UserId $user.Id -AddLicenses @(@{SkuId = $e3Sku}) -RemoveLicenses @()
        
        Write-Host "✓ Licensed: $($user.UPN)" -ForegroundColor Green
        $licenseSuccessCount++
        
        # Small delay to avoid throttling
        Start-Sleep -Seconds 1
        
    } catch {
        Write-Host "✗ License failed: $($user.UPN) - $($_.Exception.Message)" -ForegroundColor Red
        $licenseErrorCount++
    }
}

Write-Host "\nLicense Summary: Success: $licenseSuccessCount | Failed: $licenseErrorCount" -ForegroundColor Cyan
Pro tip: UsageLocation must be set before assigning licenses. If you encounter licensing errors, verify the UsageLocation field in your CSV matches your tenant's allowed countries.

Verification: Check user licenses in the admin center or run:

Get-MgUser -UserId "user@contoso.com" | Select-Object DisplayName, AssignedLicenses
07

Verify Import Results and Clean Up

Perform final verification of the import process and clean up the PowerShell session. First, verify a sample of created users:

# Verify random sample of created users
$sampleUsers = $results | Where-Object Status -eq "Success" | Get-Random -Count 3

foreach ($user in $sampleUsers) {
    $verifyUser = Get-MgUser -UserId $user.Id | Select-Object DisplayName, UserPrincipalName, AccountEnabled, AssignedLicenses
    Write-Host "Verified: $($verifyUser.DisplayName) - Enabled: $($verifyUser.AccountEnabled)" -ForegroundColor Green
}

Generate a comprehensive summary report:

# Generate final report
$report = @{
    TotalProcessed = $userData.Count
    SuccessfulCreations = $successCount
    FailedCreations = $errorCount
    SuccessfulLicensing = $licenseSuccessCount
    FailedLicensing = $licenseErrorCount
    Timestamp = Get-Date
}

$report | ConvertTo-Json | Out-File "C:\temp\import_summary.json"
Write-Host "\n=== FINAL IMPORT SUMMARY ===" -ForegroundColor Yellow
$report | Format-List

Clean up the PowerShell session:

# Disconnect from Microsoft Graph
Disconnect-MgGraph
Write-Host "Disconnected from Microsoft Graph" -ForegroundColor Green
Pro tip: Keep the results CSV file for future reference and troubleshooting. It contains the Object IDs of created users for easy management.

Verification: Log into the Microsoft 365 admin center and navigate to Users > Active users to confirm all users appear with correct information and license assignments.

Frequently Asked Questions

What PowerShell modules are required for Microsoft 365 bulk user import in 2026?+
Microsoft Graph PowerShell SDK (version 2.21.1 or later) is the only supported module for bulk user operations. The legacy MSOnline and AzureAD modules were deprecated in 2024-2025 and no longer work with modern authentication. Install using 'Install-Module Microsoft.Graph -Scope CurrentUser' and import the Users module specifically.
How many users can I import at once using PowerShell into Microsoft 365?+
There's no hard limit on CSV file size, but Microsoft Graph has throttling limits of approximately 50 user creations per hour for bulk operations. For larger imports, implement delays between requests (2-3 seconds) or use batching with Invoke-MgGraphRequest. Most organizations successfully import 100-500 users per session with proper throttling.
What CSV format and encoding is required for Microsoft 365 user import?+
Use UTF-8 encoding with comma-separated values. Required columns include UserPrincipalName, DisplayName, MailNickname, and PasswordProfile. Optional but recommended columns are GivenName, Surname, UsageLocation (required for licensing), City, and Department. Passwords must meet tenant complexity requirements with minimum 8 characters including uppercase, lowercase, numbers, and symbols.
How do I handle errors and conflicts during bulk user import to Microsoft 365?+
Implement try-catch blocks around New-MgUser cmdlets to capture specific error messages. Common issues include duplicate UserPrincipalName conflicts, weak passwords, and missing UsageLocation for licensing. Pre-validate by checking existing users with Get-MgUser filters. Export results to CSV with success/failure status and error details for troubleshooting and retry operations.
Can I automatically assign licenses during bulk user import with PowerShell?+
License assignment requires a separate step after user creation using Set-MgUserLicense cmdlet. First identify available SKUs with Get-MgSubscribedSku, then assign licenses to successfully created users. UsageLocation must be set in the user object before license assignment. Common SKUs include ENTERPRISEPACK (Microsoft 365 E3) and ENTERPRISEPREMIUM (Microsoft 365 E5).
Emanuel DE ALMEIDA
Written by

Emanuel DE ALMEIDA

Microsoft MCSA-certified Cloud Architect | Fortinet-focused. I modernize cloud, hybrid & on-prem infrastructure for reliability, security, performance and cost control - sharing field-tested ops & troubleshooting.

Discussion

Share your thoughts and insights

You must be logged in to comment.

Loading comments...