Skip to main content

PowerShell: Automate AD & M365 User Provisioning

A comprehensive PowerShell script to create new users in Active Directory and assign Microsoft 365 licences, mailboxes, and group memberships in one step.

7 min readUpdated March 29, 2026

Overviewsection

Creating a new user typically involves multiple manual steps across Active Directory, Microsoft 365, Exchange Online, and various internal systems. This script consolidates that process into a single command that creates the AD user, assigns an M365 licence, waits for mailbox provisioning, and adds group memberships.

Tip

For cloud-only organisations (no on-premises Active Directory), you can skip the AD and sync steps entirely and create users directly in Microsoft Entra ID using New-MgUser from the Microsoft Graph PowerShell SDK.

Prerequisitessection

  • Active Directory PowerShell module (RSAT-AD-PowerShell)
  • Microsoft Graph PowerShell SDK (Microsoft.Graph)
  • Exchange Online Management module (ExchangeOnlineManagement)
  • Domain admin rights for AD operations
  • Microsoft 365 admin or User Administrator role
  • Microsoft Entra Connect configured for user synchronisation (or Microsoft Entra Cloud Sync as an alternative)
  • PowerShell 5.1 or later (PowerShell 7.6 LTS recommended)

The Scriptsection

powershell
<#
.SYNOPSIS
    Provision a new user in Active Directory and Microsoft 365.
.DESCRIPTION
    Creates an AD user account, waits for Microsoft Entra Connect sync,
    assigns an M365 licence, and adds the user to standard groups.
.PARAMETER FirstName
    User's first name.
.PARAMETER LastName
    User's last name.
.PARAMETER Department
    Department name (used for OU placement and group membership).
.PARAMETER JobTitle
    Job title for the AD and M365 profile.
.PARAMETER Manager
    SAMAccountName of the user's manager.
.PARAMETER LicenceSku
    M365 licence SKU to assign. Defaults to Business Premium.
#>

param(
    [Parameter(Mandatory)]
    [string]$FirstName,

    [Parameter(Mandatory)]
    [string]$LastName,

    [string]$Department = "General",

    [string]$JobTitle = "",

    [string]$Manager = "",

    [string]$LicenceSku = "SPB" # Microsoft 365 Business Premium
)

# --- Configuration ---
$domain        = "corp.example.com"
$emailDomain   = "example.com"
$baseOU        = "OU=Users,DC=corp,DC=example,DC=com"
$usageLocation = "US"
$defaultGroups = @("All Staff", "VPN Users")

# --- Derived values ---
$displayName   = "$FirstName $LastName"
$samAccount    = ("$($FirstName[0])$LastName").ToLower() -replace '[^a-z0-9]', ''
$upn           = "$samAccount@$emailDomain"
$email         = "$FirstName.$LastName@$emailDomain".ToLower()

# Truncate SAM to 20 chars (AD limit)
if ($samAccount.Length -gt 20) {
    $samAccount = $samAccount.Substring(0, 20)
}

# Generate a temporary password
$tempPassword = "Welcome!" + (Get-Random -Minimum 1000 -Maximum 9999) + "!"

Write-Host "=== New User Provisioning ===" -ForegroundColor Cyan
Write-Host "Name:     $displayName"
Write-Host "SAM:      $samAccount"
Write-Host "UPN:      $upn"
Write-Host "Email:    $email"
Write-Host "Dept:     $Department"
Write-Host ""

# --- Step 1: Create AD User ---
Write-Host "[1/5] Creating Active Directory user..." -ForegroundColor Yellow

Import-Module ActiveDirectory -ErrorAction Stop

