Make Sure The Right People Access Your Exchange Online Mailboxes
One of the recommendations made in the Office 365 for IT Pros eBook is that tenant administrators should conduct periodic reviews of permissions assigned to mailboxes to ensure that the right people (other than the mailbox owners) have access, perhaps by creating an Exchange Online mailbox permissions report. A recent request in the Microsoft Technical Community prompted me to look at the situation again to make sure that our advice was still accurate (it is).
Scripting a Report
I responded to the original question with some quick and dirty PowerShell but decided that a better job could be done. If you use the Get-MailboxPermission cmdlet to examine permissions on an Exchange Online mailbox, several types exist:
The mailbox owner (if you’re unsure, run Get-MailboxPermission with the -Owner parameter to see this entry).
Permissions needed by Exchange Online to access the mailbox for different purposes, such as removing items during retention policy processing. These entries appear like EXOForest\Organization Management, where “EXOForest” is the name of the Exchange Online forest hosting the mailbox.
An entry for “JitUsers” (Just in time access) assigned to Microsoft support personnel when they need access to the mailbox.
System entries like NT AUTHORITY\System and NT AUTHORITY\NETWORK SERVICE.
For the purpose of this exercise we don’t care about these permissions because they exist on all mailboxes. What we’re looking for are delegated permissions used to grant non-owner accounts access to the mailbox. Vasil Michev, our esteemed technical editor, has a script in the TechNet Gallery to report non-standard permissions, but there’s always room for another PowerShell answer to a problem.
My script (the full version of the Exchange Online mailbox permissions report is available on GitHub) selects user and shared mailboxes (those most likely to have extra permissions). For each mailbox, we extract the permissions and look for those assigned to other Office 365 accounts. We store details of these permissions into a list that is written out to a CSV file after all mailboxes are processed. Here’s the basic idea:
# Quick and simple script to generate a report of non-standard permissions applied to Exchange Online user and shared mailboxes
# Needs to be connected to Exchange Online PowerShell with an administrative account to run
CLS
Write-Host "Fetching mailboxes"
[array]$Mbx = Get-Mailbox -RecipientTypeDetails UserMailbox, SharedMailbox -ResultSize Unlimited | Select DisplayName, UserPrincipalName, RecipientTypeDetails
If ($Mbx.Count -eq 0) { Write-Error "No mailboxes found. Script exiting..." -ErrorAction Stop }
# We have some mailboxes, so we can process them...
CLS
$Report = [System.Collections.Generic.List[Object]]::new() # Create output file
$ProgressDelta = 100/($Mbx.count); $PercentComplete = 0; $MbxNumber = 0
ForEach ($M in $Mbx) {
$MbxNumber++
$MbxStatus = $M.DisplayName + " ["+ $MbxNumber +"/" + $Mbx.Count + "]"
Write-Progress -Activity "Processing mailbox" -Status $MbxStatus -PercentComplete $PercentComplete
$PercentComplete += $ProgressDelta
$Permissions = Get-MailboxPermission -Identity $M.UserPrincipalName | ? {$_.User -Like "*@*" }
If ($Null -ne $Permissions) {
# Grab each permission and output it into the report
ForEach ($Permission in $Permissions) {
$ReportLine = [PSCustomObject] @{
Mailbox = $M.DisplayName
UPN = $M.UserPrincipalName
Permission = $Permission | Select -ExpandProperty AccessRights
AssignedTo = $Permission.User
MailboxType = $M.RecipientTypeDetails }
$Report.Add($ReportLine) }
}
}
$Report | Sort -Property @{Expression = {$_.MailboxType}; Ascending= $False}, Mailbox | Export-CSV c:\temp\MailboxPermissions.csv -NoTypeInformation
Write-Host "All done." $Mbx.Count "mailboxes scanned. Report of non-standard permissions available in c:\temp\MailboxPermissions.csv"
The CSV file is stored by user mailbox and then shared mailbox (you must use a calculated expression to sort by multiple properties when the first property is sorted in descending order).
As you can see from Figure 1, the Exchange Online mailbox permissions report details FullAccess and SendAs permissions assigned to mailboxes. The fact that these permissions exist isn’t an issue by itself as the permissions are usually well-justified. For instance, FullAccess permission is needed by delegates to have full control over a shared or user mailbox (as in the case of Outlook Mobile delegation). However, it’s important to review each assignment to understand if it is still valid and necessary. If not, the permission should be removed.
Figure 1: Reporting mailbox permissions
The Exchange Online mailbox permissions report doesn’t include folder-level permissions assigned by Outlook. These permissions can be reviewed with the Get-MailboxFolderPermission cmdlet. To find all such permissions for a mailbox, you would need to run Get-MailboxFolderStatistics to generate a list of mailbox folders and then check each folder to see if any permissions exist. I’ll cover how to do this in a future post.
For many more examples of using PowerShell to manage Exchange Online and other Office 365 components, subscribe to the Office 365 for IT Pros eBook and find some hidden jewels.
That depends on your organization. One simple approach is to send email to the owner of each mailbox with permissions set to as them if the permissions are still required,.
Is there a way to report if a mail-enabled security group is assigned permissions? The script appears to report if a user is assigned, but not if a group is.
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
Once you have the data, what’s a good process for getting it distributed to users then collecting their responses?
That depends on your organization. One simple approach is to send email to the owner of each mailbox with permissions set to as them if the permissions are still required,.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\md\permission.ps1:27 char:10
+ $Report.Add($ReportLine) }
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\md\permission.ps1:27 char:10
+ $Report.Add($ReportLine) }
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At C:\md\permission.ps1:27 char:10
+ $Report.Add($ReportLine) }
+ ~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
We are getting this error when we use this script.
Looks like you’re getting no results when you run a call. Step through the script command by command and find out where the problem is.
Is there a way to report if a mail-enabled security group is assigned permissions? The script appears to report if a user is assigned, but not if a group is.
The script has a line to filter out anything but users:
$Permissions = Get-MailboxPermission -Identity $M.UserPrincipalName | ? {$_.User -Like “*@*” }
Use a different filter to include whatever you want to report, like:
$Permissions = Get-MailboxPermission -Identity Cservices | ? {$_.User -ne “NT AUTHORITY\SELF”}
But if you include mail-enabled security groups, you’ll need to change the code to report the permissions because the current code assumes a mailbox.