Current version: 1.0.1

A PowerShell script that will remove duplicate appointments from a calendar in an Exchange mailbox.


Note that this script was originally published on the Messaging Developer Blog, but this listing is an updated version that supports OAuth.

We regularly get requests for an automated way of deleting duplicate appointments from calendars (most often caused by migration issues or mistaken mailbox imports, but there are lots of reasons that you could end up with duplicates).  So, here is a script that checks for and deletes duplicates.  As usual, it uses EWS to connect to the mailbox, and requires the EWS Managed API to work (and does not need to be run on the Exchange server - any client machine will do).

A calendar item is considered a duplicate if either of these conditions is true:

The script works by first of all reading all the appointments, and then checking each and building up a list of any duplicates.  Once all the items have been checked, the duplicate items are deleted in bulk (in batches of 100).  Currently the script will only work for appointments, though you can use it against public folder calendars or any other as needed (by using -FolderPath and -PublicFolders parameters as necessary).

To run it against a single mailbox using the credentials of the logged in user:

.\Remove-DuplicateAppointments.ps1 -mailbox

To run it against an Office 365 mailbox (you'll be promtped for credentials):

.\Remove-DuplicateAppointments.ps1 -mailbox -EwsUrl "" -Credentials (Get-Credential)

To run it against an Office 365 mailbox when MFA is enabled (note that you'll need the ADAL dll present in the same folder as the script, please see -OAuth parameter for further information):

.\Remove-DuplicateAppointments.ps1 -mailbox -EwsUrl "" -OAuth


-Mailbox The SMTP address of the mailbox to be processed.
-FolderPath If specified, this is the folder that will be searched for duplicate appointments (if not specified, the default Calendar folder will be processed).
-DuplicatesTargetFolder If set, then any duplicate items will be moved to this folder instead of deleted.
-PublicFolders If present, then folders are located in Public Folders (not in the user's mailbox).  FolderPath needs to be specified if this switch is used.
-Credential Will accept PSCredentials for authentication to Exchange (e.g. Get-Credential).  This cannot be used with other authentication parameters (e.g. -username).  
-OAuth If this switch is specified, the script will use OAuth to log on to the mailbox.  Note that this requires ADAL - the dll should be placed in the same folder as the script.  ADAL can be downloaded from Github:  
-OAuthClientId Client Id for the application that is registered in Azure.  There is an application already registered in the Microsoft tenant that you can use (by missing out this parameter), but you will be prompted to consent during first log-on.  It would be better to create your own application registration in your own tenant (as then you have complete control over it).  
-OAuthRedirectUrl The redirect Url as registered in Azure AD (do not use if you do not register your own Azure application).  
-Impersonate Mailbox will be accessed using Impersonation (the authenticating account needs impersonation rights to the mailbox). 
-EwsUrl The EWS endpoint.  If not specified, autodiscover will be used to determine the correct EWS URL.  For Office 365, you can use "" as the URL.
-EwsManagedAPIPath The path to the EWS Managed API.  The script will search standard locations for the dll, but if you have installed it elsewhere you may need to specify the location.
-IgnoreSSLCertificate If present, any SSL certificate errors will be ignored.  Useful for testing in a lab with self-signed certificates.
-AllowInsecuredIndirection  If present, autodiscover will accept redirects to insecure addresses.
-LogFile If specified, actions will be logged to this file (the same information is shown in the console).
-TraceFile If specified, EWS requests/responses will be logged to this file.  This is only useful for debugging.
-WhatIf If present, no actions will be taken on duplicates found.