# Determine OU based on department
$targetOU = "OU=$Department,$baseOU"
if (-not (Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$targetOU'" -ErrorAction SilentlyContinue)) {
    Write-Warning "OU '$targetOU' not found. Using base OU."
    $targetOU = $baseOU
}

$newUserParams = @{
    Name              = $displayName
    GivenName         = $FirstName
    Surname           = $LastName
    DisplayName       = $displayName
    SamAccountName    = $samAccount
    UserPrincipalName = $upn
    EmailAddress      = $email
    Title             = $JobTitle
    Department        = $Department
    Path              = $targetOU
    AccountPassword   = (ConvertTo-SecureString $tempPassword -AsPlainText -Force)
    Enabled           = $true
    ChangePasswordAtLogon = $true
}

try {
    New-ADUser @newUserParams -ErrorAction Stop
    Write-Host "  AD user created successfully." -ForegroundColor Green
} catch {
    Write-Error "Failed to create AD user: $_"
    exit 1
}

# Set manager if provided
if ($Manager) {
    try {
        Set-ADUser -Identity $samAccount -Manager $Manager
        Write-Host "  Manager set to: $Manager" -ForegroundColor Green
    } catch {
        Write-Warning "Could not set manager: $_"
    }
}

# --- Step 2: Add to AD Groups ---
Write-Host "[2/5] Adding to AD groups..." -ForegroundColor Yellow

foreach ($group in $defaultGroups) {
    try {
        Add-ADGroupMember -Identity $group -Members $samAccount -ErrorAction Stop
        Write-Host "  Added to: $group" -ForegroundColor Green
    } catch {
        Write-Warning "Could not add to group '$group': $_"
    }
}

# --- Step 3: Wait for Microsoft Entra Connect Sync ---
Write-Host "[3/5] Waiting for Microsoft Entra Connect sync..." -ForegroundColor Yellow
Write-Host "  Triggering delta sync..."

try {
    # Trigger a delta sync on the Microsoft Entra Connect server
    Invoke-Command -ComputerName "AADC-SERVER" -ScriptBlock {
        Start-ADSyncSyncCycle -PolicyType Delta
    } -ErrorAction Stop
} catch {
    Write-Warning "Could not trigger sync. Waiting for scheduled sync (up to 30 min). Note: if using Entra Cloud Sync, sync is automatic and this step can be skipped."
}

# Poll for the user to appear in Microsoft Entra ID
Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.ReadWrite.All" -NoWelcome

$maxWait = 300 # 5 minutes
$elapsed = 0
$mgUser = $null

while ($elapsed -lt $maxWait) {
    $mgUser = Get-MgUser -Filter "userPrincipalName eq '$upn'" -ErrorAction SilentlyContinue
    if ($mgUser) { break }
    Start-Sleep -Seconds 15
    $elapsed += 15
    Write-Host "  Waiting... ($elapsed seconds)" -ForegroundColor Gray
}

if (-not $mgUser) {
    Write-Error "User did not sync to Microsoft Entra ID within $maxWait seconds. Assign licence manually."
    exit 1
}

Write-Host "  User found in Microsoft Entra ID: $($mgUser.Id)" -ForegroundColor Green

# --- Step 4: Assign Microsoft 365 Licence ---
Write-Host "[4/5] Assigning Microsoft 365 licence..." -ForegroundColor Yellow

# Set usage location (required before licence assignment)
Update-MgUser -UserId $mgUser.Id -UsageLocation $usageLocation

# Find the licence SKU
$sku = Get-MgSubscribedSku | Where-Object { $_.SkuPartNumber -eq $LicenceSku }

if (-not $sku) {
    Write-Error "Licence SKU '$LicenceSku' not found. Available SKUs:"
    Get-MgSubscribedSku | Select-Object SkuPartNumber, ConsumedUnits, @{N='Total';E={$_.PrepaidUnits.Enabled}}
    exit 1
}

$availableUnits = $sku.PrepaidUnits.Enabled - $sku.ConsumedUnits
if ($availableUnits -le 0) {
    Write-Error "No available licences for $LicenceSku. $($sku.ConsumedUnits)/$($sku.PrepaidUnits.Enabled) consumed."
    exit 1
}

Set-MgUserLicense -UserId $mgUser.Id -AddLicenses @(@{SkuId = $sku.SkuId}) -RemoveLicenses @()
Write-Host "  Licence '$LicenceSku' assigned ($availableUnits remaining)." -ForegroundColor Green

# --- Step 5: Verify Mailbox Provisioning ---
Write-Host "[5/5] Verifying Exchange Online mailbox..." -ForegroundColor Yellow

Connect-ExchangeOnline -ShowBanner:$false

$mailboxReady = $false
$elapsed = 0

while ($elapsed -lt 120) {
    $mailbox = Get-Mailbox -Identity $upn -ErrorAction SilentlyContinue
    if ($mailbox) {
        $mailboxReady = $true
        break
    }
    Start-Sleep -Seconds 10
    $elapsed += 10
    Write-Host "  Waiting for mailbox... ($elapsed seconds)" -ForegroundColor Gray
}

if ($mailboxReady) {
    Write-Host "  Mailbox provisioned: $($mailbox.PrimarySmtpAddress)" -ForegroundColor Green
} else {
    Write-Warning "Mailbox not ready after 2 minutes. It may still be provisioning."
}

# --- Summary ---
Write-Host ""
Write-Host "=== Provisioning Complete ===" -ForegroundColor Cyan
Write-Host "Display Name:    $displayName"
Write-Host "Email:           $email"
Write-Host "Username:        $samAccount"
Write-Host "Temp Password:   $tempPassword"
Write-Host "Licence:         $LicenceSku"
Write-Host "Groups:          $($defaultGroups -join ', ')"
Write-Host ""
Write-Host "IMPORTANT: Communicate the temporary password securely (not via email)." -ForegroundColor Yellow

Disconnect-MgGraph -ErrorAction SilentlyContinue
Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue

Usagesection

Create a single usersection

powershell
.\New-UserProvision.ps1 -FirstName "Jane" -LastName "Smith" -Department "Engineering" -JobTitle "Software Developer" -Manager "jdoe"

Create multiple users from a CSVsection

powershell
# CSV columns: FirstName, LastName, Department, JobTitle, Manager
Import-Csv ".\new-users.csv" | ForEach-Object {
    .\New-UserProvision.ps1 -FirstName $_.FirstName -LastName $_.LastName -Department $_.Department -JobTitle $_.JobTitle -Manager $_.Manager
}
Warning

The temporary password is displayed in the console output. Never email it to the user. Use a phone call, a secure messaging app, or your organisation's password delivery process.

Customisationsection

VariablePurposeChange to
$domainAD domain FQDNYour domain
$emailDomainEmail domainYour email domain
$baseOUBase OU for new usersYour user OU distinguished name
$usageLocationM365 usage location (country code)Your country code (US, GB, AU, etc.)
$defaultGroupsAD groups to add all new users toYour standard groups
$LicenceSkuM365 licence SKU part numberCheck Get-MgSubscribedSku for your SKUs

Troubleshootingsection

ErrorCauseFix
"User already exists"Duplicate SAM or UPNCheck AD for existing accounts with the same name
"Licence SKU not found"Wrong SKU nameRun Get-MgSubscribedSku to list available SKUs
"No available licences"All licences consumedPurchase more or remove unused assignments
"User did not sync"Microsoft Entra Connect not running or sync delayedCheck Entra Connect sync status on the server
"Could not trigger sync"No access to Entra Connect serverConfigure PowerShell remoting or wait for scheduled sync
PowerShellActive DirectoryMicrosoft 365AutomationUser Provisioning

Was this article helpful?