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 contactFolders endpoint 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

ComponentDetails
PowerShellWindows PowerShell 5.1 or PowerShell 7+
MSAL.PS moduleInstall-Module MSAL.PS -Scope CurrentUser -Force
EWS Managed APIAuto-downloaded from NuGet on first run (cached locally)
Entra ID App RegistrationSame 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" -WhatIf

Important: 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 -WhatIf

Certificate 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" ` -AllMailboxes

Create 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..." ` -AllMailboxes

Step-by-step migration

1

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.

2

Install prerequisites

Install-Module MSAL.PS -Scope CurrentUser -Force

The EWS Managed API DLL is downloaded automatically from NuGet on first run.

3

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 -WhatIf

Use -Verbose for detailed folder diagnostics.

4

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" ` -AllMailboxes
5

Install 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

ParameterRequiredDescription
-TenantIdYesEntra ID tenant (GUID or *.onmicrosoft.com)
-ClientIdYesApplication (client) ID of the Entra app registration
-FolderNameNoFolder to remove. Default: Kontakte - Sync. Also checks dash/en-dash variants automatically.
-MailboxListFileNoText file with one email address per line. Lines starting with # are ignored.
-AllMailboxesNoFetches all licensed users via Microsoft Graph. Requires User.Read.All permission on the app registration.
-ClientSecretNoClient secret value from the Entra app registration. Simplest option for app-only auth — no certificate needed.
-CertificatePathNoPath to the PFX certificate file for app-only auth
-CertificatePasswordPathNoPath to the encrypted credential file for the PFX password
-CertificateThumbprintNoCertificate thumbprint (LocalMachine\My or CurrentUser\My)
-DeleteModeNoDelete (default) removes the folder and all contacts.Empty removes contacts but keeps the folder.
-ThrottleDelayMsNoDelay between mailboxes in ms. Default: 300
-EwsDllPathNoCustom path to Microsoft.Exchange.WebServices.dll. Auto-downloaded if not found.
-WhatIfNoPreview 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 skipped

Interactive (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.

  1. In Azure Portal, go to App Registrations → your app → API Permissions
  2. Click Add a permissionAPIs my organization uses → search Office 365 Exchange Online
  3. Select Application permissions → check full_access_as_appAdd permissions
  4. 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"
Microsoft has deprecated 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"
The app registration is missing the 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"
The app registration needs https://login.microsoftonline.com/common/oauth2/nativeclient as a redirect URI. In Azure Portal, go to App Registrations → your app → AuthenticationAdd a platformMobile and desktop applications → enable the native client redirect URI.
"MSAL.PS module not found"
Install with: Install-Module MSAL.PS -Scope CurrentUser -Force
No folder found, but mailbox should have one
Run with -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
The script tries to auto-download from NuGet. If your network blocks NuGet, manually download theMicrosoft.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.