Script updated several times between 2020-02-10 and 2020-03-5 to tweak different aspects when using the new Exchange Online powershell cmdlets that is currently in preview but is generally much more efficient for this task along with code readability and documentation updates. I received confirmation that getting mailbox folder permissions will return something more than a displayname and that we will eventually be able to use the FolderId instead of folder path since folder paths have problems with backslashes and other foreign characters.
Problem:
You need to document, monitor, and manage mailbox and mailbox folder permissions across an entire O365 tenant. When trying with PowerShell, we can't even pipe Get-Mailbox to Get-MailboxFolderStatistics to Get-MailboxFolderPermissions. When attempting these things in PowerShell, it has traditionally been extremely slow.
Source:
This gets weird when it comes to mailbox permissions (FullAccess, SendAs, SendOnBehalf), Calendar Permissions, and Mailbox Folder Permissions (tons of options) and HR wants you to verify that an employee does not have access to that mailbox even if they are rehired later. The old Exchange PowerShell commands transmitted too much data so the results of pulling large numbers of mailboxes and folders were too slow to be feasible. One could search the Unified Audit Log for folder permissions changes but that doesn't give the baseline.
Resolution:
You can download my interactive script that does the retrieval, partial retrieval, and resuming here:
https://github.com/hornerit/powershell/blob/master/Get-O365MailboxPermissionsAcrossTenant.ps1. I have updated several times and maintain updates on it for tweaking special scenarios and will update once the new cmdlets offer different data.
No matter how you try, you will need to make a local copy of the mailbox/folder permissions somewhere because attempting to query this information is too time-intensive to be useful. With the advent of the new Exchange Online V2 powershell cmdlets, the performance of getting the mailbox, mailbox permissions, and folder permissions is not nearly as horrible as it was before.
Make sure you have an Exchange Admin account and you have installed the new Exchange module (Install-Module ExchangeOnlineManagement) - you can connect and even support MFA using Connect-ExchangeOnline and all the old ExchangeOnline commands still work. From there, the more straightforward version for mailbox permissions for 5 mailboxes is something like this:
Get-EXOMailbox -ResultSize 5 |
Get-EXOMailboxPermissions |
Where-Object {
$_.IsInherited -eq $false -and
$_.Deny -eq $false
} |
Select-Object Identity,User,@{Label="AccessRights";Expression={$_.AccessRights -join ","}} |
Export-CSV -Path "C:\someFolder\MailboxPermissions.csv" -Append
Or for the Mailbox Folder permissions (for 5 mailboxes):
Get-EXOMailbox -ResultSize 5 |
Get-EXOMailboxFolderStatistics |
Where-Object {
$_.SearchFolder -eq $false -and
@("Root","Calendar","Inbox","User Created") -contains $_.FolderType -and
(@("IPF.Note","IPF.Appointment",$null) -contains $_.ContainerClass -or $_.Name -eq "Top of Information Store")
} |
Select-Object @{Label="Identity";Expression={
if($_.Name -eq "Top of Information Store"){
$_.Identity.Substring(0,$_.Identity.IndexOf("\"))
} else {
$_.Identity.Substring(0,$_.Identity.IndexOf("\"))+':'+$_.Identity.Substring($_.Identity.IndexOf("\")).Replace([char]63743,"/")
}
}} |
Get-EXOMailboxFolderPermissions |
Where-Object { $_.AccessRights -ne "None" } |
Select-Object Identity,FolderPath,User,@{Label="AccessRights";Expression={$_.AccessRights -join ","}} |
Export-CSV -Path "C:\someFolder\MailboxFolderPermissions.csv" -Append
Or combine them effectively (for 5 mailboxes):
Get-EXOMailbox -ResultSize 5 |
Tee-Object -Variable "myMailboxes" |
Get-EXOMailboxPermissions |
Where-Object {
$_.IsInherited -eq $false -and
$_.Deny -eq $false
} |
Select-Object Identity,User,@{Label="AccessRights";Expression={$_.AccessRights -join ","}} |
Export-CSV -Path "C:\someFolder\MailboxPermissions.csv" -Append
$myMailboxes |
Get-EXOMailboxFolderStatistics |
Where-Object {
$_.SearchFolder -eq $false -and
@("Root","Calendar","Inbox","User Created") -contains $_.FolderType -and
(@("IPF.Note","IPF.Appointment",$null) -contains $_.ContainerClass -or $_.Name -eq "Top of Information Store")
} |
Select-Object @{Label="Identity";Expression={
if($_.Name -eq "Top of Information Store"){
$_.Identity.Substring(0,$_.Identity.IndexOf("\"))
} else {
$_.Identity.Substring(0,$_.Identity.IndexOf("\"))+':'+$_.Identity.Substring($_.Identity.IndexOf("\")).Replace([char]63743,"/")
}
}} |
Get-EXOMailboxFolderPermissions |
Where-Object { $_.AccessRights -ne "None" } |
Select-Object Identity,FolderPath,User,@{Label="AccessRights";Expression={$_.AccessRights -join ","}} |
Export-CSV -Path "C:\someFolder\MailboxFolderPermissions.csv" -Append
If you are a PowerShell person, you may notice 3 oddities with my combining:
- I did not use a foreach-object
- I used a Tee-Object
- There are a ton of filters and some weird Select-Object stuff going on in the middle with MailboxFolderStatistics
Foreach-Object breaks the new Exchange Online cmdlets' multithreading (speed) so I can't say Get-Mailboxes | foreach-object { Get-Mailboxpermissions; Get-MailboxFolderPermissions} ... It was actually faster to run each command separately. To keep from having to retrieve the mailboxes twice, Tee-Object allows you to take the output from the Get-EXOMailboxes, store it in a variable, and use it a second time once your first full command is over - and it does NOT break the PowerShell pipeline so speed stays happy!
The reason for all the weirdness with Get-EXOMailboxFolderStatistics is that when someone tries Get-MailboxFolderPermission but only supplies the email address of the mailbox, it only retrieves the folder permissions for the folder "Top of Information Store". So one might think - I will just use the Get-EXOMailboxFolderStatistics to get the list of all the folders within the mailbox and send THAT over to the Get-EXOMailboxFolderPermission...well, it fails miserably because the Identity of the folder from Statistics looks like mailbox@domain.com\NameOfFolder but the MailboxFolderPermissions cmdlet is expecting mailbox@domain.com:\NameOfFolder\NameofSubfolder. Right now, the FolderID is not supported in the new cmdlets, only the path. Either way, the lack of nice pairing is stupid, but fixable by Selecting the identity of folders and then fixing the name and then passing that along the pipeline. The other filters I throw in there are so that I don't ask about permissions for every dumb folder in a mailbox that might be some Teams, Yammer, etc. system folder and subfolder but I do want Calendars.