All scripts
Automation 467
Bulk Grant Shared Mailbox Permissions From a CSV File
Reads a CSV of shared mailbox and user pairs and grants Full Access and Send As permissions in Exchange Online, so a batch of access requests does not mean running the same two commands over and over.
Grant-SharedMailboxPermissionFromCsv.ps1
<#
.SYNOPSIS
Grants Full Access and Send As permissions on shared mailboxes in bulk from a CSV file.
.DESCRIPTION
Reads a CSV file listing shared mailbox addresses and the users who need access, then
applies Full Access and Send As permissions in Exchange Online for each row. Useful when
onboarding a batch of users to shared mailboxes at once instead of running Add-MailboxPermission
and Add-RecipientPermission one pair at a time. Skips a row and logs a warning if the mailbox
or user cannot be found, so one bad row does not stop the rest of the batch.
.PARAMETER CsvPath
Path to a CSV file with columns: SharedMailbox, User
.PARAMETER AutoMapping
If set, leaves Outlook auto-mapping on (the default). Pass -AutoMapping:$false to grant
access without the mailbox automatically showing up in the user's Outlook profile.
.EXAMPLE
.\Grant-SharedMailboxPermissionFromCsv.ps1 -CsvPath .\mailbox-access.csv
.EXAMPLE
.\Grant-SharedMailboxPermissionFromCsv.ps1 -CsvPath .\mailbox-access.csv -AutoMapping:$false
.AUTHOR
Shehryar Hassan
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$CsvPath,
[Parameter(Mandatory = $false)]
[bool]$AutoMapping = $true
)
if (-not (Get-Command Get-Mailbox -ErrorAction SilentlyContinue)) {
Write-Error "Exchange Online PowerShell module isn't connected. Run Connect-ExchangeOnline first."
return
}
if (-not (Test-Path $CsvPath)) {
Write-Error "CSV file not found at path: $CsvPath"
return
}
$rows = Import-Csv -Path $CsvPath
if (-not $rows -or $rows.Count -eq 0) {
Write-Warning "CSV file is empty, nothing to process."
return
}
$results = foreach ($row in $rows) {
$mailbox = $row.SharedMailbox
$user = $row.User
if ([string]::IsNullOrWhiteSpace($mailbox) -or [string]::IsNullOrWhiteSpace($user)) {
Write-Warning "Skipping row with a blank SharedMailbox or User value."
continue
}
$mailboxObj = Get-Mailbox -Identity $mailbox -ErrorAction SilentlyContinue
if (-not $mailboxObj) {
Write-Warning "Skipping $user, mailbox not found: $mailbox"
continue
}
$userObj = Get-Recipient -Identity $user -ErrorAction SilentlyContinue
if (-not $userObj) {
Write-Warning "Skipping $mailbox, user not found: $user"
continue
}
try {
Add-MailboxPermission -Identity $mailbox -User $user -AccessRights FullAccess -InheritanceType All -AutoMapping $AutoMapping -ErrorAction Stop | Out-Null
Add-RecipientPermission -Identity $mailbox -Trustee $user -AccessRights SendAs -Confirm:$false -ErrorAction Stop | Out-Null
[PSCustomObject]@{
SharedMailbox = $mailbox
User = $user
Status = "Granted"
}
}
catch {
Write-Warning "Failed on $mailbox for $user, $($_.Exception.Message)"
[PSCustomObject]@{
SharedMailbox = $mailbox
User = $user
Status = "Failed"
}
}
}
$results | Format-Table -AutoSize
$failedCount = ($results | Where-Object { $_.Status -eq "Failed" }).Count
if ($failedCount -gt 0) {
Write-Warning "$failedCount row(s) failed. Check the warnings above for details."
}
else {
Write-Host "All rows processed successfully."
}
Read it before you run it, and test in a safe tenant first.