Sync and seek: User matching in tenant migrations

Few things ruin a migration weekend faster than logging into the target tenant and finding two accounts of every user. 😱 One is the account you carefully migrated, the other is a duplicate that sync helpfully created because it could not figure out that the on-prem AD account and the cloud account were the same person. Now you have a cleanup job nobody scoped for, and a user who cannot sign in reliably until you sort out which object is the real one.

This post is about avoiding exactly that. Specifically, how Entra decides that a cloud user and an on-prem AD user are the same object, and how that plays out in the messy real-world topologies you hit during migrations and mergers: Two tenants sharing one AD, one tenant absorbing a second AD, and the combination of both. I will keep Microsoft Entra Connect Sync as the main character in this post, because that is what most of you are still running. I will call out where Microsoft Entra Cloud Sync behaves differently, because those differences may bite you if you assume the two tools work the same way.

It all comes down to the anchor

Every matching decision Entra makes rests on one attribute. Microsoft calls it the sourceAnchor on-premises and the ImmutableID in the cloud, and the two names describe the same value seen from two directions. It uniquely ties an on-prem object to its cloud counterpart, and as the name “immutable” suggests, you really do not want it changing under you.

Here is the part that decides whether your merge survives or falls apart. There are two candidate attributes for the anchor:

  • objectGUID: Stamped on an AD object when it is created and never writable again, not even by AD itself. Globally unique, which sounds great until you move the object to another forest and it gets a brand new one.
  • ms-DS-ConsistencyGuid: Writable, which is the whole point. You can carry it from one forest to another, and that is what keeps the cloud link alive through a migration.

Every currently supported version of Connect Sync defaults to ms-DS-ConsistencyGuid as the anchor for user objects. It only falls back to objectGUID if ms-DS-ConsistencyGuid is already populated in your directory by something else, which the wizard reads as in use and avoids. That default has been in place for years, so on any tenant you are likely to touch today, it is the user anchor. The catch is that the source anchor is set at install and rarely revisited, so plenty of older tenants still run objectGUID, or another attribute, simply because that is what someone selected when the tenant was first set up. You can still migrate an objectGUID tenant to ms-DS-ConsistencyGuid, but not the other way, and an individual object’s anchor value is fixed once it has synced. Confirm what yours actually uses rather than assuming the modern default.

Worth knowing too: Groups are not the clean parallel to users they first appear to be. Connect uses ms-DS-ConsistencyGuid as a group’s immutableId only when the attribute is already populated, and otherwise falls back to objectGUID. Unlike users, Connect does not write ms-DS-ConsistencyGuid back to the group object, so it stays empty unless you set it yourself, which is exactly why a freshly synced group shows no value there. Two consequences follow. If you want a group to keep its cloud link through a cross-forest move, copy ms-DS-ConsistencyGuid (or the objectGUID) onto the target group yourself before the move, because Connect will not seed it. And there is no settable ImmutableID on the cloud side of a group, so you cannot hard-match a cloud-only group to an on-prem one: A security group has no soft or hard match at all, and a mail-enabled group can only soft-match on its primary SMTP address, so a cloud-only group lands beside the incoming on-prem group as a duplicate. Microsoft documents the group anchor behaviour here: Microsoft Entra Connect: Migrate groups from one forest to another.

My strong recommendation, and Microsoft’s: Use ms-DS-ConsistencyGuid. If your tenant predates that default and is still pinned to objectGUID, fixing it before any migration is time very well spent. You can read Microsoft’s design guidance here: Microsoft Entra Connect: Design concepts.

How to check which anchor you are using

Do not guess, confirm it:

  1. On the Connect Sync server, open the Microsoft Entra Connect wizard.
  2. Select View or export current configuration.
  3. Look at the Synchronization Settings section. The Source Anchor attribute is listed there.

If it reads mS-DS-ConsistencyGuid, you are in a good place. If it reads objectGUID, you may want to plan changing it before you migrate anything.

Converting between the on-prem value and the cloud ImmutableID

The on-prem attribute is a GUID or a hex byte array, and the cloud ImmutableID is a Base64 string. They hold the same value, it just does not look that way at a glance, so you will constantly need to convert between them when validating matches.

Get a user’s objectGUID and turn it into the Base64 ImmutableID:

$user = Get-ADUser "jdoe" -Properties objectGUID
[System.Convert]::ToBase64String(([guid]$user.objectGUID).ToByteArray())

Go the other way, from an ImmutableID back to a GUID:

[Guid]([System.Convert]::FromBase64String("1wfM9xV8fUSHbcAbDlqeOA=="))

And to read ms-DS-ConsistencyGuid as a readable GUID rather than a raw byte array:

Get-ADUser "jdoe" -Properties "mS-DS-ConsistencyGuid" |
  Select-Object samAccountName, objectGUID,
    @{n='consistencyGuid';e={[guid]$_.'mS-DS-ConsistencyGuid'}}

Keep these handy. Half of all “why won’t this match” troubleshooting is just confirming the on-prem value and the cloud value are genuinely the same once you decode them.

Hard match or soft match

When sync sends a new object up, Entra tries to find an existing cloud object that represents the same identity. It checks three attributes, namely userPrincipalName, proxyAddresses, and sourceAnchor/ImmutableID (source). There are two ways it can land a match.

Hard match uses the ImmutableID. If the incoming object’s anchor equals an existing cloud object’s ImmutableID, they are bound together. This is precise and predictable, and it is what you want for migrations.

Soft match is the fallback. When no ImmutableID matches, Entra tries the incoming object’s UPN or primary SMTP address against existing cloud objects. There is one rule you must respect: The cloud object you want to soft match against has to have an empty ImmutableID. If it already carries an ImmutableID from a different on-prem object, you get the dreaded InvalidSoftMatch error, which is Entra correctly refusing to staple two unrelated identities together. Details here: Troubleshoot errors during synchronization.

How to hard match an existing cloud user to an AD account

  1. Get the Base64 ImmutableID from the AD object using the conversion above.
  2. Connect with Microsoft Graph PowerShell. The legacy MSOnline and AzureAD modules are retired, so use Graph:
   Connect-MgGraph -Scopes "User.ReadWrite.All"
   Update-MgUser -UserId "jdoe@contoso.com" -OnPremisesImmutableId "3jFgh7G/i0+csBxnpglNPg=="
  1. Make sure the AD object is in scope for sync, then force a sync cycle:
   Start-ADSyncSyncCycle -PolicyType Delta
  1. Confirm the result in the Synchronization Service Manager and in the Entra admin center, where the user should flip to On-premises sync enabled: Yes.

A couple of things to know before you do this at scale. You can set OnPremisesImmutableId on a cloud-only (managed) user, but the operation fails on an object that is already synced from another directory. And the cloud object you are matching to must not already own a different ImmutableID. If the user stubbornly stays “In cloud” after a sync, check three things: Connect export errors, that the UPN and proxyAddresses are unique and consistent between the two objects, and that the AD object really is in sync scope. Microsoft’s walkthrough for the existing-tenant case is here: Connect when you already have a tenant.

⚠️ Hard matching is unforgiving. Set the wrong ImmutableID on the wrong cloud user and you have just bound two people together. For bulk work, drive it from a validated CSV and test on one or two accounts before you let it loose on hundreds.

“Can’t I just flip isCloudManaged like I do with mailboxes?”

This question comes up now that Source of Authority is a thing, and the answer matters.

No, and it points the wrong direction. Setting isCloudManaged = true on an object is the User (or Group) Source of Authority feature, now generally available, and it needs Connect Sync 2.5.76.0 or later, or Cloud Sync agent 1.1.1370.0 or later. It takes an object already synced from AD and hands authority to Entra, so sync stops pushing changes down and you manage the identity in the cloud. That is an AD minimisation play for organisations retiring AD. It is not a tool for linking a fresh cloud user to an on-prem account, and for users there is still no provisioning back into AD, so you cannot use it to claim a migrated cloud user into a new forest either.

