Introduction
The migration is done. The maintenance window closed, the sites are live on SharePoint Subscription Edition, and the ticket that sat in the backlog for 18 months is finally marked resolved.
Looking back, I know more now than I did when we started. Some of that knowledge came from research. Most of it came from finding out, in the middle of a production environment, that what we assumed was true wasn’t.
This post is not a technical walkthrough. Posts 1 through 11 handled that. This one is different. This is the list I wish someone had handed me before we kicked off the project — the things that didn’t show up in Microsoft documentation, didn’t come up in vendor calls, and only revealed themselves once we were deep enough in that backing out would have been its own problem.
We migrated a production SharePoint 2019 farm to Subscription Edition. Real content. Real users. Real consequences if something went wrong. Over the course of that project, we built 12 PowerShell scripts that turned a process that would have taken months of manual effort into something repeatable, auditable, and survivable.
Here are the 10 lessons that shaped all of it.

Lesson 1: You Can’t Skip the Inventory Phase
We thought we knew our farm. We had documentation. We had a farm topology diagram that someone had updated, at some point, in the past. We had a SharePoint admin who had been there for three years and could name every web application from memory.
We were wrong.
When we ran Get-SPInventoryReport.ps1 against the production farm, it returned three content databases that nobody on the team knew existed. Not one — three. One of them held 400GB of data. Another was attached to a decommissioned web application from four years ago. The web application was gone. The database was still there. It still had active content inside it.
If we had built our migration plan from memory and existing documentation, we would have left 400GB of data behind and orphaned a content database on the source server with no awareness that it existed. The inventory script doesn’t just give you a list — it gives you the truth about what you actually have.
Run the inventory before you plan anything. Write the migration plan after the inventory report is in your hands, not before.
If we hadn’t had Get-SPInventoryReport.ps1, we would have started migrating a farm we didn’t actually understand.
Lesson 2: Workflows Are a Bigger Problem Than You Think
Every stakeholder we talked to during discovery said some version of the same thing: “We have a few workflows.” That phrase, repeated across four departments and six site collection owners, became the most expensive set of words in the project.
When we ran Get-SPWorkflowAudit.ps1, the numbers told a different story. We found 312 SP2013 workflows, 47 Nintex workflows, and 8 workflows that were still running on sites nobody had logged into in over two years. Not decommissioned — running. Firing on schedule. Sending emails that presumably nobody was reading.
SP2013 workflows do not migrate to SharePoint Subscription Edition. They don’t carry over. They don’t automatically upgrade. They need to be rebuilt in Power Automate, one by one, by someone who understands what the original workflow was doing. At the pace our team could move, that’s weeks of remediation work, not hours.
We ran this audit three months before cutover. That timing was intentional, and it’s the only reason the workflow remediation was complete before the maintenance window opened. If we had discovered 312 workflows two weeks before cutover, we would have had a hard conversation with leadership about delaying the project.
Run the workflow audit at least three months before your planned cutover date. Workflow remediation is measured in weeks per workflow, not hours.
If we hadn’t had Get-SPWorkflowAudit.ps1, we would have walked into cutover with 312 workflows that were going to break the moment users tried to use them.
Lesson 3: Test Your Ports Four Weeks Before Cutover, Not Four Hours
In the original project plan, port testing was scheduled for the week before the maintenance window. That felt like plenty of time. If something was blocked, we’d submit a change request and get it resolved in a day or two.
That assumption did not survive contact with our enterprise change management process.
We moved port testing to four weeks out and ran Test-SPSE-InterServer.ps1 across the planned server topology. The script found six inter-server ports blocked by firewall policy. We submitted change requests for all six. Two of them came back denied on the first submission — they required security exceptions that had to go through a separate approval chain. That chain took eleven days.
Eleven days. For a firewall rule change.
If we had tested a week before cutover, we would have been sitting in a governance meeting arguing about port exceptions while the maintenance window passed us by. We tested four weeks out specifically because we suspected firewall changes would be slower than we expected. We were right.
In enterprise environments, firewall changes are not self-service. They go through change advisory boards, security reviews, and approval chains that exist for good reasons and operate on their own timeline. Start this process early.
If we hadn’t had Test-SPSE-InterServer.ps1, we would have discovered those six blocked ports the night of cutover.
Lesson 4: Log Shipping Is Worth the Complexity
Our original migration plan called for a traditional backup and restore approach. Back up each content database on the source, restore it on the destination, mount it, verify it. The estimated downtime for a farm with our database volume was six hours.
Six hours was already at the edge of what leadership would approve for a maintenance window. Our DBA pushed back on the plan and suggested SQL log shipping instead. There was resistance on the team — log shipping adds configuration complexity, requires careful monitoring, and introduces more moving parts into an already complex migration. The simpler approach felt safer.
We ran Configure-LogShippingJobs.ps1 to set up and validate the log shipping jobs. Once we got past the initial configuration, the secondary databases tracked the primary consistently. By the time the maintenance window opened, the secondary was within 15 minutes of the primary across all databases.
Actual downtime: 45 minutes. Not six hours — 45 minutes.
The difference is that with log shipping, you’re not waiting for backups to complete and restore during the window. The data is already there. You’re just doing the final sync, detaching, and mounting. The setup complexity paid for itself the moment the clock started on the maintenance window.
If the business can absorb six hours of downtime and you have a simple farm, backup-restore is fine. If your window is tight, log shipping is the answer.
If we hadn’t had Configure-LogShippingJobs.ps1, we would have spent those six hours hoping nothing went wrong during a multi-hour restore sequence.
Lesson 5: Parallel Backup Saved Us Days
We had 23 content databases going into the migration. Before we built the parallel processing approach, we did the sequential math: average backup-plus-restore time per database, multiplied by 23, factoring in database sizes. The estimate came out to roughly 68 hours. Three days of continuous database operations.
Three days was not a maintenance window. Three days was an outage.
Parallel-Migrate-Full.ps1 runs migration jobs across configurable parallel streams. We ran four concurrent jobs. Total wall-clock time for all 23 databases: five hours. The math on parallelism is not subtle — going from 68 hours to five hours is not a marginal improvement, it’s the difference between a migration that fits inside a weekend window and one that doesn’t.
The parallel approach does require more infrastructure headroom during the migration — you’re running four backup and restore operations simultaneously, which means storage I/O and network bandwidth become your constraints instead of time. We planned for that. The tradeoff was entirely worth it.
Before you finalize your migration approach, do the sequential time calculation first. If the number exceeds your maintenance window, parallelism isn’t optional. It’s a requirement.
If we hadn’t had Parallel-Migrate-Full.ps1, we would have been asking for a three-day outage window and almost certainly been told no.
Lesson 6: Large Files Will Surprise You
The original wave plan was built on two metrics: site collection count and database size. Those felt like the right variables. Bigger databases take longer, more site collections take more time. We grouped accordingly and estimated wave durations from there.
The large file scan changed everything.
Get-SPLargeFileInventory.ps1 scans SharePoint content for files exceeding a configurable size threshold. We ran it at 100MB. The result: 2.1 terabytes of files over 100MB, concentrated in a small number of document libraries. Almost all of it was video files — recordings, training content, and archived demos that had accumulated in a few libraries over years.
Those libraries alone blew our wave timing estimates completely. A document library with 5,000 items and 50GB in small files migrates very differently from a library with 800 items and 400GB in large video files. The second library takes significantly longer and puts different pressure on network throughput, staging storage, and validation time.
We had to redesign three migration waves because of the large file scan results. That redesign happened before cutover, because we ran the scan early. If we had discovered this during a wave, we would have been recalculating timelines in real time while users were waiting.
Build your wave plan after the large file scan. File count and database size don’t tell you migration time — file composition does.
If we hadn’t had Get-SPLargeFileInventory.ps1, we would have had three waves overrun their windows with no warning.
Lesson 7: Search Always Takes Longer Than Expected
Before the maintenance window, I told our IT director that search would be operational within four hours of cutover. That was an estimate based on our test environment, where the content corpus was much smaller.
I should not have made that estimate.
The initial full crawl of the production environment — 10.2 million items — took 18 hours. Not four. Eighteen. Users spent most of the first business day after migration unable to find content through search. We got tickets. We got escalations. We got a tense conversation with leadership about what “operational” meant.
The problem wasn’t the crawl itself — crawls take time and there’s a lower bound on how fast they go. The problem was that we hadn’t configured search topology, index partitioning, or crawl throttling correctly before the window opened. Deploy-SPSE-Search-Optimized.ps1 handles those configurations, and we used it, but we hadn’t fully tuned the parameters for production scale. We had calibrated them for test.
The lesson isn’t that search will always take 18 hours. The lesson is that search readiness is a separate workstream, not an appendix to the main migration. It needs its own timeline, its own testing at scale, and its own set of expectations communicated to leadership before the window opens — not after users start calling.
If we hadn’t had Deploy-SPSE-Search-Optimized.ps1, the crawl would have taken significantly longer, and the topology would have been wrong from the start.
Lesson 8: Have a Rollback Plan You Actually Tested
We had a rollback plan. It was documented. It was approved. It covered what we would do if the migration went wrong past a defined decision point — which databases we’d restore, how we’d revert DNS, and how we’d communicate to users.
What the rollback plan did not include: a tested, scripted procedure for putting 800-plus site collections into read-only mode quickly.
During a pre-cutover rehearsal about two weeks before the maintenance window, we walked through the rollback sequence. When we got to the step that required setting all sites read-only to prevent content writes during a potential rollback, we timed how long it would take to do it manually. Forty minutes. Per the script we hadn’t written yet, we did it manually in the rehearsal.
Forty minutes to set sites read-only is forty minutes of potential data divergence between your source and destination during a rollback scenario. That’s not acceptable.
We wrote Set-SitesReadOnly.ps1 and Set-SitesReadWrite.ps1 before the actual maintenance window. In production rehearsal, the same operation — all 800-plus sites — took three minutes. Forty minutes versus three minutes is the difference between a rollback that works and a rollback that creates data consistency problems.
A rollback plan that has never been run is a wish, not a plan. Test it in your DR environment. Time every step. Script everything that would take more than five minutes manually.
If we hadn’t had those scripts, our rollback plan would have failed on the step that mattered most.
Lesson 9: The Executive Summary Report Opened Doors
For six months before the migration kicked off in earnest, we had been trying to get leadership to approve extended maintenance windows and commit resources to the project. We sent database lists. We sent farm topology diagrams. We sent meeting invites that got declined.
Nothing moved.
Then we generated the executive summary report using Get-MigrationExecutiveSummary.ps1. It produced a single-page dashboard: risk scores per site collection, migration status, timeline projection, and a traffic-light summary of where the farm stood against migration readiness criteria. It looked like something a project manager would build in PowerPoint, but it was generated directly from the farm data in about 90 seconds.
We put it in an email. We got approval within two days. Three-week maintenance windows, resource commitments, the works.
Nothing about the underlying data had changed. The risk was the same as it had been when we sent the database list. What changed was the output format. Leadership doesn’t read database tables and raw inventory exports. They read dashboards with risk indicators and clear status summaries. The data was always there. The report made it legible for the audience that needed to act on it.
Generate the right output for the right audience. The technical team needs raw data. Leadership needs a dashboard. Don’t expect one format to work for both.
If we hadn’t had Get-MigrationExecutiveSummary.ps1, we might still be waiting for that approval.
Lesson 10: Automate Everything You Run More Than Once
The first time we ran farm health checks, it was manual. We pulled individual PowerShell cmdlets from a reference doc, ran them one at a time, copied output into a Word document, and spent about two hours producing a report that was inconsistently formatted and probably missed something.
The second time, we had a draft script.
The tenth time, the script had parameter flags, structured logging, error handling, and email alerting built in. What started as a two-hour manual process became a five-minute scheduled task that ran itself and delivered results to a shared mailbox.
That pattern — see it once manually, script it the second time, refine it until it’s robust — is how more than 80 hours of manual migration work became a 12-script toolkit. Not because we planned it that way from the beginning, but because we were disciplined about a single rule: if you do it more than twice, you write a script.
Time-to-script is a forcing function. The moment you commit to scripting something, you’re forced to define it precisely — what are the inputs, what are the outputs, what does success look like, what should trigger an alert. That precision makes the process better even before the script runs.
Everything in this series — the inventory, the workflow audit, the parallel migration, the rollback procedures, the search deployment, the executive summary — started as a manual step that got scripted. The discipline of automating everything repeatable is the reason the migration was survivable.
If we hadn’t committed to that rule, we would have spent weeks on tasks the toolkit now handles in minutes.

The Full Migration Path: All 12 Posts
If you want to follow the complete technical path from planning through post-migration validation, here is the full series in order:
- The Complete Guide to Migrating SharePoint 2019 to Subscription Edition
- How to Run a Full SharePoint Farm Inventory Before Migration
- Scanning for Large Files in SharePoint Before Migration (and Why It Matters)
- How to Audit SharePoint Workflows Before Migration (SP2013 & Nintex)
- Testing Port Connectivity and Firewall Rules for SharePoint SE
- Setting Up SQL Log Shipping for Zero-Downtime SharePoint Migration
- Parallel Database Backup and Restore at Scale for SharePoint Migration
- The SharePoint Migration Cutover Playbook: Step by Step
- Adding SharePoint Content Databases to an Availability Group (AG)
- How to Set Up Search Architecture in SharePoint Subscription Edition
- Post-Migration Validation: Reports, Health Cards, and Compliance Checks
- 10 Lessons Learned: Migrating SharePoint 2019 to Subscription Edition ← you are here
Get the Complete Migration Toolkit
Twelve posts. Twelve scripts. One migration that actually shipped.
If you’ve read this far, you know what this project looked like in practice — the unknown databases, the 312 workflows nobody admitted to, the eleven-day firewall approval chain, the eighteen-hour search crawl. Every lesson in this post required a script. Writing those scripts from scratch, testing them against a production farm, and refining them until they were reliable took somewhere between 40 and 100 hours of engineering time.
You don’t have to spend that time.
The Complete SP2019 → SPSE Migration Toolkit includes all 12 scripts: the farm inventory report, the workflow auditor, the port connectivity tester, the log shipping configurator, the parallel migration engine, the large file scanner, the search deployment script, the read-only and read-write rollback scripts, the executive summary generator, and the master orchestration script that ties the entire sequence together.
That is a toolkit built against a real production migration, refined through every problem described in this series, and ready to run against your farm.
Contact sudharsan_1985@live.in to get the Complete Migration Toolkit.
Migrations are hard. The ones that go well aren’t lucky — they’re prepared.