
Bulk-Create Shared Mailboxes and Permissions in Exchange Online with PowerShell
Before you run this
This guide reads a CSV file and, for each row, creates a shared mailbox in Exchange Online and grants the people you list Full Access and Send As on it. The purpose is to onboard many shared mailboxes (team@, support@, invoices@) in one pass instead of clicking through the admin center dozens of times.
Privileges. You connect to Exchange Online with an account that holds the Exchange Administrator role (or Global Administrator). There is no local root/sudo involved — this runs from your workstation against the service — but the connecting account is a privileged one, so treat the session accordingly. You need the ExchangeOnlineManagement PowerShell module installed. Windows PowerShell 5.1 and PowerShell 7 both work.
Test first. Read the script before you run it. The CSV is the dangerous part: a typo in an alias or a wrong recipient in the Send-As column grants someone the ability to send mail as a shared address. Run it once against a single test row with $DryRun = $true (which turns on -WhatIf everywhere) and read the simulated output before you commit. Do not paste this at a live tenant blind.
What it changes. It creates new mailbox objects and adds delegation. Creating a shared mailbox is additive and low-risk. Granting Full Access and Send As is a real security change — the delegates can read the mailbox and send as it. Everything here is reversible (I show the undo commands at the end), and Remove-Mailbox in Exchange Online soft-deletes, so a mailbox is recoverable for roughly 30 days — but treat permission grants with the caution any access grant deserves.
What I assume
- Exchange Online (Microsoft 365), not on-premises Exchange. On-prem you'd use the on-server Exchange Management Shell; the mailbox cmdlets are similar but Send As is handled with
Add-ADPermissionthere, notAdd-RecipientPermission. - You have an accepted domain (
example.com) already configured in the tenant. - The delegate accounts already exist as licensed users.
Install the module and connect
# One-time install (current user scope needs no elevation)
Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser
# Connect - opens a browser sign-in for your admin account
Connect-ExchangeOnline -UserPrincipalName admin@example.com
If your organisation requires it, Connect-ExchangeOnline also supports modern auth with MFA out of the box — the browser prompt handles it.
Build the CSV
Create a file, say C:\path\to\shared-mailboxes.csv. Replace every value with your own. Separate multiple delegates in a single cell with a semicolon:
DisplayName,Alias,PrimarySmtpAddress,FullAccessUsers,SendAsUsers
Support Team,support,support@example.com,alice@example.com;bob@example.com,alice@example.com
Invoices,invoices,invoices@example.com,carol@example.com,carol@example.com
- DisplayName — what shows in the GAL.
- Alias — the mail nickname; also what the script uses as the identity for later commands.
- PrimarySmtpAddress — the address the mailbox sends and receives as.
- FullAccessUsers / SendAsUsers — semicolon-separated UPNs. Leave a cell blank if you don't want that permission.
The script
Save this as New-SharedMailboxes.ps1. It is safe by default: $DryRun starts at $true, so nothing changes until you deliberately flip it.
# Requires an active Connect-ExchangeOnline session as Exchange Administrator.
$csvPath = "C:\path\to\shared-mailboxes.csv" # <-- your CSV
$DryRun = $true # set to $false to apply for real
$rows = Import-Csv -Path $csvPath
foreach ($row in $rows) {
# --- Create the shared mailbox (skip if the alias already exists) ---
$existing = Get-Mailbox -Identity $row.Alias -ErrorAction SilentlyContinue
if ($existing) {
Write-Warning "Mailbox '$($row.Alias)' already exists - skipping creation."
}
else {
New-Mailbox -Shared -Name $row.DisplayName -DisplayName $row.DisplayName `
-Alias $row.Alias -WhatIf:$DryRun
if ($row.PrimarySmtpAddress) {
# Set the sending/receiving address explicitly
Set-Mailbox -Identity $row.Alias `
-PrimarySmtpAddress $row.PrimarySmtpAddress -WhatIf:$DryRun
}
}
# --- Full Access (AutoMapping adds the box to Outlook automatically) ---
foreach ($user in ($row.FullAccessUsers -split ';' | Where-Object { $_.Trim() })) {
Add-MailboxPermission -Identity $row.Alias -User $user.Trim() `
-AccessRights FullAccess -InheritanceType All -AutoMapping $true `
-WhatIf:$DryRun
}
# --- Send As ---
foreach ($user in ($row.SendAsUsers -split ';' | Where-Object { $_.Trim() })) {
Add-RecipientPermission -Identity $row.Alias -Trustee $user.Trim() `
-AccessRights SendAs -Confirm:$false -WhatIf:$DryRun
}
}
A note on the dry run: with $DryRun = $true, New-Mailbox is only simulated, so the mailbox doesn't actually exist yet — the permission lines that follow will report that they can't find it. That is expected during the dry run. Read the New-Mailbox "What if:" lines to confirm the names and aliases are right, then set $DryRun = $false and run again to create the mailboxes and apply permissions for real.
A couple of choices worth knowing about:
-AutoMapping $truemakes Outlook auto-add the shared mailbox for anyone with Full Access. Set it to$falseif you'd rather users add it manually — useful for mailboxes with a lot of delegates.- If you want a delegate's replies to land in the shared mailbox's Sent Items (not their own), set
Set-Mailbox -Identity <alias> -MessageCopyForSentItemsEnabled $true. I left it out of the loop to keep the standard path clean; add it if your team expects it.
Verify it worked
List the shared mailboxes you just created:
Get-Mailbox -RecipientTypeDetails SharedMailbox |
Select-Object DisplayName, Alias, PrimarySmtpAddress
Check delegation on one mailbox. Full Access first — filter out the built-in SELF entry so you only see the grants you added:
Get-MailboxPermission -Identity support |
Where-Object { $_.User -notlike "NT AUTHORITY\SELF" } |
Select-Object User, AccessRights
Get-RecipientPermission -Identity support |
Select-Object Trustee, AccessRights
Send As and Full Access changes can take a few minutes to fully propagate through the service before Outlook reflects them; the cmdlets above show the state immediately regardless.
Undo
Remove a single Full Access grant:
Remove-MailboxPermission -Identity support -User alice@example.com `
-AccessRights FullAccess -InheritanceType All -Confirm:$false
Remove a Send As grant:
Remove-RecipientPermission -Identity support -Trustee alice@example.com `
-AccessRights SendAs -Confirm:$false
Remove a mailbox created in error:
Remove-Mailbox -Identity support -Confirm:$false
Remove-Mailbox soft-deletes in Exchange Online — the mailbox sits recoverable for about 30 days, so this is not an instant point of no return, but don't rely on that as a workflow. To reverse a whole batch, drive the same commands from your CSV with a foreach loop the way the create script does.
When you're done, close the session cleanly:
Disconnect-ExchangeOnline -Confirm:$false
For exact parameters and any options I didn't cover, check Microsoft Learn for New-Mailbox, Add-MailboxPermission, and Add-RecipientPermission — they list every switch these cmdlets accept, and the shared-mailbox behaviour is documented under Exchange Online mailbox management.
Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.
More about the author →Was this article helpful?
Tap a star — no sign-in needed.
Be the first to rate this article.
