Report Calendar permissions via the Graph API

Recently, we blogged about Exchange Online’s sharing policies control no longer being enforced. It does seem like Microsoft has switched to the Graph-based methods for handling calendar sharing and even the PowerShell cmdlets behave differently now. And since the Graph has no clue as to what sharing policies are, it all makes sense.

So, if Graph is the “source of truth” now, it begs the question should we also use the Graph methods when it comes to reports on Calendar permissions? We will explore this question in the current article. TL;DR version – we can, but the Graph still has no proper methods to list mailboxes, which complicates things unnecessarily. Let’s dig in.

Prerequisites

As with most things Graph, permissions are the first thing to discuss. Since we aim to report on calendar permissions across the entire organization we need the application permissions model, as best we can do with the delegate permissions model is to cover own and shared resources. Specifically, we need the User.Read.All permission to fetch all user objects as well as to make some guesses as to which of them has a mailbox. For the calendar objects and their associated permissions we need the Calendars.Read scope.

Once you have an application with sufficient permissions configured, use it to obtain an access token for the required calls. A basic example on how to fetch an access token via client secret is included with the sample script attached to this article (line 12-36). You are strongly advised to replace it with your own preferred method of obtaining access tokens non-interactively. DO NOT store any client secrets in code!

LINK TO THE SCRIPT GOES HERE

No good way to fetch mailboxes

Moving on, once an access token is obtained, the script will try to fetch a list of mailboxes for further processing. As there are still no Graph API endpoints to work with mailboxes, we fetch the list of users instead and filter it based on few criteria. First we exclude any guest users, as mailboxes are not supported for such. We also exclude any users with empty mail property, a quick indicator as to whether the object is “known” to Exchange Online. Here’s the query:

"https://graph.microsoft.com/v1.0/users?$filter=userType eq 'member' and mail ne null&$select=id,Mail,MailNickname,ProxyAddresses&$top=999&$count=true"

We cannot filter the list of users based on license/service plan, as some mailbox types do not require an active license. Much the same argument rules out using the /reports endpoint, as the built-in reports do not return all mailboxes. Furthermore, we cannot use filter on the AccountEnabled property. Any of the actually useful properties and methods require a request to be made per user object, greatly increasing the script runtime, so we opt for the easier, albeit a bit inaccurate approach.

One further improvement that can be made to said query is to compare the list of email address (ProxyAddresses) entries associated with the user object against the list of accepted domains for the tenant. This can help exclude mail user recipients, but again does not account for all possible scenarios. For that reason, and the additional permissions needed to fetch the set of domains, the current version of the script does not include said logic. Instead, we rely on subsequent queries against the calendar endpoints to dynamically exclude “invalid” entries. Let us know if you think the logic used can be improved.

Calendar permissions

Once we have the list of (supposed) mailboxes, the rest is easy. A simple call to the /calendar/calendarPermissions endpoint will give us the permissions stamped on the default Calendar. This will also include any permissions granted on the Default security principal, represented as My organization in the Graph. There is no analogue of the Anonymous principal. Also, a slight difference exists in how permission levels are being returned, refer to the following table in the official documentation for more details.

One of the benefits Graph offers in comparison to the Exchange Online cmdlets is that we can use the “well-known” name for the Calendar folder, thus avoid the need to fetch its actual folder Id to account for possible localization impact. Credit where credit is due. Of course, mailboxes can have multiple calendars and said calendars can also be shared with either internal or external folks.

To address such scenarios, thew script supports the -IncludeNonDefaultCalendars switch parameter. When you run the script with said switch toggled, it will attempt to enumerate all calendars within the mailbox (using the /calendars endpoint) and then fetch permissions on each of them (/calendars/{%calendarid}/calendarPermissions endpoint). As one can expect, this will greatly increase the time it takes to complete the script, as multiple additional calls need to be made for each user.

The rest of the script just handles the output. We’ve chosen a simple “one permission entry per line” approach, which in turn means that you will likely get multiple entries per mailbox. The main benefit of this approach is that it makes it much easier to filter the output based on the mailbox owner (represented by objectID and mail properties) or recipient (represented by emailAddress). As usual, feel free to update the relevant code as you see fit. We’ve also included an (AI generated) HTML-based version of the output, just so we can brag about spending some tokens 🙂

Examples

Here are some examples on how to run the script and the type of output it generates. First, make sure to get your copy from our GitHub repo, and update the authentication bits (lines 12-36). Then use one of the two ways to run the script:

#Generate permission report for the default Calendar folder
.\Graph_Calendar_Permissions_inventory.ps1

#Generate permission report for all Calendar folders
.\Graph_Calendar_Permissions_inventory.ps1 -IncludeNonDefaultCalendars

Here is how the output looks like in CSV form:

Graph Calendar permissions

And here’s how the HTML output looks like:

Graph Calendar permissions1

The script also supports verbose output, which is useful for troubleshooting purposes. A basic progress bar was also included as it can take a while to process thousands of users, and you’d probably want some indicator to assure you it’s still working.

Before closing the article, here are few things that the script does not do. As mentioned already, it cannot correctly filter just users with mailboxes, so expect some warnings (when processing a user without a mailbox). It does not include any logic to differentiate between internal and external sharing, use the output files to address such scenarios. For mailboxes with more than 100 calendars, the results might be incomplete (too lazy to add another loop to handle such scenarios).

As usual, do let us know if you find the script useful, or you run into any issues with it. Comments and suggestions are always welcome, too.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading