DEV Community

Cover image for Solved: Botched Domain Migration in Jan 2024 – Just Discovered the Damage. How Do I Fix This?
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Botched Domain Migration in Jan 2024 – Just Discovered the Damage. How Do I Fix This?

🚀 Executive Summary

TL;DR: A botched domain migration often leads to ‘Access Denied’ errors because applications store user permissions by old, orphaned Active Directory Security Identifiers (SIDs). The fix involves diagnosing new SIDs and updating application databases, with Active Directory Migration Tool (ADMT) and sIDHistory migration being the crucial preventative measure.

🎯 Key Takeaways

  • Domain migrations can cripple applications by creating ‘orphaned’ user accounts, where application databases still reference old Active Directory Security Identifiers (SIDs) instead of new ones.
  • The Active Directory Migration Tool (ADMT) is critical for domain migrations, specifically its feature to migrate sIDHistory, which stamps a user’s old SID onto their new account, ensuring applications recognize them.
  • Solutions for SID mismatch issues range from emergency manual SQL updates for critical users, to scalable PowerShell scripts for bulk database remediation, or a ‘Nuke and Pave’ strategy involving database restoration and re-migration as a last resort.

A botched domain migration can leave your applications crippled by “orphaned” user accounts linked to old SIDs. This guide provides a senior engineer’s playbook for diagnosing the SID mismatch issue and implementing realistic fixes, from emergency SQL patches to permanent, scripted solutions.

We Botched a Domain Migration. Here’s How to Fix the Lingering Damage.

I still remember the knot in my stomach. It was a Tuesday morning, and the Director of Sales was on the phone, his voice a little too calm. “Darian, none of my regional managers can access the Q1 forecast dashboard. It just says ‘Access Denied’. This was working on Friday.” My coffee suddenly tasted like battery acid. We had just finished a “seamless” domain migration over the weekend. Turns out, the LOB application that ran the dashboard stored user permissions by their Active Directory Security Identifier (SID), and we had just created thousands of new SIDs for everyone, leaving the old ones orphaned in the application’s database. That was a long, long day of manual SQL updates and a career-defining “learning experience.”

Seeing a recent Reddit thread about this exact problem from January brought it all rushing back. Someone’s team migrated from CORP.LOCAL to a new domain, and months later, they’re discovering the fallout. If you’re in that boat, take a deep breath. You’re not the first, and you won’t be the last. Let’s get this sorted.

First, Let’s Understand the “Why”

This isn’t just about a username changing. In the world of Windows and Active Directory, your identity isn’t just DOMAIN\username. The real key to the kingdom is your Security Identifier (SID). It’s a unique, non-reusable string of characters that AD assigns to a user, group, or computer.

When you migrated from the old domain to the new one, every user effectively got a brand-new SID. Your application, however, still has the old SID stored in its user permissions table. So, when NEWDOMAIN\jsmith tries to log in, the application looks up their permissions, finds nothing for their new SID, and promptly tells them to get lost. The user exists, but their link to their permissions is broken.

Pro Tip: This is why tools like the Active Directory Migration Tool (ADMT) are critical. They have features to migrate sIDHistory, which stamps the user’s old SID onto their new account. This allows applications to recognize the user by either their new SID or their old one, providing a much smoother transition.

The Recovery Playbook: Three Tiers of Fixes

Depending on the scale of the damage and the time you have, there are a few ways to approach this. We’ll go from the quick band-aid to the proper surgical fix.

1. The Triage: A Quick and Dirty SQL Fix

This is your emergency, “get the CEO back online” solution. It’s manual, it doesn’t scale, but it works in a pinch. The goal is to find the old, orphaned SID in your database and manually replace it with the new, correct one.

Step 1: Get the user’s NEW SID.

You can do this with a quick PowerShell command on a domain controller:

Get-AdUser -Identity 'jsmith' | Select-Object SID
Enter fullscreen mode Exit fullscreen mode

Let’s say this returns S-1-5-21-1234567890-123456789-1234567890-5512.

Step 2: Find the user’s OLD SID in the database.

You’ll need to know your application’s schema, but you’re probably looking for a Users or Permissions table. You’ll have to find the record by username.

SELECT UserSID, UserName, UserId
FROM dbo.ApplicationUsers
WHERE UserName = 'CORP\jsmith';
Enter fullscreen mode Exit fullscreen mode

This might return the old, orphaned SID: S-1-5-21-9876543210-987654321-9876543210-4891.

Step 3: Update the record.

Now, perform a targeted UPDATE. Always wrap this in a transaction!

BEGIN TRANSACTION;