You may be confusing this with its sibling in Exchange Online, IsExchangeCloudManaged, set with Set-Mailbox -IsExchangeCloudManaged $true, which needs Connect Sync 2.5.190.0 or later. It transfers the Source of Authority for the Exchange attributes to the cloud so you can edit them in Exchange Online, while identity attributes stay managed from AD. One caveat worth flagging: Writeback of designated Exchange attributes to on-prem AD is now in public preview through Cloud Sync, with GA targeted for late June 2026, so this is no longer strictly a one-way street. Useful for retiring your last Exchange Server, still not a matching mechanism. Cloud-based management of Exchange attributes for Remote Mailboxes in hybrid environments | Microsoft Learn

So treat Source of Authority as the endgame, and not as the matching step. Once your identities are matched, stable, and you genuinely want to cut the AD dependency without deleting and recreating accounts, that is when SOA earns its place. Overview here: Transfer user Source of Authority to the cloud.

With the mechanics out of the way, let us walk the three topologies.

Scenario 1: Two tenants, one AD

You have a single on-prem forest and you are moving between two tenants, often because of a carve-out, a rebrand, or a consolidation onto a different tenant. The good news for matching: Both tenants derive the ImmutableID from the same AD anchor, so the value you hard match into the target tenant is identical to the one in the source. The matching itself is the easy part here.

You can sync one forest to two tenants, with a separate sync configuration per tenant. What trips people up is everything around the match:

  • 🚨 A given verified custom domain can only live in one tenant at a time. You cannot have contoso.com verified in both tenants simultaneously.
  • Hybrid join is per tenant. The service connection point in AD points at a single tenant, so a device can only Hybrid Entra join one of them. If both tenants need joined devices during a transition, you have a device strategy problem to solve separately.
  • Do not confuse this with an unsupported layout. Two tenants, two sync configs is fine. Two Connect Sync servers pointing at the same single tenant is not supported, the only exception being a staging server.

💡 Pro tip: Pre-stage the match before you flip the domain. Set the ImmutableID on the target tenant’s cloud users while everything is still calm, validate the decoded values line up, and you turn cutover day into a non-event.

Scenario 2: One tenant, two ADs

This is the classic acquisition. You have a tenant synced from your forest, you buy a company, and now their forest needs to land in your existing tenant. Syncing multiple forests into a single tenant is fully supported, either with one Connect Sync server that can reach all forests, or with your existing Connect Sync forest plus the new forest brought on using Cloud Sync. Microsoft’s topology guidance covers both: Connect supported topologies and Cloud Sync supported topologies.

Here is the difference that decides whether you get clean matches or duplicates, and it is the single most important Connect Sync versus Cloud Sync distinction in this whole post:

Connect Sync can match the same person across two forests. Cloud Sync cannot.

Connect Sync’s multi-forest configuration lets you declare that a user is represented only once across all forests and match them on an attribute, for example the mail attribute, or ObjectSID plus msExchMasterAccountSID in an account-resource forest setup. That is exactly what you need when the same human exists in both directories and you want a single cloud identity instead of two.

Cloud Sync does not do this. It performs no matching across forests, and it expects each identity to be represented once and uniquely across everything it syncs. Point Cloud Sync at two forests that both contain the same person and you get two separate cloud objects, unless you have manually lined up the ms-DS-ConsistencyGuid in advance so the second forest’s object hard-matches the existing cloud object.

So the decision rule is simple:

  • If the two forests contain overlapping people you want collapsed into one cloud identity, this is a Connect Sync job with multi-forest matching configured.
  • If the populations are disjoint, Cloud Sync is perfectly fine and a lot lighter to run.

Where multi-forest matching is set

