M365 Migration Outlook Repair Toolkit
Repairing the client side after an on-premises Exchange to Microsoft 365 cutover.
View on GitHubThe problem this exists for
The mailbox moves cleanly. The server side reports success. Then Monday arrives and a third of the users cannot open Outlook, and every one of them describes it differently: a password box that will not accept the right password, a client stuck on disconnected, an error saying it cannot reach an Exchange server that no longer exists.
None of that is a migration failure. It is the client refusing to accept that the mailbox moved, for four distinct reasons that look identical from the help desk.
The four root causes
- Cached credentials. Windows still holds a stored password for the old server, so Outlook keeps presenting it, keeps getting rejected, and keeps asking the user to type it again. The user is not wrong. The credential store is.
- Autodiscover cache and registry redirects. The client resolves to the deprecated infrastructure because a cached XML file or a registry redirect answers before anything else gets a chance. This is the one that survives a reboot and outlives most attempts to fix it.
- Modern authentication disabled by policy. An old policy key suppresses the Microsoft 365 sign in interface entirely, so the user never sees the window they need to complete.
- Broken MAPI profiles. The profile is bound to a server that is gone. No amount of credential clearing helps, because the profile itself has to be rebuilt.
Any one of these produces a ticket. Two of them together produce a ticket that gets escalated, because the first fix appears to work and then the symptom returns the next morning.
Design constraints
Two constraints shaped everything.
It has to run as a standard user. If the tool needs administrator rights, it needs a deployment mechanism, an approval, and a person from the team to run it. That turns a two minute fix into a scheduled visit. Every repair in the toolkit was chosen or rewritten so it works in the user's own context.
It has to have no dependencies. PowerShell 5.1, which ships with Windows, and nothing else. No modules to install, no execution policy negotiation, no version drift between machines. A user double clicks a .cmd file and the thing runs.
The safety model
This is a tool that deletes Outlook profiles and edits the registry on machines belonging to people who did not ask for it. The safety design is not a feature, it is the price of being allowed to exist.
- Dry run previews come before any modification, and dry run is the default posture rather than an opt in.
- The registry is backed up before an edit, not after the decision to edit.
- Every written value is read back and verified. A write that silently failed is worse than one that loudly failed, because it is reported as a fix.
- PST inventory is saved before profile removal, so nothing detaches a file that nobody remembers was attached.
- The script exits with code 1 on failure. It never reports success it did not achieve.
That last point is the one I would argue for hardest. A repair script that always exits zero is worse than no script, because it converts a visible problem into a machine that has been marked as fixed.
The eight repair steps
- Remove cached credentials. Clears the stored passwords still pointing at the decommissioned server.
- Sign out the old Office identity. Drops the licensing identity tied to the previous tenant.
- Correct registry policy. Re-enables modern authentication where policy disabled it.
- Clear the Autodiscover cache. Removes cached XML and registry redirects pointing at old infrastructure.
- Inventory profiles. Lists every MAPI profile before anything is touched.
- Rebuild the broken profile. Removes the profile behind "cannot connect to Exchange server".
- Reattach PST files. Saves the inventory first, then reattaches to the rebuilt profile.
- Report device state. Prints Entra join and registration state, usually the answer when one machine behaves differently.
Two entry points, two audiences
End users get START.cmd to repair and DIAGNOSE.cmd to collect information. Two files, no parameters, no decisions.
Administrators get the underlying script with real control:
.\Fix-M365MigrationOutlook.ps1 -Steps All -DryRun
.\Fix-M365MigrationOutlook.ps1 -Steps All -Force
.\Fix-M365MigrationOutlook.ps1 -RemoveProfile 'Outlook'
.\Fix-M365MigrationOutlook.ps1 -OldAccount 'user@olddomain.com'
.\Fix-M365MigrationOutlook.ps1 -CachedMode Off
Splitting the interfaces this way was the single most useful decision. The user facing path cannot do anything dangerous because it cannot be given a dangerous instruction, and the administrator path does not have to be defensive about a user pasting a flag they read in a forum.
What it is made of
Fix-M365MigrationOutlook.ps1is the repair engine.Test-Blocker.ps1collects diagnostics without changing anything, which is what you want in hand before you decide what to change.START.cmdandDIAGNOSE.cmdare the user entry points.- A Pester test suite, because a script that edits the registry on other people's machines should have tests, and because it is the only way to keep the dry run path honest as the tool grows.
What I would change
The step selection is a flat list. In practice the steps have an order and some of them make others unnecessary, and the tool does not know that. Running everything is safe but slower than it needs to be, and a smarter version would diagnose first and then run only the steps the diagnosis implicates.
That is the next version. The current one is deliberately dumb, and being dumb is why I trust it on a machine I cannot see.