All scripts
Automation 275

Bulk Set Calendar Permissions From a CSV File

Applies calendar folder permissions across many mailboxes at once from a CSV file, updating existing permissions instead of duplicating them. Handy for onboarding an assistant, standing up a shared team calendar, or rolling out one access level across a department.

Set-BulkCalendarPermission.ps1
<#
.SYNOPSIS
Sets calendar folder permissions on a list of mailboxes from a CSV file.

.DESCRIPTION
Reads a CSV of mailbox, delegate user, and access rights, then applies
Add-MailboxFolderPermission or Set-MailboxFolderPermission in Exchange Online
to each mailbox's Calendar folder. Existing permissions for the same user are
updated instead of duplicated. Useful for onboarding a new assistant, setting
up a shared team calendar, or rolling out one standard access level across a
department in one pass instead of clicking through Outlook for each mailbox.

.AUTHOR
Shehryar Hassan

.EXAMPLE
.\Set-BulkCalendarPermission.ps1 -CsvPath .\calendar-permissions.csv

CSV columns expected: Mailbox, User, AccessRights
Example row: shared.sales@contoso.com, jane@contoso.com, Editor
#>

param(
    [Parameter(Mandatory = $true)]
    [string]$CsvPath,

    [switch]$WhatIfOnly
)

if (-not (Get-Module -ListAvailable -Name ExchangeOnlineManagement)) {
    Write-Error "The ExchangeOnlineManagement module is not installed. Run: Install-Module ExchangeOnlineManagement"
    return
}

Import-Module ExchangeOnlineManagement

if (-not (Get-ConnectionInformation)) {
    Connect-ExchangeOnline
}

if (-not (Test-Path $CsvPath)) {
    Write-Error "CSV file not found at $CsvPath"
    return
}

$rows = Import-Csv -Path $CsvPath

if (-not $rows -or $rows.Count -eq 0) {
    Write-Error "No rows found in $CsvPath. Expected columns: Mailbox, User, AccessRights"
    return
}

$validRights = @(
    "Owner", "PublishingEditor", "Editor", "PublishingAuthor", "Author",
    "NonEditingAuthor", "Reviewer", "Contributor", "AvailabilityOnly", "LimitedDetails"
)

$results = @()

foreach ($row in $rows) {
    $mailbox = $row.Mailbox
    $user = $row.User
    $rights = $row.AccessRights

    if ([string]::IsNullOrWhiteSpace($mailbox) -or [string]::IsNullOrWhiteSpace($user) -or [string]::IsNullOrWhiteSpace($rights)) {
        Write-Warning "Skipping row with missing values: $($row | Out-String)"
        continue
    }

    if ($validRights -notcontains $rights) {
        Write-Warning "Skipping $mailbox, unrecognized AccessRights value: $rights"
        continue
    }

    $folder = "$($mailbox):\Calendar"
    $status = "Unknown"

    try {
        $existing = Get-MailboxFolderPermission -Identity $folder -User $user -ErrorAction SilentlyContinue

        if ($WhatIfOnly) {
            $status = if ($existing) { "Would update to $rights" } else { "Would add with $rights" }
        }
        elseif ($existing) {
            Set-MailboxFolderPermission -Identity $folder -User $user -AccessRights $rights -ErrorAction Stop
            $status = "Updated to $rights"
        }
        else {
            Add-MailboxFolderPermission -Identity $folder -User $user -AccessRights $rights -ErrorAction Stop
            $status = "Added with $rights"
        }
    }
    catch {
        $status = "Failed: $($_.Exception.Message)"
    }

    $results += [pscustomobject]@{
        Mailbox      = $mailbox
        User         = $user
        AccessRights = $rights
        Result       = $status
    }
}

$results | Format-Table -AutoSize

$outputPath = Join-Path -Path (Split-Path $CsvPath -Parent) -ChildPath "calendar-permission-results.csv"
$results | Export-Csv -Path $outputPath -NoTypeInformation
Write-Host "Results saved to $outputPath"

Read it before you run it, and test in a safe tenant first.