Purpose
An import of a large contact set — tens of thousands of objects — runs for hours or days, appears to stall, and only completes after the GALsync service is restarted by hand. Smaller imports on the same server finish normally. This article explains what the import is actually doing, why scale changes its behaviour, and what can be adjusted.
What an import does, step by step
Understanding the sequence is what makes the symptoms interpretable.
- GALsync reads the data file and collects the domains found in each object's
DistinguishedName. These become the source domains. - The source DN is stored on every contact GALsync creates, in the source-domain property — by default
ExtensionAttribute9orCustomAttribute9. This is how GALsync later recognises a contact as one of its own. - On the first Exchange Online PowerShell connection, GALsync retrieves the contacts it previously created, filtering on that stored source domain:
This is the longest-running command in the whole import when many contacts already exist, and it is where a large import spends most of its time.Get-Recipient -ResultSize Unlimited -Filter {(CustomAttribute9 -like "*DC=company,DC=onmicrosoft,DC=com*")} - While those results are still streaming, GALsync opens a second PowerShell connection to fetch recipient-type-specific attributes.
- Existing contacts are matched by primary SMTP address. Unmatched existing contacts are deleted — contacts only, never MailUsers, GuestUsers or groups. Unmatched objects from the data file are created.
Two things follow from this. The import is dominated by one long query whose cost grows with the number of contacts already present, not with the number changing. And it depends on two concurrent PowerShell connections to Exchange Online.
Where large imports actually fail
The second connection never establishes. If the environment does not permit a second concurrent Exchange Online PowerShell connection, the import runs on one and becomes markedly slower and more fragile. The logs will show whether the second connection was ever opened — this is worth checking explicitly rather than assuming.
The connection drops mid-run without a response. On long-running imports the connection to Microsoft can be interrupted after roughly 20 to 40 minutes without GALsync receiving a corresponding PowerShell response. The policy then sits waiting on a reply that will never arrive, which is what "hung" looks like from outside.
A proxy sits in the path. Where corporate policy routes all Microsoft 365 traffic through a proxy, that is worth identifying early. Proxied connections to the Microsoft cloud are a recognised source of this behaviour, and the fact that other applications work through the same proxy does not rule it out — long-lived streaming PowerShell sessions are not the same workload as ordinary HTTPS requests.
What you can adjust
Advanced PowerShell connection settings. Version 8.6.0 added these to the Organization Settings dialog on the Exchange Online tab of the environment configuration, specifically to improve the handling of Exchange Online PowerShell connections, scripts and commands. If you are running an older version and seeing this behaviour, upgrading is the substantive step rather than a general recommendation.
Maximum connections. This is configurable at the Exchange Online App Registration dialog. Be aware that reducing it to 1 to work around a suspected second-connection problem has not reliably helped in practice, and tenants vary in how many concurrent connections they permit. Establish what your tenant actually allows before tuning this, rather than assuming a limit.
National clouds. Microsoft 365 national cloud environments are not the commercial cloud and behave differently on connection limits and throttling. Support for national cloud parameters when connecting via Microsoft Graph was added in 8.6.0.
Schedule. A large import competing with directory synchronization activity will be slower and more prone to collisions. Moving it to a quieter window is cheap to try.
What cannot be done
Two reasonable-sounding ideas do not work, and it saves time to know this up front.
You cannot split one source across two import policies into the same destination. Each policy reconciles the destination against its own data file, so two policies would each delete the other's contacts. The one-import-policy-per-container rule is not a style preference.
Staging by creating contacts in batches does not help an update problem. Where the difficulty is modifying an existing large set rather than creating it, phased creation is not relevant — the long query in step 3 runs over the whole existing set regardless.
When "successful" imports leave contacts stale
If a large import completes and some contacts still hold old values — and deleting a contact and letting it be recreated returns it fully current — that is a different problem with a documented cause and fix. See Synced Contacts Report Up to Date but Never Change: the Source Domain Problem.
That distinction matters here because a slow import and a silently-not-updating import can occur together in the same environment, and the remedies are unrelated.
Collecting evidence
- Action → Export Configuration and Action → Export Status, from both servers.
- Use the NETsec LogViewer and search by error code rather than reading chronologically — on a multi-day run the log is very large.
- Note whether the second PowerShell connection appears in the log at all, how long the
Get-Recipientfilter query took, and where in the sequence the run stopped progressing. - Record whether a proxy is in the path and what the tenant's concurrent connection limit is.
Those four facts are what distinguish the causes above, and gathering them before escalating avoids the most common round trip.
References
- GALsync 8.7 Manual p.12 — one import policy per organizational unit; why splitting a source across policies deletes contacts.
- GALsync 8.6.0 changelog — Advanced PowerShell connection settings on the Organization Settings dialog, Exchange Online tab; national cloud parameters for Microsoft Graph.
- The current GALsync manual, version history and the NETsec LogViewer are published by NETsec at netsec.de.
Comments
0 comments
Please sign in to leave a comment.