Tag: PowerShell


How to delete those annoying duplicate Lync contacts


sfb-contact-duplicatesIf you’ve been using Lync (now Skype for Business) for a while, you’ll likely find that you have a number of duplicate contacts in your mailbox, due to a legacy behavior of the client auto-creating contacts everytime the client version changed. If you’ve been running Lync for quite a while, these duplicates may run north of 10,000 objects, which will start to mess with your Outlook performance and do horrible things to your mobile phone address book.

The underlying cause of these duplicates being created has long been fixed via CUs, but the contacts remain. The trouble with removing these if you’re on Exchange Online is the contacts have special permissions and can’t be deleted by the end user.

I needed to solve this problem recently, and found plenty of suggested fixes but they were either way too manual (fine for a few duplicates but not thousands) or they just didn’t work at all.

Being a fan of all things PowerShell, I dug out some cmdlets and figured out how to do it via PowerShell, which I’ll spell out below. I’ve included a full copy of the script at the end of this post if you just want to steal the whole thing. Go for it.

To do this you’ll need a PowerShell window (no special modules required) and an account with the global admin role. (To be honest I’ve been lazy here, there will be a combination of role permissions lower than global admin that enables this, I just haven’t figured out what they are yet)

First up, the basics. Get yourself connected to Exchange Online via PowerShell.

$cred = get-credential  
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell/" -Credential $cred -Authentication Basic -AllowRedirection Import-PSSession $session -AllowClobber

Next create a mailbox to store the log output. I’d recommend using a shared mailbox so it doesn’t consume a license. Be sure to grant yourself access to the mailbox so you can review outputs.

New-Mailbox -Name LyncContactRemoval -Shared 
Get-Mailbox LyncContactRemoval | Add-MailboxPermission -User  -AccessRights FullAccess -InheritanceType All

Now to find the offending contacts. We use a mailbox search query to iterate over all mailbox items that contains the unique string that each duplicate contains. The output of the search is saved to the shared mailbox you created above to allow you to verify the query returns only those items you want.

I’ve included a few sample queries to get you started.
1. Query against a single mailbox
2. Query multiple mailboxes
3. Query all mailboxes

Note that these commands will include the dumpster by default. If you want to exlude the mailbox dumpster from the query, add “-SearchDumpster:$false” to the end of the command. There is also a return item limit of 10,000 items per mailbox.

# Example 1 - For a single mailbox  
Get-Mailbox  | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -TargetMailbox LyncContactRemoval -TargetFolder Cleanup -LogLevel Full -LogOnly

#   Example 2 - For a couple of mailboxes  
Get-Mailbox | ?{$_.alias -eq '' -OR $_.alias -eq ';' -or $_.alias -eq ''} | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -TargetMailbox LyncContactRemoval -TargetFolder Cleanup -LogLevel Full -LogOnly

#   Example 3 - For all mailboxes  
Get-Mailbox | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -TargetMailbox LyncContactRemoval -TargetFolder Cleanup -LogLevel Full -LogOnly

Open the shared mailbox via Outlook and locate the TargetFolder path you used. This will include a mail object with the results of the query you ran, and a CSV file attachment containing the precise results down to a per-contact level. Validate your results are as you expect, and then either proceed with deletion, or refine your query and re-run the search.

Once you’re happy with your query and the output you’re getting, time to hit delete. This is basically just a case of using the -DeleteContent flag on the same query command you used above.

If you use the ‘all mailboxes’ query this may take a very long time. In my case I had about 150 mailboxes with 350,000 duplicate contacts. The deletion process took 7 hours to complete.

Due to the query return item limit, this will only delete 10,000 items from each mailbox at a time. Pending on how many duplicates your users have you may need to run this more than once (I did).

#  For a single mailbox  
Get-Mailbox  | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -DeleteContent

# For a couple of mailboxes (this filter uses the mailbox alias value, but you could filter on any field that makes sense to you)
Get-Mailbox | ?{$_.alias -eq '' -OR $_.alias -eq '' -or $_.alias -eq ''} | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -DeleteContent

# For all mailboxes - note this could take a loooong time.
Get-Mailbox | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -DeleteContent -SearchDumpster:$false

That’s it – job done.

Technically you should remove the shared mailbox you created at the beginning to clean this up. I left mine in place for a while as a lazy log of what was removed, but deleted it once I was happy nobody was upset by the sudden removal of 350,000 completely useless contact objects. That and of course close your session like a good ‘sheller should.

Get-Mailbox LyncContactRemoval | Remove-Mailbox
Remove-PSSession $session

If you’re looking for the entire script, you’ll find it below.  Hope it’s useful.

JB

 

# Get yourself connected to Exchange Online 
$cred = get-credential  
$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell/" -Credential $cred -Authentication Basic -AllowRedirection Import-PSSession $session -AllowClobber

# First create a destination mailbox for query reports. Use a shared mailbox so it doesn't consume a license 
New-Mailbox -Name LyncContactRemoval -Shared 
# Grant yourself access to the mailbox so you can open via Outlook 
Get-Mailbox LyncContactRemoval | Add-MailboxPermission -User <your-mailbox-upn> -AccessRights FullAccess -InheritanceType All

# Query for candidates and log results (no other action taken)

# Example 1 - For a single mailbox  
Get-Mailbox  | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -TargetMailbox LyncContactRemoval -TargetFolder Cleanup -LogLevel Full -LogOnly

#   Example 2 - For a couple of mailboxes (this filter uses the mailbox alias value, but you could filter on any field that makes sense to you)  
Get-Mailbox | ?{$_.alias -eq '' -OR $_.alias -eq ';' -or $_.alias -eq ''} | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -TargetMailbox LyncContactRemoval -TargetFolder Cleanup -LogLevel Full -LogOnly

#   Example 3 - For all mailboxes  
Get-Mailbox | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -TargetMailbox LyncContactRemoval -TargetFolder Cleanup -LogLevel Full -LogOnly

# Note - these commands will include the dumpster by default. If you want to exlude the mailbox dumpster from the query, add "-SearchDumpster:$false" to the end of the command

# Open the mailbox in Outlook and locate the TargetFolder path you used. This will include a mail object with the results of the query, and a CSV file attached containing the precise results down to a per-contact level. Validate your results are as you expect, and then either proceed with deletion, or refine your query and re-run the search.    
# To actually delete the objects.. do this

#  For a single mailbox  
Get-Mailbox  | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -DeleteContent

# For a couple of mailboxes (this filter uses the mailbox alias value, but you could filter on any field that makes sense to you)
Get-Mailbox | ?{$_.alias -eq '' -OR $_.alias -eq '' -or $_.alias -eq ''} | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -DeleteContent

# For all mailboxes - note this will take a loooong time. For 232 mailboxes with 350,000 duplicate contacts, this process took 7 hours to complete.
Get-Mailbox | Search-Mailbox -SearchQuery 'all:"This contact was updated from Microsoft Lync" OR all:"This contact was added from Microsoft Lync"' -DeleteContent -SearchDumpster:$false

 


JB / The Daywalker

Ginger IT dude hanging out down in New Zealand, playing with technology since ages ago.

Currently Service Delivery Manager at Silicon Systems, formerly Skype for Business MVP, and generally into all things Microsoft (and a few things that aren’t).

When I’m not nerding out on technology, you can find me running ultramarathons, brewing beer, or in my woodshop building something.


On The Socials

Visit Us On LinkedinVisit Us On TwitterVisit Us On Facebook