Given the COVID-19 situation the world is currently dealing with, the annual Microsoft MVP Summit got cancelled and transformed into a Virtual Summit on Microsoft Teams. The organizing team had to pivot over two weeks to get this going, and they did a great job, but there were some rough edges. One of these rough edges was in regards to the session catalog and the inability to easily build a schedule in your personal Outlook calendar.
I took it as a challenge to find an efficient way to extract all sessions as .ics files, which can be easily imported in your favorite calendaring app. The result is rather hacky, but the problem-solving process might inspire someone!
Setting the scene
A little bit of background information to help understand the solution:
- Virtual MVP Summit is set up as one big Microsoft Teams team containing all MVP's from over the world, combined with all Microsoft presenters and support staff
- Each track (a group of related sessions) got their own channel
- Each session in a track was scheduled as a Channel Meeting in their respective channel
- The session catalog was a Group Calendar web part on a modern page in the SharePoint site backing this Microsoft Teams team, containing ALL sessions across ALL tracks
- There were 820 channel meetings scheduled over 4 days time
- A Channel Meeting ends up in the Calendar associated with an Office 365 Group
- All invited MVP's are Azure AD B2B guests in the Microsoft tenant
The problems
Being an Azure AD B2B guest user presented an interesting set of problems:
- Azure AD B2B guest users have no access to the Office 365 Calendar (also group mailbox is off limits), so there is no way to subscribe to that calendar.
- Azure AD B2B Guests can access the Calendar information through Microsoft Graph, but that just gives us a JSON representation of all events. Microsoft Graph doesn't have a way to transform them into .ics files
- Azure AD B2B Guests can access the Calendar information through the Group Calendar web part on a modern SharePoint page. This web part allows displaying 10 events per page, doesn't display the description, has no visual Calendar representation, has no filters etc. It has a great button labeled "Sync calendar", commonly understood to sync the calendar to your own Outlook Calendar but this is just a refresh button for the web part 🙄.
The team that created this web part is in desperate need of a UX expert 😉
How I solved it
How much I dislike the Group Calendar web part, it does have one great feature: export a specific event to an .ics file! Since this already exists, my goal was to find a repeatable way to piggyback on that code to bulk export all sessions.
Step 1: Get the group id and an access token
All relevant data is already loaded in the Group Calendar web part, so in comes the Nex Edge (Edgium? ChrEdge?) Dev Tools. "Sync calendar" and watch the network requests:
We take the Request URL and the authorization token (bearer token) for the next step.
Step 2: Get all events in JSON format
The Request URL from the previous step loads 10 events, let's tweak it to get them all. Time to load up PostMan, use the Request URL and the Bearer token and send out the request:
I just tweaked the parameters a bit:
startDateTime
andendDateTime
to cover the MVP Summit timeframe- added
$count
so it would return total number of results - adjusted
$orderBy
to sort by subject - added a
$filter
to hide all cancelled events - adjusted
$select
to only return the properties I'm interested in - adjust
$top
from 10 to 999 (max value) hoping that I didn't have to deal with pagination (luckily there were only about 820 events 😀)
Now Microsoft Graph returns me all the event id's:
Step 3: Piggyback on the export code in the Group Calendar web part
This is where the fun part start: how to use al 820-ish event ids from Microsoft Graph and feed them into the export code that exists in the Group Calendar web part. We don't have the code, since it is a first party web part and we have no idea where to look.
Enter New Edge Dev Tools again! Digging through the sources of the page I found the file responsible for the export functionality (files/sp-client/chunk.sp-event-detail-dialog_3902dd58568831a01392.js), and with the pretty-print from Dev Tools it became almost readable:
After some digging I found the code that is triggered when clicking "Add event to your calendar", so I put a breakpoint after it:
Refresh the page, open an event detail screen and it now hits the breakpoint. Key functions here are t.prototype._handleClick
and t.prototype._exportICS
. The first one is triggered when clicking "Add event to your calendar" and passes the relevant session id to the second function, which will generate and download the .ics file.
Cool thing with JavaScript is that you can now just override basically anything in the code, and replace it with our own functionality!
I switched over to the Console tab in New Edge Dev Tools and pasted in the following code:
window.allSessions = [
{
"@odata.etag": "W/\"ZDTNU4GBZk2k3qyQrusE+AAACgMKqA==\"",
"id": "AAMkADQ1NGQ5ODEyLTk1NjAtNGJhNy04MDVlLWVjNmVjYmE0N2YzMABGAAAAAAAs-gPPZE2GSYf6iRV6vx30BwBkNM1TgYFmTaTerJCu6wT4AAAAAAENAABkNM1TgYFmTaTerJCu6wT4AAAJ90ePAAA=",
"seriesMasterId": null
},
{
"@odata.etag": "W/\"ZDTNU4GBZk2k3qyQrusE+AAACgMQZg==\"",
"id": "AAMkADQ1NGQ5ODEyLTk1NjAtNGJhNy04MDVlLWVjNmVjYmE0N2YzMABGAAAAAAAs-gPPZE2GSYf6iRV6vx30BwBkNM1TgYFmTaTerJCu6wT4AAAAAAENAABkNM1TgYFmTaTerJCu6wT4AAAJ90eQAAA=",
"seriesMasterId": null
}
];
window.sessionData = window.allSessions.map(m => m.id);
window.sleepExport = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds));
}
window.exportSessionData = async (x) => {
for (const m of window.sessionData) {
x._exportICS(m);
await window.sleepExport(200);
}
}
t.prototype._handleClick = function() {
window.exportSessionData(this);
}
This code does multiple things:
- create an
allSessions
array on the window object, storing the output from PostMan - adjust the
allSessions
array keep the session ids and store them in thesessionData
array on the window object - create a
sleepExport
function that just pauses for a given amount of milliseconds - create an
exportSessionData
function that would loop over each session id insessionData
trigger_exportICS
function for each session id in the array. After the export, it "sleeps" for 200ms.
I did it without the sleep first, but although all requests were sent to Microsoft Graph I got a bunch of429 Too Many Requests
responses.
Also, it seems that Chrome couldn't handle all the downloading and some downloads were just skipped 😯. - overwrite the
t.prototype._handleClick
function with a version of our own, callingexportSessionData
instead of the original functionality
Now, when I click on "Add event to your calendar", the JavaScript code would happily loop through all session ids, generate and download all .ics files. Just a couple of minutes patience, and there they all are:
The result
A collection of 820 .ics files was generated and downloaded to my computer, and as a result shared as an official way to consume the session catalog for MVP Summit 2020. It made me very proud, but it also made 3000 people very happy and successful in building their own personal schedule for the week.
And all of this in just a night's work!
UPDATE: there is a Microsoft Graph User Voice item that requests for the export functionality to be included natively in Graph. Go vote!