[AD 3.1] Build Sheet: Converting to Cloud-Only and Disabling Directory Synchronization

The pilot-wave method for the one-way door: transfer a handful of users to cloud-managed, verify, then cut the sync for the tenant and clean up what lingers. Rollback shown where it genuinely exists, and the point of no return named plainly.


By the end of this you have transferred a pilot wave of users to cloud-managed and watched them behave, cut directory synchronization for the whole tenant with the Connect server retired first, cleared the on-premises identifiers that linger, made the password-expiry decision on purpose, and confirmed the tenant stands with nothing on-premises behind it. The irreversible step is fenced by a rehearsal you can roll back, and the one moment after which there is no going back is named where it happens. This assumes the tenant is already on password hash sync and the Connect server is on a current build.


Before Step 1: Hybrid Identity Administrator, the Microsoft Graph PowerShell module, and a maintenance window for the tenant-wide cut even though the work itself is quick, because the backend conversion runs afterward on its own clock. Keep the Stage 0 inventory open; the users you pick for the pilot should be ordinary desk users whose rows carried no on-premises dependency.

Step 1: Connect with the right scope

The whole operation is Graph, because there is no admin-center screen for per-user conversion. Connect once with the permissions the pilot and the cut both need.

Connect-MgGraph -Scopes "User-OnPremisesSyncBehavior.ReadWrite.All",
  "User.ReadWrite.All", "Organization.ReadWrite.All", "Directory.ReadWrite.All"

Step 2: Transfer the pilot wave

Pick three users who came out of Stage 0 with no on-premises dependency of their own. Transfer authority for each to the cloud, one object at a time. This is the rehearsal, and it is reversible. The transfer is a write to the object’s own onPremisesSyncBehavior resource, not to the user object, so it goes through a direct Graph request rather than through a user cmdlet.

$pilot = "[email protected]","[email protected]","[email protected]"
foreach ($upn in $pilot) {
  $id = (Get-MgUser -UserId $upn).Id
  Invoke-MgGraphRequest -Method PATCH `
    -Uri "https://graph.microsoft.com/v1.0/users/$id/onPremisesSyncBehavior" `
    -Body @{ isCloudManaged = $true }
  Write-Host "Transferred $upn"
}

Each transferred user is now mastered in the cloud while the other hundred and thirty-seven keep syncing from Active Directory as before. Nothing about their sign-in changed. What changed is where their identity is edited.

Step 3: Verify the pilot, over a real interval

Leave the pilot users converted for a working week, not an afternoon. Confirm each object now reports itself cloud-managed, then confirm the human experience: the pilot users sign in, reach their mail, reach their files, and use whatever line-of-business tools their role needs. A conversion that is clean on paper and broken at the desk is a conversion you want to find now, on three people you warned, rather than on the whole company.

foreach ($upn in $pilot) {
  $id = (Get-MgUser -UserId $upn).Id
  $soa = Invoke-MgGraphRequest -Method GET `
    -Uri "https://graph.microsoft.com/v1.0/users/$id/onPremisesSyncBehavior?`$select=isCloudManaged"
  "{0}  cloud-managed={1}" -f $upn, $soa.isCloudManaged
}

Step 4: Roll back, if the rehearsal said to

This is the only step in the stage with a genuine undo, which is exactly why the pilot exists. If a pilot user broke, hand authority back. The object returns to cloud-managed false, and the sync engine reclaims it on its next cycle rather than instantly, so give it one.

$id = (Get-MgUser -UserId "[email protected]").Id
Invoke-MgGraphRequest -Method PATCH `
  -Uri "https://graph.microsoft.com/v1.0/users/$id/onPremisesSyncBehavior" `
  -Body @{ isCloudManaged = $false }

The block on cloud-object takeover is on by default, and while it is on the sync engine will not reclaim the object, so a rollback that appears to stall is almost always this. Turn it off, let a sync cycle run, confirm the object is back under on-premises control, then turn it back on. Leaving it off is a standing exposure, not a tidy-up you can defer.