You do not toggle this later. It is configured once, during Connect Sync installation, on the Uniquely identifying your users page:

  1. Run Connect Sync setup in Customize mode, or rerun setup when you add the second forest.
  2. On Uniquely identifying your users, choose whether a person is represented only once across all forests or exists across multiple directories. Pick the latter when the same human has an account in both forests.
  3. Under Match using, select the attribute Connect should match on: Mail for GALSync-style setups, or ObjectSID and msExchMasterAccountSID for the classic account-resource forest with linked mailboxes. A specific attribute is available if neither fits.
  4. Finish the wizard and run a full sync cycle.

🚨 Get this right at install. The source anchor in particular is fixed once objects have synced, and reworking the matching after objects are already joined means disconnects and churn, so it is not something you want to be changing on a live directory.

When the two ADs eventually consolidate for real and objects physically move between forests, you are back to the anchor principle from the top of this post. Carry ms-DS-ConsistencyGuid across the move and the existing cloud matches survive. Skip that and every migrated object looks brand new to Entra.

How to prepare the anchor before a cross-forest move

Populate ms-DS-ConsistencyGuid in the source forest from the existing objectGUID, so a migration tool has a stable value to carry over:

Get-ADUser -Filter * -SearchBase "OU=SyncedUsers,DC=contoso,DC=com" `
  -Properties objectGUID, "mS-DS-ConsistencyGuid" |
  Where-Object { -not $_.'mS-DS-ConsistencyGuid' } |
  ForEach-Object {
    Set-ADObject -Identity $_.DistinguishedName `
      -Replace @{ 'mS-DS-ConsistencyGuid' = ([guid]$_.objectGUID).ToByteArray() }
  }

Then, during the inter-forest migration, make sure the migration process copies ms-DS-ConsistencyGuid to the target object. A tool like ADMT does this for user objects. After the move, the target object has a new objectGUID but the same consistency GUID, so the ImmutableID is unchanged and the cloud match holds.

Connect already populates ms-DS-ConsistencyGuid for users within the sync scope in the source forest, so for them the script above is optional. Groups are different: Connect never stamps them, so populating ms-DS-ConsistencyGuid yourself is the only way a group has a stable value to carry across the move. If your migration includes groups, run the same stamping against them first:

Get-ADGroup -Filter * -SearchBase "OU=SyncedGroups,DC=contoso,DC=com" `
  -Properties objectGUID, "mS-DS-ConsistencyGuid" |
  Where-Object { -not $_.'mS-DS-ConsistencyGuid' } |
  ForEach-Object {
    Set-ADObject -Identity $_.DistinguishedName `
      -Replace @{ 'mS-DS-ConsistencyGuid' = ([guid]$_.objectGUID).ToByteArray() }
  }

Scenario 3: The combination, two tenants and two ADs

This is real merger and acquisition life, and it is just the previous two scenarios stacked on top of each other. The cloud-to-cloud movement is handled by a migration tool or by Entra cross-tenant sync. The on-prem landing is handled by anchor management. Nothing new conceptually, but one practical point catches teams out every time.

🚨 Your migration tool must preserve the anchor, and I can’t promise that all of them do so by default. Some tenant-to-tenant tooling may not map ms-DS-ConsistencyGuid out of the box, which means it is silently dropped during migration. The target object then gets a freshly generated ImmutableID, and your tenant-to-tenant match on ImmutableID fails, spawning duplicates. The fix is to add explicit attribute-mapping logic in the tool so the consistency GUID is carried (or seeded from the source objectGUID when empty, without overwriting an existing target value).

So before you trust any migration tool in a combined scenario, ask it one question: Does it carry ms-DS-ConsistencyGuid? If you cannot answer that confidently, you do not yet know whether your matches will survive.

The gotchas that will actually bite you