UPDATE dbo.ApplicationUsers
SET UserSID = 'S-1-5-21-1234567890-123456789-1234567890-5512' -- The NEW SID
WHERE UserName = 'CORP\jsmith' 
AND UserSID = 'S-1-5-21-9876543210-987654321-9876543210-4891'; -- The OLD SID

-- COMMIT; 
-- ROLLBACK; -- Always have your escape hatch ready.
Enter fullscreen mode Exit fullscreen mode

This is fast for one or two users, but a nightmare for hundreds.

2. The Scalpel: A Permanent, Scripted Solution

This is the proper engineering solution. We’re going to write a script to do the heavy lifting. The logic is simple: for each user in our database, we query the new Active Directory for their new SID and update the database record. This is perfect for cleaning up the entire user base in one controlled, repeatable process.

Here’s a conceptual PowerShell script to show the logic. This assumes you’re running it from a machine that can talk to both your database and your new domain controllers.

# --- DISCLAIMER: THIS IS A CONCEPTUAL SCRIPT. TEST THOROUGHLY! ---

# Import necessary modules
Import-Module ActiveDirectory
Import-Module SqlServer

# Database connection details
$sqlInstance = "prod-db-01"
$database = "AppDatabase"

# Get all users from the app database with old domain prefix
$query = "SELECT UserId, UserName, UserSID FROM dbo.ApplicationUsers WHERE UserName LIKE 'CORP\%'"
$appUsers = Invoke-Sqlcmd -ServerInstance $sqlInstance -Database $database -Query $query

# Loop through each user and fix them
foreach ($user in $appUsers) {
    # Extract the username (e.g., 'jsmith' from 'CORP\jsmith')
    $samAccountName = ($user.UserName -split '\\')[1]

    try {
        # Find the user in the NEW domain
        $adUser = Get-ADUser -Identity $samAccountName -ErrorAction Stop
        $newSID = $adUser.SID.Value

        # If the SIDs don't match, update the database
        if ($newSID -ne $user.UserSID) {
            Write-Host "Fixing user: $($user.UserName)... OLD SID: $($user.UserSID), NEW SID: $newSID"

            # Construct and run the update query
            $updateQuery = "UPDATE dbo.ApplicationUsers SET UserSID = '$newSID' WHERE UserId = $($user.UserId)"
            # Invoke-Sqlcmd -ServerInstance $sqlInstance -Database $database -Query $updateQuery
            Write-Host "--> (Simulated) Update for $samAccountName complete."
        }
    }
    catch {
        Write-Warning "Could not find user '$samAccountName' in the new domain. Skipping."
    }
}
Enter fullscreen mode Exit fullscreen mode

CRITICAL WARNING: Test this script against a restored copy of your production database first. A tiny logic error in a script like this can cause catastrophic, resume-generating damage. Test, test, and test again before uncommenting that Invoke-Sqlcmd line.

3. The ‘Nuke and Pave’: When All Else Fails

Sometimes the damage is too widespread, the database schema is too complex, or you have zero confidence in your data’s integrity. This is the last resort.

The “Nuke and Pave” involves:

  • Restore: Restore the application database from the last known-good backup taken *before* the domain migration.
  • Halt: Take the application offline to prevent any new data from being written.
  • Re-Migrate Correctly: Use a tool like ADMT to re-migrate the users, but this time, ensure you are migrating the sIDHistory attribute.
  • Re-Launch: Once the identity foundation is correct (users have their old SID in their history), bring the application back online. Users from the new domain should now be recognized by the application because it can resolve their identity via their SID History.

This option means data loss (anything entered between the migration and the restore is gone) and significant downtime. It’s a painful, high-visibility option, but sometimes it’s the only way to be 100% sure you have a clean slate.

Comparing the Solutions

Solution Speed Risk Scalability Effort
1. The Triage (SQL Fix) Very Fast (per user) Low (if careful) Very Poor Low
2. The Scalpel (Scripted) Fast (for all users) High (if not tested) Excellent Medium
3. The Nuke and Pave Very Slow (days) Extreme (data loss) N/A Very High

My advice? Start with the Triage for your most critical users to stop the bleeding. While they’re back to work, you can develop and, more importantly, test the Scalpel solution to fix everyone else. The Nuclear Option should only be on the table if you suspect this SID issue is just the tip of a much larger iceberg of migration-related data corruption.

Good luck. And next time, let’s get that sIDHistory migration checked off the list *before* go-live.


Darian Vance

👉 Read the original article on TechResolve.blog


Support my work

If this article helped you, you can buy me a coffee:

👉 https://buymeacoffee.com/darianvance

Top comments (0)