PowerShell: Remove duplicate calendar appointments

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 iCALUID of the item matches the iCALUID of another item.  Calendar items should be unique – if a matching uid is found, no further check is made and the matched appointment is deleted.
  • The subject, start, and end time match another item. 

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 500).  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 mailbox@domain.com

To run it using alternative credentials (you’ll be prompted for the credentials):

.\Remove-DuplicateAppointments.ps1 -mailbox mailbox@domain.com -Credentials (Get-Credential)

Other useful parameters (there are more, but check the script for further information):

-FolderPath: use to specify the path of the calendar to be processed (only use if not the default calendar, e.g. “\Calendar2”)
-PublicFolders: this switch needs to be specified if the folder path points to a calendar in public folders (otherwise the path is relative to the mailbox)
-WhatIf: if this switch is present, no changes are made, but items that would be deleted are logged
-LogFile: information is written to this file showing what has been deleted (or would be deleted if -WhatIf is also present)


Comments (21)

  1. Daniel says:

    Is it possible to use this script with Office 365 and if the answer is yes – how?

    Kind regards


  2. It works with Office 365 the same way as it works with on-premise Exchange.  Just specify mailbox and credentials when calling the script.

  3. Paul says:

    Hi David,

    When we run this we get:

    Exception calling "Add" with "1" argument(s): "Collection was of a fixed size."

    This is on:



                   $subject.Add($item.Subject, @($item))

    in the function 'SearchForDuplicates'

    Any suggestions how to solve this?


  4. Paul – how are you running it (in normal PowerShell session, EMC, etc.), and what version PowerShell?  I discovered that some of the scripts didn't work with earlier versions of PowerShell (I write them against the latest, usually) so I had to test and update them – it could be the same here.

  5. Michel says:

    Hi David

    It is in the EMC of Exchange 2013 SP1 Ru6

  6. Jeremy says:

    When I try to run it against Office 365 I get "Failed to locate EWS Managed API, cannot continue". I have EWS Managed API installed and have used it with another script. Running on Server 2012. Thanks!

  7. Jeremy says:

    Hi David,

    I am getting a "Failed to locate EWS Managed API, cannot continue" error when trying to run this script.

    I have already run: Import-Module -Name "C:Program FilesMicrosoftExchangeWeb Services1.1Microsoft.Exchange.WebServices.dll

    Is there an additional step I'm missing here? Running PowerShell ISE as Admin.


  8. It may or may not run from the ISE (I haven't tested in that – while I tend to use the ISE for writing the script, I test it in a standard PowerShell console).  It should work with any version of the managed API, however 1.1 is very old – it would be worth downloading the latest (at time of writing this is 2.2) and installing that.

  9. Michael says:

    Hi David,

    I´m running the script to Office365 and get the following error:

    "Failed to locate EWS Managed API, cannot continue"

    I used the following command:

    .Remove-DuplicateAppointments.ps1 -mailbox mailbox@domain.com -Credentials (Get-Credential)

    We don´t have an on premise exchange, just an office365 abo.

    Could you help me please?


  10. The script requires the EWS Managed API.  This can be downloaded from the Microsoft Download Centre.

  11. Jeremy says:

    Hi David,

    We have online office365 and have been trying to figure out a way to remove a calendar .ics item that is unable to be deleted.  It does create a duplicate event so I tried your script and and received this error.  

    Exception calling "Bind" with "2" argument(s): "The Url property on the ExchangeService object must be set."

    At C:ScriptRemove-DuplicateAppointments.ps1:430 char:10

    +             $Folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($global:se …

    +    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

       + FullyQualifiedErrorId : ServiceLocalException

    You cannot call a method on a null-valued expression.

    At C:ScriptRemove-DuplicateAppointments.ps1:244 char:9

    +         $results = $Folder.FindItems($view)

    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidOperation: (:) [], RuntimeException

       + FullyQualifiedErrorId : InvokeMethodOnNull

    Any advice would be much appreciated.  Still new to scripting so my knowledge is weak but my understanding seems sound.


  12. Dean says:

    Same type of error as Jeremy from PowerShell when trying to use this against Office 365 although this may be based on the fact that no duplicate items were found.

    Downloaded and successfully installed EWS Managed API 2.2

    Using Windows 10 with the new PowerShell console


    Processing mailbox dean@domain.onmicrosoft.com

    Exception calling "Bind" with "2" argument(s): "The Url property on the ExchangeService object must be set."

    At C:UsersDeanRemove-DuplicateAppointments.ps1:430 char:10

    +             $Folder = [Microsoft.Exchange.WebServices.Data.Folder]::B …

    +             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException

       + FullyQualifiedErrorId : ServiceLocalException

    You cannot call a method on a null-valued expression.

    At C:UsersDeanRemove-DuplicateAppointments.ps1:244 char:9

    +         $results = $Folder.FindItems($view)

    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidOperation: (:) [], RuntimeException

       + FullyQualifiedErrorId : InvokeMethodOnNull

    No duplicate items found!

  13. John says:

    I got the same error as Jeremy and Dean. I was able to resolve it by adding the "-AllowInsecureRedirection" parameter. Works great after that!

  14. Kathy Lieu says:

    Hi David,
    I came across this post while searching for iCALUID. Apologize in advance as my question doesn’t have anything to do with the script. Please redirect/forward my question to any other post as you see fit. My question is related to iCALUID and its uniqueness. Do you see any issues with us using iCalUid as a unique identifier, any known issues with it’s uniqueness that we should be aware of? Thank you! -Kathy

  15. tanja says:

    Cannot get it working on child calenders. I have a regular Calender and calender folder 2 under . But script do not find it
    \Remove-DuplicateAppointments.ps1 -folderpath “calender\calender2”

    1. tanja says:

      Exact error is:
      ew-Object : Cannot find an overload for “FolderId” and the argument count: “2”.
      At C:\temp\Remove-DuplicateAppointments.ps1:385 char:33
      + $folderId = New-Object Microsoft.Exchange.WebServices.Data.F …
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : InvalidOperation: (:) [New-Object], MethodException
      + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand

      Failed to find Slottet, path requested was \calender\calender2
      Failed to find folder \calender\calender2

  16. David says:

    For all you O365 admins out there trying to get this to work, the below works fine.

    .\Remove-DuplicateAppointments.ps1 -mailbox user@contoso.com -Credentials (Get-Credential) -EwsURL https://outlook.office365.com/EWS/Exchange.asmx -IgnoreSSLCertificate -AllowInsecureRedirection -LogFile C:\Scripts\log.txt

    NB: The EWSURL is generic for all Orgs in O365

    1. Nelson says:

      How can I run this script against multiple mailboxes?

  17. Nelson says:

    This script works great against O365 mailboxes. Is there a way that it can be run against more than one mailbox at a time?

  18. JoeGCH says:

    I just tried this multiple times (on the same test account) and it deleted EVERYTHING except for ONE item on a calendar that had several hundred entries. Not sure what’s going on here. Outlook for Windows 2016 CTR with latest patches to Exchange Online. THIS IS WHY WE ALWAYS TEST!

  19. Joe – if it deleted all the items except for one, then the script for some reason identified that all the items were duplicates. You can use -WhatIf to ensure no changes are made and to see what the script will do. Also use -LogFile parameter to see what it did.

Skip to main content