A couple of traps that are not obvious:

  • Group membership is the part to watch. Provided you carried the group’s ms-DS-ConsistencyGuid across the move yourself, which Connect will not do for you as noted earlier, the group object reconnects to its cloud counterpart. But membership is projected from a single forest, so if the target forest’s copy of the group does not hold the full membership, the sync engine can drop members in the cloud. In practice the inter-forest migration tool that moves the group usually carries its membership across too, which avoids this, but that behaviour varies between tools, so confirm yours does it rather than assuming.
  • Cloud Sync has no staging mode. If your migration playbook leans on staging-server dry runs, that is a Connect Sync capability. Plan accordingly if Cloud Sync is in the mix.

Connect Sync versus Cloud Sync at a glance

For the matching decisions specifically, here is the short version:

BehaviourConnect SyncCloud Sync
Default user anchorms-DS-ConsistencyGuid (objectGUID fallback)ms-DS-ConsistencyGuid if present, else objectGUID
Match same person across forestsYes, with multi-forest matching configuredNo, expects each identity once
Multiple disconnected forests, one tenantYes, server must reach all forestsYes, lightweight agents per forest
Hard match by ImmutableIDYesYes
Staging server dry runYesNo

This is not a “Cloud Sync is worse” table. Cloud Sync is the better answer for disconnected forests with disjoint populations and for shrinking your on-prem footprint. It is just a different tool with different matching behaviour, and treating it like Connect Sync is how you end up with duplicates.

Wrapping up

The monster above is less fictional than it looks. Get the matching wrong and the duplicate you create is a real account that real people have to live with. A user signs in and lands on the wrong object, the one missing their mailbox and their licenses. Their mail starts arriving on an identity they cannot see, and the apps and files they need are tied to the other account. They open a ticket, then another, and the help desk cannot tell which of the two identical-looking accounts is the real one any better than they can. Multiply that across a migration wave and you have the cleanup nobody scoped, now with an audience of frustrated users and a manager asking why the project broke everything.

All of that traces back to one thing, so if you take only one thing from this post, take the anchor. Choose ms-DS-ConsistencyGuid, make sure it is populated, and carry it across every forest and tenant boundary the project crosses. The ImmutableID is the thread that holds a cloud identity to its on-prem origin, and your entire job during a migration or merger is making sure that thread never silently snaps.

Around that, the rest is discipline. Decode your matches and check the values for real instead of assuming they line up. Pick Connect Sync or Cloud Sync based on whether you actually need matching across forests. Confirm your migration tool carries the consistency GUID before you trust it with a single user. And remember that Source of Authority is where you go after the matching is solid and stable, when you are ready to cut the cord to AD, not a shortcut around the matching itself.

As always: Every tenant and every forest is a little different, so test all of this in your own environment before you trust it in production. Assume something will behave unexpectedly, because in hybrid identity, something usually does.

Do that, and the two-headed monster stays on the poster where it belongs. Good luck with the migration, and may your user count in the target tenant be exactly what you expect it to be.

Thank you all for reading, and as always, test before you trust!

Author

  • Per-Torben Sørensen has 27 years of experience in IT and Microsoft infrastructure. He is currently a Microsoft Most Valuable Professional (MVP) within Identity & Access, a Microsoft Certified Trainer (MCT) and works as a Senior Architect within M365 at SoftwareOne. His passion is Entra ID and Identity and access management and helps customers become "copilot-ready". He's also an engaged speaker and is always eager to share his knowledge and learn from others.

    View all posts

Discover more from Agder in the cloud

Subscribe to get the latest posts sent to your email.

By Per-Torben Sørensen

Per-Torben Sørensen has 27 years of experience in IT and Microsoft infrastructure. He is currently a Microsoft Most Valuable Professional (MVP) within Identity & Access, a Microsoft Certified Trainer (MCT) and works as a Senior Architect within M365 at SoftwareOne. His passion is Entra ID and Identity and access management and helps customers become "copilot-ready". He's also an engaged speaker and is always eager to share his knowledge and learn from others.

Leave a Reply