I recently had trouble with “a fruit device” creating a lot of duplicate Outlook calendar events. So I developed a console application utility to remove all those duplicates, at the same time trying out the Microsoft Graph .NET Client Library. This turned out to be a little more involved than I though at first.
First, the constructor of GraphServiceClient takes an IAuthenticationProvider, that should add an authorization header consisting of a bearer token to each request. I copied AuthenticationHelper from the console connect sample on GitHub (there are samples on GitHub for a lot of languages and frameworks). You can use the Outlook API directly with base URL https://outlook.office.com/api/v2.0/, in which case the required scope is https://outlook.office.com/calendars.readwrite. But Microsoft recommends using the graph API with base URI https://graph.microsoft.com/v1.0, in which case the required scope is calendars.readwrite, unless you need features available in the former but not the latter (e.g. Outlook tasks API).
The authentication helper uses PublicClientApplication from Microsoft.Identity.Client. My first attempt was to target .NET Core for the console application, but it turned out that PublicClientApplication doesn’t support showing a user interface dialog to input credentials on .NET Core, so I had to revert to .NET Framework 4.6.
With the sample authentication helper, you are forced to enter credentials and consent every time the program is run, which is cumbersome especially when testing. So I found TokenCacheHelper here, which I modified slightly. One thing I changed was where tokens are stored – it felt better to store them in the user’s profile (local app data), than in the executable binary folder.
The algorithm for detecting and deleting duplicates is at a high level the following:
- Get calendars, using Me.Calendars.
- For each calendar:
- Get all events, one page at a time, using Me.Calendars[calendar.Id].Events.
- Group events by subject, start time and end time.
- For each group:
- Group by ID. (This seems weird, but I discovered that the events call can respond with two or more items with the same ID. We don’t want to try to keep one of them and delete the rest, because that is not possible – one delete call will delete them all since it is in reality the same event.)
- Call delete with each unique event ID except the first one.
Another thing I discovered is that some calendars are read-only, notably holiday calenders, so it is not possible to remove duplicates in those. My code checks for this as well.
The complete source is available on GitHub: henriko2018/RemoveDuplicateEvents.