Recovering From a Failed Dynamics 365 Migration: Missing Data, Angry Users, Audit Pending
A troubleshooting reference for data that is missing or wrong after go live: stop the damage, reconcile by entity, recover what can be recovered and document the rest honestly.
What do you do when a Dynamics 365 migration has left data missing or wrong and an audit is coming? First, stop making it worse: freeze writes to the affected tables, pause the loads, flows and plug-ins that touch them, take a manual Dataverse backup, and confirm the source data still exists before anyone re-runs anything. Then reconcile: count and checksum every entity between source and target, classify each gap as not migrated, partially migrated or transformed wrong, and fix them in that order. A Dataverse restore rolls back a whole environment, never a single table, so it is rarely the fix. Tell the auditor what happened, with numbers, instead of quietly re-importing and hoping.
What should you do in the first hours after a Dynamics 365 migration goes wrong?
Stop the damage growing before you try to repair any of it. The most expensive mistake in a failed migration is a well meant re-run: somebody deletes the loaded rows and runs the job again, a scheduled delta keeps writing over corrections, or a user merges duplicates that were actually two different customers. Each of those destroys the evidence of what went wrong and sometimes the only copy of the correct data.
The generic crisis steps for a failing Dynamics 365 project, including freezing change, exporting what the business cannot lose and securing administrative access, are set out in our Dynamics 365 rescue guide. For a migration specifically, the first hours come down to the three decisions below.
| Decision | Do | Do not |
|---|---|---|
| Writes | Pause every load, integration, flow and plug-in step that writes to the affected tables, and log each one. | Delete the loaded rows and re-run the job "to start clean". Deleted rows lose their GUIDs, their relationships and any user edits with them. |
| Restore point | Take a labelled manual backup and note when your restore window closes. | Restore production to a pre-migration backup before you know what users have entered since go live. |
| Source | Confirm the source is still readable and until when, and keep the staging data and load logs untouched. | Let the old system be switched off or its licence lapse on the date somebody agreed before the problem was found. |
What are the steps to recover missing or corrupted data after a failed migration?
This is the order we work in on a failed migration. The first three steps preserve what can still be recovered, the next three establish what is actually wrong and why, and the last two repair it and prove the repair.
- Step 1. Freeze writes to the affected tables: Turn off any scheduled delta or incremental load, pause the flows, classic workflows and plug-in steps that write to the damaged tables, and put a written change freeze on production. Turn things off rather than deleting them, and log what was paused, when and by whom.
- Step 2. Take a restore point and record the retention date: Create a manual backup of the environment in the Power Platform admin center with a label that says why, then write down the date the oldest usable restore point expires for your environment type. Do not restore anything yet.
- Step 3. Preserve the source before anything is re-run: Establish whether the source system is still live, read only or scheduled for shutdown, and until when. Keep the staging database, the extract files, the mapping document and the load logs exactly as they are, and take a dated extract of the source if you do not already hold one.
- Step 4. Quantify the damage entity by entity: For every migrated table, compare row counts, the list of source keys present in each system and a checksum of the business columns per key, first on the whole table and then on the subset the business cares about.
- Step 5. Classify every gap: Put each difference into one of three classes: not migrated (the key is absent from Dataverse), partially migrated (the record exists but children, history, files or later columns did not arrive) or transformed wrong (the record is complete but the values are wrong).
- Step 6. Rule out causes other than the migration: Use the created by, modified by, created on and modified on columns, audit history, system jobs, the plug-in trace log and flow run history to tell a failed load apart from an integration, a mis-registered plug-in, a flow, a bulk delete or a bad merge, and stop that cause first.
- Step 7. Fix in order: not migrated, then partially migrated, then transformed wrong: Load missing parents before their children, complete partial records once their parents exist, and only then correct wrong values, rehearsing each pass in a sandbox copied from production with automation disabled and counts taken before and after.
- Step 8. Document the gap for the audit: Write down the timeline, the reconciliation counts per entity, the classification, what was corrected and how it was proved, and what is genuinely lost, with a named owner and a date, and give that to the auditor rather than a clean system with no explanation.
How do you measure what is missing, entity by entity?
With numbers, per table, against the source. "It looks like some contacts are missing" is not a finding, and it cannot be fixed or explained to an auditor. Build a reconciliation per migrated entity, with the source on one side and Dataverse on the other, joined on the source system key you should have carried into an alternate key column during the load.
For the Dataverse side, extract through the read only Dataverse SQL (TDS) endpoint where it is enabled, through Azure Synapse Link or Link to Microsoft Fabric where those already exist, or through a Web API export into the same staging database that holds the source extract. Comparing inside one database is far more reliable than comparing two spreadsheets.
The validation checks for a planned migration, such as created date distribution, ownership and file bytes, are listed in our Salesforce to Dynamics 365 data migration guide, and the rescue diagnostics for migration damage are on our project rescue and takeover page. The recovery reconciliation below is narrower: it exists to classify every gap.
| Measure | How | What it tells you |
|---|---|---|
| Row count per table | Count in the source extract and in Dataverse, on the whole table and on the business subset (for example active accounts or open cases). | A total that matches while the subset does not usually means a filter at extract or a status mapping, not lost rows. |
| Key difference | Anti join on the source key both ways: keys in the source but not in Dataverse, and keys in Dataverse with no source. | The first list is your not migrated population. The second is duplicates, test data or records created by users since go live. |
| Column checksum per key | Hash a normalised concatenation of the business columns (trimmed text, dates in UTC, choice values mapped to labels) on both sides and compare per key. | Keys present on both sides with different hashes are the transformed wrong population, before anyone has to eyeball a grid. |
| Child counts per parent | Count activities, notes, contacts and line items per parent key on both sides. | Parents with fewer children in Dataverse are partially migrated even though the parent row itself reconciles. |
| Control totals | Sum money and quantity columns per table, split by currency. | Counts can match while totals do not, which points at currency, precision or a column mapped to the wrong field. |
Is the data not migrated, partially migrated or transformed wrong?
Every gap belongs to one of three classes, and the class decides both the fix and the order. Fix them in the order shown: loading missing records first means partial records can be completed against parents that exist, and correcting values last means the correction is not undone by a later reload. The honest fourth outcome, data that exists in neither system, belongs in a loss register rather than being hidden inside one of the three.
| Class | Signature | Usual cause | Fix |
|---|---|---|---|
| 1. Not migrated | The source key is absent from Dataverse. | Rows rejected by the load (failed lookups, required columns, validation), whole batches that failed, or a filter at extract nobody recorded. | Load from the preserved source with an upsert on the alternate key, setting created on and owner at create, parents before children. |
| 2. Partially migrated | The record exists but children, notes, attachments, activity history, state or later columns did not arrive. | A second pass that never ran, a file workstream that was deferred, lookups that could not resolve because the parent loaded later. | Run the missing pass for that population only, then close records through the proper close operations rather than by setting a status value. |
| 3. Transformed wrong | Both sides have the key and the column checksums differ. | A wrong mapping, a wrong choice value map, time zone or currency handling, or text truncation. | Correct in place where users have edited the record since go live; reload only where they have not, because a reload destroys real work. |
Was it the migration, or is something else damaging the data?
Before fixing anything, prove the cause. A surprising share of "migration damage" is done after the load by something still running, and if that is the case every correction will be overwritten. The system columns on the damaged rows, together with audit history and the job logs, usually separate the causes within a day.
Where the fault is inside plug-in code or stacked solution layers, that is the point at which reading the code matters more than reading the data, which is covered in the rescue guide.
| Cause | Evidence that points to it | Where to look |
|---|---|---|
| Failed or incomplete load | Whole ranges of source keys missing together, created by the migration account, created on clustered on the load days, gaps matching batch boundaries. | The migration tool logs and error rows, staging tables, the created by and created on columns. |
| Bad integration | Modified by an application user, modified on clustered at the integration schedule, values that revert after you correct them. | Application users in the admin center, the integration logs, modified by and modified on. |
| Mis-registered plug-in | Values change on every save regardless of what the user edited, a step registered on the wrong message, stage or with no filtering attributes, changes stamped with the user who saved. | Plug-in steps in the Plug-in Registration Tool, the plug-in trace log if logging is enabled, audit history showing several columns changing in one transaction. |
| Flow or classic workflow | A flow run that succeeded while writing the wrong value, changes stamped with the identity the flow connection runs as. | Flow run history, which is retained only for a limited period, and system jobs for classic workflows. |
| Bad merge | Records that are inactive rather than deleted, with the master lookup set and their children re-parented to another record. | Audit history on the surviving and the merged record. There is no unmerge, so a wrong merge has to be reversed by hand. |
| Bulk delete or duplicate clean up | Records missing that the load log says were created successfully. | Bulk record deletion jobs and duplicate detection jobs in data management, audit history for delete events if auditing was on. |
What can a Dataverse backup and restore actually recover?
Less than most people hope, and it is still worth protecting on day one. Environments with a Dataverse database get automatic system backups, and administrators can take manual backups on demand. How long either can be restored from depends on the environment type and whether Dynamics 365 apps are installed: at the time of writing Microsoft documents up to 28 days for production environments with Dynamics 365 apps and 7 days for most other environments, with some retention configurable for managed production environments. Read the figure your own admin center shows rather than relying on any article, including this one.
A restore returns an entire environment to a point in time. It cannot restore one table, one set of records or one column in place, and restoring over an environment overwrites everything in it, including every legitimate record users entered since the backup. The target of a restore must be a sandbox, so production has to be converted to a sandbox before it can be overwritten, which is itself a decision to make deliberately.
The pattern that works in a recovery is to restore the backup into a different, separate sandbox environment, disable the flows and integrations in that copy so it cannot send email or write to live systems, extract the affected rows from it, reconcile them against production and apply only the difference through an upsert on the alternate key.
| Can recover | Cannot recover |
|---|---|
| A copy of the environment as it stood at a point inside the retention window, for extracting records that were overwritten or deleted after that point. | Anything older than the retention window, however important it turns out to be. |
| Customizations and configuration as they stood at that point, useful for proving what the build looked like. | Data that never reached Dataverse, such as rows the migration rejected. That can only come from the source. |
| Deleted rows restored individually, where the Dataverse recycle bin is available and was turned on before the deletion, within its configured retention. | A single table or record set restored in place in production without rolling back everything else. |
| A basis for a selective re-apply: restore to another sandbox, extract, reconcile, upsert the difference. | Documents stored outside Dataverse, such as files in SharePoint document locations, which follow SharePoint retention instead. |
What can Microsoft support do after a failed migration, and what can it not?
Microsoft support is the right route for platform problems and the wrong one for migration mistakes. Raise a support request from the Power Platform admin center when the platform itself is in the way: a backup or restore operation that fails or will not complete, capacity limits blocking a restore or a reload, service protection throttling you cannot work within, a service incident affecting the environment, or a question about which restore points exist.
Support will not rebuild your field mapping, reconstruct records that were never loaded, work out which of your plug-ins or flows damaged the data, or write your audit explanation. Do not plan on support recovering a restore point outside the retention window your admin center shows. Being clear about which of the two problems you have saves days waiting on a ticket that was never going to fix a mapping error.
What does Dynamics 365 audit history show, and what if auditing was off during the migration?
Audit history records, per change, the date, the user or application identity the change ran under, the event, the column, and the old and new value. It is the strongest evidence for separating a migration write from an integration, a plug-in or a person, but only within its limits. Auditing has to be enabled at the environment, table and column level, it is not retroactive, and it is kept for the audit retention period set on the environment while consuming log capacity.
Migrations are normally run with auditing switched off so the load does not record itself, which means the rows as loaded often have no audit trail at all. In that case the load logs, the staging data and the preserved source become your primary evidence, and you should say so plainly. Turn auditing on for the affected tables and columns now, before the corrections start, so that every repair from here on is recorded.
The native audit grid is hard to read for this kind of investigation. Solzet Easy Audit is a free PCF control that reads the same Dataverse audit data and lets you follow a single field from its first value to its current one, filter by user and date, and export the history to CSV for the evidence pack.
How do you reload or correct data without making the damage worse?
Treat every corrective pass as a small migration with its own rehearsal. The load mechanics, including dependency order, upsert on an alternate key and setting overriddencreatedon at create, are covered step by step in the Salesforce to Dynamics 365 data migration guide and apply to any source. What changes in a recovery is that users have been working in the system since go live.
- Rehearse each pass in a sandbox copied from production, not in a stale test environment, so it runs against the data users have actually changed.
- Disable the plug-in steps, flows, classic workflows, business rules and duplicate detection rules that fire on the table for the duration of the pass, and re-enable them from a written list afterwards.
- Upsert on the source key rather than deleting and recreating, so records keep their GUIDs, their relationships, one continuous audit trail and any work users did on them.
- Never overwrite a column a user has edited since go live with a value from the source without a rule for which wins. Compare modified on against the go live date and route conflicts to a named person.
- Take counts and checksums before and after every pass and keep them. A correction without counts around it is indistinguishable from a second round of damage.
- Created on cannot be set on update. If a table went in without the created on override and the dates matter, that table is a reload, and that decision has to be made before users edit it any further.
What should you tell the auditor about a failed migration?
The truth, documented. Auditors deal with incidents all the time; what turns an incident into a finding is a gap nobody can explain, or records that were quietly re-imported with no trail showing what changed. A re-import that leaves created dates, owners and audit history inconsistent with the story you tell is worse than the original gap. The evidence pack for an automation failure in our rescue guide follows the same principles; for a migration it contains the items below.
- A dated timeline: when the migration ran, when the problem was found, when writes were frozen and when each corrective pass ran.
- The reconciliation per entity: source count, target count, and the not migrated, partially migrated and transformed wrong counts, before and after correction.
- The cause, with the evidence for it: load logs, system columns, audit history or run history, and a note of where auditing was not enabled.
- The correction record: what was changed, by which method, under whose approval, with before and after counts for every pass.
- The loss register: data that cannot be recovered from either system, why, the business impact, and what was done instead, such as manual recapture from paper or email.
- The controls now in place: auditing on for the affected tables, the change freeze or release process, and who signs off future loads.
Should you stabilise the current Dynamics 365 system or rebuild it?
Most failed migrations are recovered, not rebuilt. If the data model is sound and the damage is confined to the load, a bounded recovery followed by stabilisation keeps everything users have done since go live. A rebuild is justified when the target model itself is wrong, when the created on and ownership problems are so widespread that most tables need reloading anyway, or when the platform decision behind the migration no longer holds. If you are weighing that, our project rescue and takeover service starts with a forensic audit that makes the recommendation on evidence.
We recommend the right solution - whether that's Microsoft Dynamics 365, Power Platform, or a custom-built CRM. Some businesses need the Microsoft ecosystem. Others need full control without licensing. We deliver both. Where a recovery exposes that Microsoft per-user licensing never fitted the organisation, custom CRM development on React, Node.js, PostgreSQL and .NET is the alternative, and the same reconciliation method moves the data there.
If a rebuild is on the table, which platform should it be on?
Can afford licensing and want the Microsoft ecosystem
Dynamics 365
Microsoft 365, Teams and Outlook integration, a mature partner ecosystem, Copilot, and apps for sales, service and field operations that are configured rather than built.
Need full control and zero licensing
Custom CRM
A CRM built on React, Node.js, PostgreSQL or .NET that you own outright: your data model, your hosting, no per-user subscription, and features shaped exactly to your process.
Not sure which fits
We help you decide
A short discovery weighs licensing budget, process complexity, integrations and long-term ownership, then recommends one path. We deliver both, so the recommendation has no reason to lean.
How does Solzet help recover a failed Dynamics 365 migration?
Our senior consultants and full-stack developers, with 8+ years of Dynamics 365 Customer Engagement and Power Platform delivery, take over failed migrations into Dynamics 365 Sales, Customer Service, Field Service and Customer Insights and other Dataverse apps: freeze and preserve, reconcile per entity, classify and correct the gaps, and assemble the documentation for the audit. We work from Yerevan, Armenia (GMT+4) in English, Armenian and Russian, and delivery capacity is expanded per engagement.
We do not work on Dynamics 365 Finance, Business Central, Dynamics NAV or Finance and Operations migrations, so if your failed migration is on one of those, you need a different partner. Contact us at contact@solzet.com or use the form below.
What do people ask us?
Can Dataverse restore a single table or deleted records after a failed migration?
Not in place. A Dataverse restore returns an entire environment to a point in time and overwrites everything in the target, which must be a sandbox. To recover specific records, restore the backup into a separate sandbox, extract the rows you need, reconcile them against production and upsert the difference. Where the Dataverse recycle bin was turned on before the deletion, individually deleted rows can also be restored within its retention.
How long are Dynamics 365 backups kept?
It depends on the environment type. At the time of writing Microsoft documents up to 28 days for production environments with Dynamics 365 apps and 7 days for most other environments, with some retention configurable for managed production environments. Check the figure shown for your environment in the Power Platform admin center and write down the date your oldest usable restore point expires.
Should we delete the migrated data and run the migration again?
Usually not. Deleting and reloading destroys record GUIDs, relationships, the continuity of the audit trail on each record and every edit users made since go live. Classify the gaps first, upsert missing and partial records on the source key, and correct wrong values in place. A full reload of a table is justified mainly when created dates or ownership were not set at create and users have not yet worked on the data.
How do we tell migration damage from a bad integration or plug-in?
Look at the system columns and the logs. Migration damage is created by the migration account, clustered on load days and in batch shaped ranges. An integration shows as modified by an application user on its schedule, and values revert after correction. A mis-registered plug-in changes values on every save. A bad merge leaves inactive records with a master record set. Audit history, the plug-in trace log and flow run history confirm which it is.
Can Microsoft support recover data lost in a migration?
Microsoft support helps with platform problems such as a restore that fails, capacity blocking a restore, throttling or a service incident. It does not fix field mappings, reconstruct records that were never loaded or diagnose your own plug-ins and flows, and you should not plan on it recovering data from outside your retention window. Migration mistakes are recovered from the source system and your own backups.
What if auditing was switched off during the migration?
Then there is no audit history for the load, because auditing is not retroactive. Use the migration tool logs, the staging data, the preserved source extract and the created and modified system columns as evidence instead, and say plainly in the audit documentation that auditing was not enabled. Turn auditing on for the affected tables and columns before corrections begin so every repair is recorded.
What should we tell auditors about missing CRM data?
Give them a dated timeline, the reconciliation counts per entity before and after correction, the cause and its evidence, the correction record with approvals, a register of what could not be recovered and why, and the controls now in place. Auditors can work with a documented incident; an unexplained gap or a silent re-import is what becomes a finding.
Can Solzet take over a failed migration another partner ran?
Yes, for Dynamics 365 Customer Engagement and Power Platform. We freeze and preserve, reconcile against the source, classify and correct the gaps and document the recovery. We do not take on Dynamics 365 Finance, Business Central, NAV or Finance and Operations migrations. The wider takeover process is described on our project rescue and takeover page.
Where should you go next?
Dynamics 365 project rescue and takeover
The takeover service, including migration damage as a rescue pattern with its diagnostics and recovery phases.
Dynamics 365 rescue guide
Crisis steps with no budget, the limits of doing it yourself, and the auditor evidence checklist.
Salesforce to Dynamics 365 data migration guide
Load order, overriddencreatedon, files and the validation pack for doing a migration properly.
Solzet Easy Audit
A free PCF control that makes Dataverse audit history readable, filterable and exportable.
Custom CRM Development
CRM on React, Node.js, PostgreSQL and .NET for organisations that need full control without Microsoft licensing.
Which solution is right for your business?
Tell us what you need. A senior consultant replies within one business day with a recommendation - Dynamics 365, Power Platform, or a custom-built CRM - not a sales script.