Key Takeaways
- The connection method in every old guide is dead.
New-PSSessionagainst Exchange Online stopped working in 2023. It’sConnect-ExchangeOnlinefrom the V3 module now, and there’s noRemove-PSSessionto clean up afterwards. Add-only works once. If the user already has any entry on that folder,Add-MailboxFolderPermissionthrows.Set-MailboxFolderPermissionis what you want for a change. Most scripts that “randomly fail” are hitting this.- The folder isn’t always called Calendar. The identity path uses the localised folder name, so
:\Calendarfails on a mailbox created in German or French. Resolve it withGet-MailboxFolderStatisticsinstead of hard-coding. - Delegate and Editor aren’t the same thing. Editor can write to the calendar. A delegate also receives the meeting invites. That’s the
-SharingPermissionFlagsparameter, and it’s Exchange Online only.
Exchange has never given administrators a sane GUI for calendar permissions. You can set them per-mailbox in the Exchange admin center, one user at a time, clicking through a modal. For anything involving more than about three mailboxes, that stops being viable, and you end up in PowerShell whether you wanted to be there or not.
This is the reference I keep for that. It’s Exchange Online / Microsoft 365 specific — on-premises Exchange shares most of the cmdlets but not the delegate flags.
Connecting, the way that still works
Most of the guides you’ll find for this were written between 2016 and 2019 and open with New-PSSession pointed at outlook.office365.com, followed by Import-PSSession. Don’t bother. Microsoft deprecated the Remote PowerShell protocol for Exchange Online and finished switching it off in 2023. The UseRPSSession escape hatch is gone too.
The replacement is the Exchange Online PowerShell V3 module, which talks to a REST endpoint and exposes the same cmdlet surface:
Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser
Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName [email protected]That opens a browser for modern auth, which means MFA works without the app-password contortions the old module needed. When you’re done:
Disconnect-ExchangeOnline -Confirm:$falseIf you’re scripting this unattended, use certificate-based app-only authentication rather than storing a credential. That’s a different post.
Two terms do most of the work in this post. Exchange Online PowerShell is Microsoft’s remote administration module for Exchange Online, and the V3 module is the current one; the connection method changed in 2023 and most guides still teach the dead one. A mailbox folder permission is an access-control entry attached to one folder inside a mailbox rather than to the mailbox as a whole, which is what makes it possible to share a calendar without exposing mail.
Read before you write
Always look at what’s there first. Get-MailboxFolderPermission takes the same identity syntax as everything else here — MailboxID:\Folder:
Get-MailboxFolderPermission -Identity [email protected]:\CalendarTypical output on a mailbox nobody’s touched:
FolderName User AccessRights SharingPermissionFlags
---------- ---- ------------ ----------------------
Calendar Default {AvailabilityOnly}
Calendar Anonymous {None}Two entries matter here and neither is a real person:
- Default — everyone in the tenant who isn’t listed explicitly. Out of the box it’s
AvailabilityOnly: free/busy blocks, no subject, no location. - Anonymous — external users hitting the published calendar URL. Leave this at
Noneunless you have a specific reason.
If you’re on a large tenant, Get-EXOMailboxFolderPermission is the REST-native equivalent and is noticeably faster across a lot of mailboxes.
The localised folder name trap
This is the one that costs an afternoon. The identity path takes the folder’s display name, in the language the mailbox was created in. A mailbox provisioned with a German locale has :\Kalender, not :\Calendar. French gets :\Calendrier. Your script works for 400 users and throws on the other 12.
Don’t hard-code it. Ask the mailbox what its calendar is called:
$mbx = "[email protected]"
$cal = (Get-MailboxFolderStatistics -Identity $mbx -FolderScope Calendar |
Where-Object { $_.FolderType -eq "Calendar" }).Name
Get-MailboxFolderPermission -Identity "$($mbx):\$cal"FolderType is the language-independent property. Filtering on it rather than on Name is what makes this survive contact with an international tenant.
The permission roles, and which ones are calendar-only
A delegate is distinct from a user who merely holds write access: both can edit the calendar, but only a delegate also receives the meeting invitations addressed to the mailbox owner. That difference is carried by -SharingPermissionFlags and is the single most common source of “the permission is set but it still is not working”.
Microsoft documents the roles as bundles of individual rights. These are the ones worth knowing, from the Add-MailboxFolderPermission reference:
| Role | What it actually allows |
|---|---|
AvailabilityOnly | Free/busy only. Calendar folders only. The default. |
LimitedDetails | Free/busy plus subject and location. Calendar folders only. |
Reviewer | Read everything. No writes. |
Contributor | Create items, but can’t read existing ones. Rarely what anyone means. |
NonEditingAuthor | Read all, create, delete only their own. |
Author | Read all, create, edit and delete their own. |
Editor | Read, create, edit and delete anything. The usual answer for a shared team calendar. |
Owner | Editor plus subfolders and folder ownership. Hand out sparingly. |
AvailabilityOnly and LimitedDetails exist only for calendar folders — you can’t apply them to an inbox.
Granting, changing, removing
Three cmdlets, and the distinction between the first two is where most scripts break.
First time — the user has no existing entry on that folder:
Add-MailboxFolderPermission -Identity [email protected]:\Calendar `
-User [email protected] -AccessRights ReviewerChanging an existing entry — including anything already covered by Default:
Set-MailboxFolderPermission -Identity [email protected]:\Calendar `
-User [email protected] -AccessRights EditorRun Add- against a user who already has a permission entry and it fails with an “existing permission entry was found” error. This is the single most common reason a bulk script dies a third of the way through. If you don’t know the current state, either check first or just call Set-.
Removing:
Remove-MailboxFolderPermission -Identity [email protected]:\Calendar `
-User [email protected] -Confirm:$falseTo reset the whole tenant’s default visibility to something more useful than free/busy, target Default — it’s a valid -User value:
Set-MailboxFolderPermission -Identity [email protected]:\Calendar `
-User Default -AccessRights LimitedDetailsDelegates are not just Editors
Here’s the distinction that trips people up. Editor lets someone modify the calendar. A delegate also receives the meeting invitations and responses — the thing the mailbox owner usually actually wanted when they asked for “calendar access”.
That’s -SharingPermissionFlags, and per the Microsoft reference it’s Exchange Online only and valid only when -AccessRights is Editor:
Add-MailboxFolderPermission -Identity [email protected]:\Calendar `
-User [email protected] -AccessRights Editor `
-SharingPermissionFlags DelegateAdd private-item visibility on top — note that CanViewPrivateItems has to be combined with Delegate, it isn’t standalone:
Add-MailboxFolderPermission -Identity [email protected]:\Calendar `
-User [email protected] -AccessRights Editor `
-SharingPermissionFlags Delegate,CanViewPrivateItemsOne documented limitation worth knowing before someone raises a ticket about it: the Outlook setting controlling where meeting requests get delivered — owner, delegate, or both — can’t be set from PowerShell at all. Microsoft points you at EWS for that.
Groups work, and they’re usually the right answer
-User accepts mail-enabled security groups, including nested ones. Granting to a group beats looping over its members: membership changes then flow through without anyone re-running a script.
Add-MailboxFolderPermission -Identity [email protected]:\Calendar `
-User "SG-Reception" -AccessRights EditorNote the constraint — it has to be a mail-enabled security group. A plain distribution group has no SID to assign rights to, and a Microsoft 365 group isn’t the same object either.
Doing it in bulk
The pattern I use, with the localisation fix folded in and Set- rather than Add- so re-running it is safe:
$target = "[email protected]"
Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox |
ForEach-Object {
$mbx = $_.PrimarySmtpAddress
$cal = (Get-MailboxFolderStatistics -Identity $mbx -FolderScope Calendar |
Where-Object { $_.FolderType -eq "Calendar" }).Name
try {
Set-MailboxFolderPermission -Identity "$($mbx):\$cal" `
-User $target -AccessRights Reviewer -ErrorAction Stop
Write-Host "OK $mbx"
}
catch {
Write-Warning "FAIL $mbx - $($_.Exception.Message)"
}
}Two things make this survivable on a real tenant. -RecipientTypeDetails UserMailbox keeps you out of room, equipment and shared mailboxes, which usually want different rules. And the try/catch means one broken mailbox logs a warning instead of killing the run at mailbox 340 of 600.
Test it against a handful first. -WhatIf is supported on all three permission cmdlets and costs nothing.
Things that will bite you
- Replication lag. Changes aren’t instant across the service. If a user says it didn’t work, check with
Get-MailboxFolderPermissionbefore changing anything — the permission is often already correct and Outlook simply hasn’t caught up. - Outlook caches aggressively. A cached-mode client can sit on a stale view of a shared calendar for a while. Testing in OWA tells you what the service actually thinks.
- Sub-calendars are separate folders. Permissions on
:\Calendarsay nothing about:\Calendar\Projects. Each one is its own ACL. -SendNotificationToUseris picky. It only applies to calendar folders, and only withAvailabilityOnly,LimitedDetails,ReviewerorEditor.
Sources
- Add-MailboxFolderPermission — role definitions, identity syntax,
SharingPermissionFlags - Set-MailboxFolderPermission
- Remove-MailboxFolderPermission
- About the Exchange Online PowerShell V3 module
- Deprecation of Remote PowerShell in Exchange Online — Exchange Team Blog
Related: Pushing Routes with DHCP Option 121 and 249, How to make a private VPN server in 10 minutes, Making Home Lab. Part 1
