Tracking the availability of other people in your organization has been a problem for calendar management systems since the early 1980s. Microsoft’s solution since the days of Schedule+ has been to publish free and busy information from user calendars to allow other people to see if someone is available when setting up a meeting. The information is presented in time slots by apps like Outlook’s scheduling assistant (Figure 1). Good as it is to see time slots, it’s limited access to view calendar information.
Figure 1: The Outlook for Windows scheduling assistant displays free and busy information for attendees
As you can see from Figure 1, more insight is available about the availability of some people than others. The ability to view details of someone’s availability depends on the permission you have to their calendar (private items like the one shown are exceptions to the rule).
Default Permission for the Default User
The default permission used within Exchange Online is AvailabilityOnly. This is one of two special permissions available for the calendar folder and it allows other users to see a graphic representation of when someone is available. However, this permission doesn’t allow you to see details of what you’re up to like the Procurement call highlighted in Figure 1. The ability to see this information is governed by the other special permission (LimitedDetails), which allows people to see the time slot reserved, the title, location, and its time status (busy, tentative, out of office, etc.).
To make sure that everyone within an organization has at least limited visibility of each other’s calendar, Exchange Online assigns the AvailabilityOnly permission to a special user called Default. People see this assignment in a slightly different manner because clients present the information in a more user-friendly manner. For instance, OWA refers to the Default user as People in my organization and interprets the permission as Can view when I’m busy (Figure 2).
Figure 1: OWA displays calendar sharing permissions, including the default for the organization
Figure 2 also shows that individuals can assign specific permissions to different users to allow them to have custom access to a calendar. This is what happens when people need to manage the calendar for other users.
Using PowerShell to Set a New Default Permission
You’re all set and there’s no more to do if you’re happy with everyone seeing calendar slots instead of appointment details. Things become more complicated if you decide that it would be better if everyone could see more information. There will always be exceptions where you want to protect calendars against casual browsing (“I wonder what the CEO is doing today…”), but the idea is to allow general access as a default.
It sounds like this is something that should be handled by a setting in Exchange’s organization configuration to control the default permission created for new calendars. Unhappily, no such setting exists, and anyway, if it did, you’d still have the problem of retrofitting a new default permission on existing calendars, including the need to respect customized permissions set for some calendars.
Which brings us to PowerShell. The Set-MailboxFolderPermission cmdlet in the Exchange Online management module is the key to assigning a new default permission to existing mailboxes and for subsequently-created mailboxes. To set a new default calendar permission, we need a script to:
Use a method to indicate if a mailbox has already been updated or should be ignored because it has custom permissions.
Find target mailboxes.
Run Set-MailboxFolderPermission against each mailbox.
Update the mailboxes so that they are not processed the next time the script runs.
Custom mailbox attributes are a good choice for storing an indication that a mailbox has updated permissions. It’s easier and faster to check a custom attribute because these attributes support server-side filtering and don’t need to check the actual permissions in place on mailboxes. With that in mind, I elected to use CustomAttribute13 to store “Open” if the mailbox is updated with a new default permission and “Blocked” if a mailbox is to be ignored.
An admin (or the user if they know how to run Set-Mailbox in PowerShell) would set the attribute to Blocked if they don’t want their availability setting updated. You’d probably have a set of well-known mailboxes to block and would process them at one time. For instance, let’s assume you have a CSV file containing the user principal names of mailboxes to block. The code would be something like:
The prototype code to find and update the availability setting for mailboxes (I’ve opted to process user and room mailboxes) with the new default calendar permission is shown below. You’ll note that I have some lines to deal with local language values of the name for the calendar folder. You will have to uncomment and use this line (it’s reasonably expensive to run Get-ExoMailboxFolderStatistics to find the calendar folder and extract its name) if your organization includes users who run Outlook or OWA in non-English languages. Some experimentation is required!
# Find mailboxes that we have not yet reset the default sharing view
[array]$Mbx = Get-ExoMailbox -RecipientTypeDetails UserMailbox, RoomMailbox -ResultSize Unlimited -Filter {CustomAttribute13 -ne "Open" -and CustomAttribute13 -ne "Blocked"}
$CalendarName = "Calendar" # English language calendar folder
ForEach ($M in $Mbx) {
Write-Host "Processing" $M.DisplayName
# You can hard-code the calendar name (above) or try and find a local language value. This is one way to look for local values...
# $CalendarName = (Get-ExoMailboxFolderStatistics -Identity $M.UserPrincipalName -FolderScope Calendar |?{$_.FolderType -eq "Calendar"}).Name
# Either way, you need to end up with a valid calendar folder reference - like Tony.Redmond@office365itpros.com:\Calendar
$CalendarFolder = $M.UserPrincipalName + ":\" + $CalendarName
Set-MailboxFolderPermission -Identity $CalendarFolder -User Default -AccessRights LimitedDetails
Set-Mailbox -Identity $M.ExternalDirectoryObjectId -CustomAttribute13 "Open"
}
The first time you run the script, it will take plenty of time to process mailboxes (expect each mailbox to take between 2-3 seconds to be updated). Later, fewer mailboxes will need updating and the script will complete faster. To be sure that new mailboxes get the new default permission, you can run the script periodically, perhaps as a scheduled task or using Azure Automation.
Figure 3 shows the effect of the change. Availability information is visible for all participants.
Figure 3: Outlook’s scheduling assistant can reveal more information after mailboxes are updated
The updated access to free and busy information will be picked up by any client which consumes this data, such as the scheduling assistant in the Teams calendar app (Figure 4).
The scheduling assistant in the Teams calendar app displays availability information for a mailbox
Need for Calendar Option in Mailbox Plan
You can argue that Microsoft should make it easier for organizations to select and apply a default calendar permission. However, you’d still probably have to run some PowerShell to adjust the permission for selected mailboxes, like those who need to preserve confidentiality. Still, it would be nice if Microsoft added the default calendar permission to mailbox plans so that new mailboxes would receive whatever permission deemed suitable by an organization. That would be a good step forward.
Learn more about how Office 365 really works on an ongoing basis by subscribing to the Office 365 for IT Pros eBook. Our monthly updates keep subscribers informed about what’s important across the Office 365 ecosystem.
12 Replies to “How to Allow Exchange Online Users to See Availability Details in Other Calendars”
Hello Tony!
Great article (as always)!
I’d add two things to the above:
1) For those of us that work in the mutinational (or non-english) environments, when end user mailbox locale can be anything, calendar folder name doesn’t have to be ‘Calendar’. To work around that i’ts possible to retrieve the folders of calendar type using
So instead
$CalendarFolder = $M.Alias + “:\Calendar”
we would have sth like
$CalendarFolder = “$($M.Alias):\$($calendarfoldername)”
2) Despite the default (system) calendar, user can also create custom folders of calendar type. Unfortunately, the ‘foldertype’ of such user-created calendars is not ‘Calendar’. It is ‘User Created’ (as any other folder created by user in mailbox). If there was a need to deal with all calendar-type folders (including user created), the cmdlet should be
Thanks for reminding me that English is not the b-all and end-all of IT. You’re 100% right to use a language dependent version of the calendar folder name. I made some changes to the code you suggested (using the FolderScope is faster as it avoids the need to process all folders in the mailbox). See what you think.
Hi Tony, thanks for script. Any assistance with this would be greatly appreciated.
This is exactly what I need, but with one issue. I need it in an AAD Synced tenant.
– I am accessing it from a delegated admin tenant.
– users are syncing from on premise
– Microsoft Business Premium licenses with password write back ability
Issue:
I cant figure out a way to apply the -customattribute to an on premise AD object while its processing the permission update to the calendar. Is there a way to run a script hybrid from the local DC so it will make attribute edits? There are only two users that need to be “blocked” so I could manually add those in AD. If I run the permission change and don’t add an attribute, I’m guessing it will try to rewrite them every time it runs from Azure – if I automate it.
— Error when updating an individuals attribute–
Error executing request. An Azure Active Directory call was made to keep object in sync between Azure Active Directory and Exchange Online. However, it failed. Detailed error message: Unable to update the specified properties for on-premises mastered Directory Sync objects or objects currently undergoing migration. DualWrite (Graph) RequestId: 11f87bc3-4dde-4bbc-a6e2-ba27dc17374e The issue may be transient and please retry a couple of minutes later. If issue persists, please see exception members for more information.
The two users you want to block have on-premises mailboxes? If so, you can only manage those attributes from an on-premises server. You’ll need to use Exchange Server PowerShell to update the custom attribute and synchronize it with the cloud.
Hi Tony! Thanks for you post!
Do you know if it is possible prevent users to modify free/busy options by itself? I see that many users hide their Free/Busy information completely by setting the permission to None. And I want to avoid that. Thanks! Fernando
I don’t believe an option exists to turn off the option for users to control sharing of free/busy information for their calendars. But you could run the script periodically to reset calendars…
{"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}
Hello Tony!
Great article (as always)!
I’d add two things to the above:
1) For those of us that work in the mutinational (or non-english) environments, when end user mailbox locale can be anything, calendar folder name doesn’t have to be ‘Calendar’. To work around that i’ts possible to retrieve the folders of calendar type using
$calendarFolderName=(get-exomailboxfolderstatistics -Identity $m.alias | ? {$_.foldertype -eq ‘Calendar’}).name
and then use this $calendarFolderName variable.
So instead
$CalendarFolder = $M.Alias + “:\Calendar”
we would have sth like
$CalendarFolder = “$($M.Alias):\$($calendarfoldername)”
2) Despite the default (system) calendar, user can also create custom folders of calendar type. Unfortunately, the ‘foldertype’ of such user-created calendars is not ‘Calendar’. It is ‘User Created’ (as any other folder created by user in mailbox). If there was a need to deal with all calendar-type folders (including user created), the cmdlet should be
$calendarFoldersNames=(get-exomailboxfolderstatistics -Identity $m.alias | ? {$_.containerClass -eq ‘IPF.Appointment’}).name
Then this variable should be treated as an array and put into loop accordingly.
I hope this adds some value to the above.
Thanks for reminding me that English is not the b-all and end-all of IT. You’re 100% right to use a language dependent version of the calendar folder name. I made some changes to the code you suggested (using the FolderScope is faster as it avoids the need to process all folders in the mailbox). See what you think.
Hi Tony, thanks for script. Any assistance with this would be greatly appreciated.
This is exactly what I need, but with one issue. I need it in an AAD Synced tenant.
– I am accessing it from a delegated admin tenant.
– users are syncing from on premise
– Microsoft Business Premium licenses with password write back ability
Issue:
I cant figure out a way to apply the -customattribute to an on premise AD object while its processing the permission update to the calendar. Is there a way to run a script hybrid from the local DC so it will make attribute edits? There are only two users that need to be “blocked” so I could manually add those in AD. If I run the permission change and don’t add an attribute, I’m guessing it will try to rewrite them every time it runs from Azure – if I automate it.
— Error when updating an individuals attribute–
Error executing request. An Azure Active Directory call was made to keep object in sync between Azure Active Directory and Exchange Online. However, it failed. Detailed error message: Unable to update the specified properties for on-premises mastered Directory Sync objects or objects currently undergoing migration. DualWrite (Graph) RequestId: 11f87bc3-4dde-4bbc-a6e2-ba27dc17374e The issue may be transient and please retry a couple of minutes later. If issue persists, please see exception members for more information.
The two users you want to block have on-premises mailboxes? If so, you can only manage those attributes from an on-premises server. You’ll need to use Exchange Server PowerShell to update the custom attribute and synchronize it with the cloud.
Great article, just what I was looking for. Thumbs up for the good work and sharing with comunity
$Mbx = Import-CSV c:\Temp\SomeTempFile.csv
ForEach ($M in $Mbx) { Set-Mailbox -Identity $M.UserPrincipalName-CustomAtrribute13 “Blocked”}
Typo in: “Atrribute”
Thanks. Fixed!
Hi Tony! Thanks for you post!
Do you know if it is possible prevent users to modify free/busy options by itself? I see that many users hide their Free/Busy information completely by setting the permission to None. And I want to avoid that. Thanks! Fernando
I don’t believe an option exists to turn off the option for users to control sharing of free/busy information for their calendars. But you could run the script periodically to reset calendars…
Thanks Tony!!