Removing Duplicate Contacts from Exchange Online Mailboxes with Graph API

I’ve recently completed a tenant to tenant consolidation and usually at the tail end of these types of projects, I end up with a library of scripts for different postfixes or modifications that I use to patch up things after migration. I’ve decided that rather than store them locally where they tend to get lost to time, I’ll start a series of post here where I can share them with the community.

The first script that I wanted to share is a simple but effective Graph API / PowerShell based script to remove duplicated contacts in a users mailbox. This scenario is pretty common when using any type of “Synchronization” type migration tool as contacts can often change in very small ways over time and tend to be duplicated in the destination. Of course the best way to prevent this is by running a single sync of contacts (and calendars) but still, due to sync failures or any number of other reasons, we can end up in this scenario.

Prepare the Environment

The script itself is pretty straight forward, it takes the following input parameters:

  • ClientID – The Client ID of an Application Registration
  • TenantID – The Tenant (Directory) ID of the Azure AD tenancy
  • ClientSecret – A Client Secret from the Application Registration
  • Mailbox – The User Principal Name of the target user

The other thing you’ll need is an App Registration. I’ve detailed setting up an App Reg in the past so I won’t go through it all here but you’ll need to assign Contacts.ReadWrite.All Application permissions and gather the Client ID and create a Client Secret to use in the above parameters.

The Script Logic

The script itself is pretty straightforward. It uses two reusable functions that I use in a lot of my scripts which are:

  • GetGraphToken – This function takes in the details of an App Reg and Secret to request an Access Token
  • RunQueryAndEnumerateResults – This function runs a Graph query and enumerates through result pages to return a combined result

Then there is one more function specific to this script, DeleteContact which takes in parameters for the Contact object ID, The users UPN and the Access Token and removes a contact.

In the main body of the script, we simply run the RunQueryandEnumerateResults function to list all contacts in the mailbox. We then loop through the contacts and every time we find a unique contact, add a unique ID for the contact to an array. I’ve used a combination of the first email address and display name to generate a unique ID but you can modify this to meet your requirements.. This is all done in the below code.

$contactlist = @()

foreach ($contact in $Results) {

    ##Generate unique ID of Contact based on first email and displayname
    $ContactUID = (($contact.emailaddresses | select -First 1 | select address).address + $contact.displayname)

    ##IF contact is already in the list, Delete
    IF ($contactlist -contains $ContactUID) {
        write-host "Deleting $contactuid"
        DeleteContact -token $token -contact $ -mailbox $mailbox
        write-host "Deleted $contactuid"
    else {
        ##Add entry to list
        write-host "Adding $contactuid to Contact List"
        $Contactlist += $ContactUID



To run the script, simply navigate to the path it’s been saved to and run it like the below example.

.\graph-Remove-Duplicate-Contacts.ps1 -Mailbox "" -ClientSecret $clientSecret -ClientID $clientID -TenantID $tenantID 

The script will write an output to the screen for each action it’s taking so you can monitor what’s going on. It can also be run pretty easily in a loop for a group of mailboxes although I haven’t allowed for throttling during large runs so you may need to modify it to compensate.

You can download the script from GitHub here and I will be posting similar scripts and snippets that I’ve used in the past over the next few weeks. Feel free to let me know if there are any specific use cases you would like to see.

As always, make sure you understand any code you run in production.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s