PowerShell: Automate New User Provisioning in Active Directory and Microsoft 365
A comprehensive PowerShell script to create new users in Active Directory and assign Microsoft 365 licences, mailboxes, and group memberships in one step.
Overview
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.
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.
Prerequisites
- 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 Script
<#
.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
Usage
Create a single user
.\New-UserProvision.ps1 -FirstName "Jane" -LastName "Smith" -Department "Engineering" -JobTitle "Software Developer" -Manager "jdoe"
Create multiple users from a CSV
# 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
}
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.
Customisation
| Variable | Purpose | Change to |
|---|---|---|
$domain | AD domain FQDN | Your domain |
$emailDomain | Email domain | Your email domain |
$baseOU | Base OU for new users | Your user OU distinguished name |
$usageLocation | M365 usage location (country code) | Your country code (US, GB, AU, etc.) |
$defaultGroups | AD groups to add all new users to | Your standard groups |
$LicenceSku | M365 licence SKU part number | Check Get-MgSubscribedSku for your SKUs |
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
| "User already exists" | Duplicate SAM or UPN | Check AD for existing accounts with the same name |
| "Licence SKU not found" | Wrong SKU name | Run Get-MgSubscribedSku to list available SKUs |
| "No available licences" | All licences consumed | Purchase more or remove unused assignments |
| "User did not sync" | Microsoft Entra Connect not running or sync delayed | Check Entra Connect sync status on the server |
| "Could not trigger sync" | No access to Entra Connect server | Configure PowerShell remoting or wait for scheduled sync |
Related Articles
Was this article helpful?
