Migration Guide
Migrating from EWS scripts
If you previously used a PowerShell script based on Exchange Web Services (EWS) to sync contacts, this guide walks you through cleaning up the old contact folders before switching to CYNC.
Why cleanup is needed
Old EWS-based scripts (like EWSContactSync, CopyContactsOnly, or similar community scripts) create contact folders at a non-standard location in each mailbox. Instead of placing the folder under the default Contacts well-known folder, they create it as a direct child of MsgFolderRoot (the mailbox root).
MsgFolderRoot
├── Inbox
├── Sent Items
├── Contacts (well-known folder)
│ └── CYNC folder ← created by CYNC (Graph API)
├── Kontakte - Sync ← created by old EWS script
└── ...
This matters because:
- Microsoft Graph cannot see these folders — the Graph API’s
contactFoldersendpoint only returns children of the Contacts well-known folder. - CYNC creates its own folder under the Contacts well-known folder (visible to Graph), so the old EWS folder becomes a stale duplicate with outdated contacts.
- Users may see duplicate contacts in Outlook — both the old EWS-created contacts and the new CYNC-synced contacts.
Important: The cleanup script only removes folders that are direct children of MsgFolderRoot. It will never touch the CYNC contact folder (which lives under the Contacts well-known folder).
Prerequisites
| Component | Details |
|---|---|
| PowerShell | Windows PowerShell 5.1 or PowerShell 7+ |
| MSAL.PS module | Install-Module MSAL.PS -Scope CurrentUser -Force |
| EWS Managed API | Auto-downloaded from NuGet on first run (cached locally) |
| Entra ID App Registration | Same app registration used by your old EWS script or CYNC. Requires EWS.AccessAsUser.All (delegated) or full_access_as_app (application) permission. For -AllMailboxes, also add User.Read.All (Graph API). |
Download the cleanup script
Download Remove-LegacyContactFolder.ps1 from the CYNC GitHub repository (tools folder) or copy it from your CYNC installation directory.
Authentication
The script supports four authentication modes. Priority order: certificate file → certificate store → client secret → interactive browser login.
Interactive login (single mailbox)
A browser window opens for sign-in. This mode can only clean up the signed-in user’s own mailbox — Microsoft has deprecated ApplicationImpersonation in Exchange Online, so accessing other users’ mailboxes requires app-only auth (client secret or certificate).
.\Remove-LegacyContactFolder.ps1 -TenantId "contoso.onmicrosoft.com" `
-ClientId "your-app-client-id" `
-MailboxListFile "mailboxes.txt" -WhatIfImportant: Interactive login can only access the signed-in user’s own mailbox. To clean up all mailboxes (-AllMailboxes), you must use app-only auth (-ClientSecret or certificate) with full_access_as_app.
Client secret (app-only, simplest)
The simplest option for app-only auth — no certificate needed. Create a client secret in the Entra app registration under Certificates & secrets and pass the secret value. Requires full_access_as_app application permission on EWS.
.\Remove-LegacyContactFolder.ps1 -TenantId "contoso.onmicrosoft.com" `
-ClientId "your-app-client-id" `
-ClientSecret "your-secret-value" `
-AllMailboxes -WhatIfCertificate file (app-only)
For unattended or large-scale cleanup, use the same certificate configured for your Entra ID app registration. Requires full_access_as_app application permission on EWS.
.\Remove-LegacyContactFolder.ps1 -TenantId "contoso.onmicrosoft.com" `
-ClientId "your-app-client-id" `
-CertificatePath ".\cert.pfx" `
-CertificatePasswordPath ".\cert.cred" `
-AllMailboxesCreate the credential file with: Get-Credential | Export-CliXml -Path cert.cred
Certificate from store (app-only)
If the certificate is installed in the local machine or current user certificate store:
.\Remove-LegacyContactFolder.ps1 -TenantId "contoso.onmicrosoft.com" `
-ClientId "your-app-client-id" `
-CertificateThumbprint "A1B2C3D4E5..." `
-AllMailboxesStep-by-step migration
Stop the old sync script
Disable or remove any scheduled task running the old EWS-based contact sync. This prevents the old script from re-creating the folder after cleanup.
Install prerequisites
Install-Module MSAL.PS -Scope CurrentUser -ForceThe EWS Managed API DLL is downloaded automatically from NuGet on first run.
Preview (dry run)
Run with -WhatIf first to see which mailboxes have the old folder without making any changes:
.\Remove-LegacyContactFolder.ps1 -TenantId "contoso.onmicrosoft.com" `
-ClientId "your-app-client-id" `
-ClientSecret "your-secret-value" `
-FolderName "Kontakte - Sync" `
-AllMailboxes -WhatIfUse -Verbose for detailed folder diagnostics.
Run the cleanup
Once you’re satisfied with the preview, remove -WhatIf to perform the actual deletion:
.\Remove-LegacyContactFolder.ps1 -TenantId "contoso.onmicrosoft.com" `
-ClientId "your-app-client-id" `
-ClientSecret "your-secret-value" `
-FolderName "Kontakte - Sync" `
-AllMailboxesInstall and configure CYNC
Follow the Getting Started guide to install the CYNC service, connect your Entra ID tenant, and run your first sync.
Parameter reference
| Parameter | Required | Description |
|---|---|---|
| -TenantId | Yes | Entra ID tenant (GUID or *.onmicrosoft.com) |
| -ClientId | Yes | Application (client) ID of the Entra app registration |
| -FolderName | No | Folder to remove. Default: Kontakte - Sync. Also checks dash/en-dash variants automatically. |
| -MailboxListFile | No | Text file with one email address per line. Lines starting with # are ignored. |
| -AllMailboxes | No | Fetches all licensed users via Microsoft Graph. Requires User.Read.All permission on the app registration. |
| -ClientSecret | No | Client secret value from the Entra app registration. Simplest option for app-only auth — no certificate needed. |
| -CertificatePath | No | Path to the PFX certificate file for app-only auth |
| -CertificatePasswordPath | No | Path to the encrypted credential file for the PFX password |
| -CertificateThumbprint | No | Certificate thumbprint (LocalMachine\My or CurrentUser\My) |
| -DeleteMode | No | Delete (default) removes the folder and all contacts.Empty removes contacts but keeps the folder. |
| -ThrottleDelayMs | No | Delay between mailboxes in ms. Default: 300 |
| -EwsDllPath | No | Custom path to Microsoft.Exchange.WebServices.dll. Auto-downloaded if not found. |
| -WhatIf | No | Preview mode. Shows what would be deleted without making changes. |
Targeting mailboxes
All mailboxes
Use -AllMailboxes to scan every mailbox in your tenant. The script skips mailboxes that don’t have the old folder.
From a file
Create a text file with one email address per line and pass it via -MailboxListFile:
# mailboxes.txt
user1@contoso.com
user2@contoso.com
# This line is a comment and will be skippedInteractive (manual entry)
If neither -AllMailboxes nor -MailboxListFile is provided, the script prompts you to type email addresses one at a time. Press Enter on an empty line to start processing.
Granting EWS application access
To clean up all mailboxes with app-only auth (client secret or certificate), the Entra app registration needs the full_access_as_app application permission for EWS. If you used the old EWS script, this is likely already configured.
- In Azure Portal, go to App Registrations → your app → API Permissions
- Click Add a permission → APIs my organization uses → search
Office 365 Exchange Online - Select Application permissions → check
full_access_as_app→ Add permissions - Click Grant admin consent
For -AllMailboxes, also add User.Read.All (Microsoft Graph, application) to enumerate licensed users.
Troubleshooting
"The account does not have permission to impersonate"
ApplicationImpersonation in Exchange Online. Interactive login can only access the signed-in user’s own mailbox. Switch to app-only auth (-ClientSecret or certificate) with full_access_as_app to clean up all mailboxes. See the EWS app permission section.403 Forbidden / "Access is denied"
full_access_as_app permission, or admin consent has not been granted yet. After adding the permission and granting consent, it can take a few minutes to propagate. If you still get 403, clear the cached MSAL token: Clear-MsalTokenCache, then re-run the script."Redirect URI mismatch"
https://login.microsoftonline.com/common/oauth2/nativeclient as a redirect URI. In Azure Portal, go to App Registrations → your app → Authentication → Add a platform → Mobile and desktop applications → enable the native client redirect URI."MSAL.PS module not found"
Install-Module MSAL.PS -Scope CurrentUser -ForceNo folder found, but mailbox should have one
-Verbose to see detailed folder listings. The script searches direct children of MsgFolderRoot. Check that the folder name matches — the default is Kontakte - Sync. If your old script used a different name, pass it via -FolderName.EWS DLL download fails
Microsoft.Exchange.WebServices NuGet package (v2.2.0), extract Microsoft.Exchange.WebServices.dll, and pass the path via -EwsDllPath.Next steps
After removing the old contact folders, follow the Getting Started guide to install and configure CYNC. You can use the same Entra ID app registration — just make sure to grant the required Graph API permissions.