# $syncId is the tenant's on-premises directory synchronization configuration id
$syncId = (Invoke-MgGraphRequest -Method GET `
  -Uri "https://graph.microsoft.com/beta/directory/onPremisesSynchronization").value[0].id

Invoke-MgGraphRequest -Method PATCH `
  -Uri "https://graph.microsoft.com/beta/directory/onPremisesSynchronization/$syncId" `
  -Body @{ features = @{ blockCloudObjectTakeoverThroughHardMatchEnabled = $false } }

# after the reclaim is confirmed, put the protection back
Invoke-MgGraphRequest -Method PATCH `
  -Uri "https://graph.microsoft.com/beta/directory/onPremisesSynchronization/$syncId" `
  -Body @{ features = @{ blockCloudObjectTakeoverThroughHardMatchEnabled = $true } }

Do not proceed to the tenant-wide cut until every pilot user is either verified good or rolled back and understood. The pilot is the gate on the gate.

Step 5: Retire the Connect server first

Order matters here and the wrong order leaves the tenant in a confusing half-state. Uninstall the sync client from the Connect server before you disable synchronization at the tenant, not after. On the Connect server, uninstall through Programs and Features, or let the wizard convert the tenant as it exits if it offers to; either way the engine stops before you throw the tenant switch. Export the server’s configuration first if you want a record of the sync rules, then shut the machine down and leave it off. Everything from here is meant to work without it, and a Connect server sitting powered on is a Connect server somebody can start again by accident.

Step 6: Cut the sync for the tenant. This is the point of no return.

Everything to here was reversible. This is not. Disabling directory synchronization starts a backend transfer of every remaining synced object to cloud-managed, it runs for up to seventy-two hours, it cannot be cancelled, and re-enabling is blocked until it finishes and is not an undo in any case. Say it out loud to whoever needs to hear it, then run it.

$org = (Get-MgOrganization).Id
Update-MgOrganization -OrganizationId $org `
  -BodyParameter @{ onPremisesSyncEnabled = $false }

On a tenant this size the conversion is usually quick, minutes to a few hours rather than the full envelope, but do not schedule anything against its completion; wait for the portal to show synchronization disabled and the objects cloud-managed before you move on. The pilot users, already converted, are unaffected; everyone else joins them.

Step 7: Clear what lingers

Some on-premises linkage identifiers survive the cut and can confuse a later device operation if left. The supported cleanup tool is ADSyncTools, version 2.5.0 or later, and it works against Graph rather than against the sync engine, so it runs from an ordinary admin workstation with PowerShell 7 after the Connect server is gone. Back up the current values before you clear them, so the cleanup is itself reversible in the narrow sense that you can see what was there. The clear runs per object, so the whole-tenant form pipes the get into the clear; a bare -All with no object named will simply sit there asking you which one.

Install-Module ADSyncTools -Scope CurrentUser   # 2.5.0 or later
Import-Module ADSyncTools

Get-ADSyncToolsOnPremisesAttribute | Export-Csv .\onprem-attrs-before.csv -NoTypeInformation
Get-ADSyncToolsOnPremisesAttribute | Select-Object id |
  Clear-ADSyncToolsOnPremisesAttribute -All -Verbose

Step 8: Deal with the never-expiring passwords

Synced users carry a flag that disables password expiry, set because expiry used to be an on-premises concern, and the cut does not clear it. Decide deliberately: if your cloud policy expires passwords, clear the flag; if you are moving to a passwordless or long-lived-passphrase posture, leave it on as a conscious choice and record why. Either way, make it a decision rather than an inheritance.

# see who still carries the never-expire flag
Get-MgUser -All -Property UserPrincipalName,PasswordPolicies |
  Where-Object { $_.PasswordPolicies -match 'DisablePasswordExpiration' } |
  Select-Object UserPrincipalName

Step 9: Remove the seamless single sign-on artifact

Seamless single sign-on left a computer account in Active Directory, and it should come out while a domain controller is still standing, because the removal reaches into AD. This does not affect modern sign-in, which rides a different mechanism entirely; it only retires the legacy convenience feature you no longer use.

One provenance note that will otherwise strand you. The module that does this, AzureADSSO.psd1, ships inside the Entra Connect installation folder, which Step 5 just uninstalled. Either run this step before you uninstall in Step 5, which is the tidier order, or download the Entra Connect installer onto any domain-joined machine and let the MSI unpack without running the configuration wizard, which is Microsoft’s own documented workaround for exactly this situation. The command also needs credentials: domain administrator for the forest, supplied in SAM account name format such as catsnackjack\admin, from an account that is not a member of Protected Users.

cd "$env:ProgramFiles\Microsoft Azure Active Directory Connect"
Import-Module .\AzureADSSO.psd1

New-AzureADSSOAuthenticationContext           # sign in as Hybrid Identity Administrator
Get-AzureADSSOStatus | ConvertFrom-Json       # confirm which forests are enabled

$creds = Get-Credential                       # domain admin, SAM format: catsnackjack\admin
Disable-AzureADSSOForest -OnPremCredentials $creds   # removes the AZUREADSSOACC computer account

# if AD is already unreachable, name the forest instead of passing credentials
# Disable-AzureADSSOForest -DomainFqdn catsnackjack.com

Step 10: Prove the tenant stands alone

The end-to-end test for this stage is the Connect server being off while everything works. It has been off since Step 5, so this confirms rather than acts: have a real user sign in, reach mail and files, and open the line-of-business application, all while the Connect server stays dark. Check the tenant reports synchronization disabled and no sync errors are accruing. If anything depends on the sync you just cut, this is where it surfaces, and it surfaces while the Connect server is merely off rather than gone, which is the recoverable version of the problem.

CheckExpected
Tenant sync stateDirectory synchronization disabled, no accruing sync errors
A real user, Connect server offSigns in, reaches mail and files, opens the LOB app
Pilot usersIndistinguishable from the rest of the tenant
Lingering identifiersCleared, with the before-state exported
Password expirySet deliberately, decision recorded
Seamless SSO accountRemoved while a DC still exists

The gate, and what carries into Stage 2

Stage 1 closes when every row in that table checks out and the Connect server has been off long enough that you trust the tenant without it. CatSnackJack held the Connect server dark for a week after the cut before decommissioning it, which is cheap insurance against a dependency that only fires monthly. The users are cloud-native now, which means the next problem is the devices those users sign in on, because a hybrid-joined laptop is still reaching for a domain that is about to stop answering. That is Stage 2, and it is the stage with no in-place shortcut, so it is worth arriving at it rested.


AD to Azure
‹ Previous: [AD 3] Identity: Cutting the Sync
Next: [AD 4] Devices: The Rebuild You Were Hoping to Avoid