An end of life legacy system holding years of data
Kove Energy installs solar, home batteries, heat pumps and EV chargers around Waikato. It has about 15 people. Around six work in the office, and contractor crews do the installs.
Jobs, customers, stock and enquiries all lived in a custom Windows desktop app. The app sat on a SQL Server 2012 Standard database, on a server in the office running Windows Server 2012 R2.
Microsoft's word for this is end of support, and both layers had reached it. SQL Server 2012 has had no security fixes since July 2022, unless you paid for extended security updates, and those ended in July 2025. Support for Windows Server 2012 R2 ended in October 2023. The app still ran. It was just running with nothing patching the holes.
The server was getting old and had to be replaced. SQL Server 2012 is not supported on Windows Server 2022 or 2025, so a new server meant the app had nowhere to go.
The bigger problem was that years of the firm's data sat in an application nobody could build on. Kove Energy wanted more from its systems: AI insights from its job history, GPS and maps for the crews, integrations with other systems, and more of the routine work automated. Before any of that, it needed a solid base.
Why Dataverse and Power Apps
Kove Energy chose Microsoft Dataverse, with a Power Apps canvas app on top. Dataverse holds related business data properly, as real tables with real links between them. Role-based access is built in, so who can see or change what is set in one place. It sits inside the Microsoft 365 the firm already uses. And it is the easiest option for the firm's IT to support, because there is no server or database engine left to patch.
What it costs to run is on the SQL Server to Dataverse page.
The rebuild: eight tables, eight screens, three roles
This was a rebuild onto Dataverse, not a lift of the old database into Azure SQL. A copy into Azure SQL would have carried the old tables across with every fault still in them. A rebuild meant each fault got fixed, or flagged, on the way in.
The data model has 8 tables. Each one keeps the old ID as an alternate key, or a stable new one where the old table had none. That means any load can be re-run without loading a row twice. I read all 8 keys back, and all 8 were Active before the load started.
The app has 8 screens: Jobs (list and panel), People, Enquiries, To do, Codes, Stats, Checks and a loading screen.
There are three roles: office manager, office staff and installer. I tested them with 18 checks, six actions for each role, and all 18 passed. Every refusal came back from Dataverse itself as a 403, so an installer cannot edit a job even by going around the app.
The database migration, measured
For this showcase I built a stand-in for the old database on SQL Server Express on my build PC, and moved it into a Dataverse environment called Kove Staging. It held 120,000 rows across 8 tables, in a 72.0 MB data file.
That is not a big database. The hard part was the quality of the data and the rebuild around it. I gave the stand-in the faults an old database usually has, and each one got a named rule and a count.
- Unknown dates. A date of 1900-01-01 stood in for "unknown" on 37 dates, 12 on jobs and 25 on people. They are blank now, which is what they always meant.
- Broken links. 114 links pointed at rows that no longer existed. A reconciliation step before the load reports every one. A missing designer was blanked on 15 jobs and a missing installer on 12. On 38 job lines the product no longer existed, so the product link was blanked and the line kept.
- Macrons. My first build of the stand-in database used a non-Unicode collation and stored 203 names with the macrons stripped. A code review caught it, and I fixed it with Unicode columns before the run. On the run, 203 of 203 names read back from Kove Staging exactly as written.
- Padded values. 50 values were padded out with trailing spaces by the old fixed-width columns, and were trimmed on the way in.
- Missing keys. Three tables had no primary key, and two of them, 74,590 rows, needed a stable ID before they could move.
- Duplicates. The data held 150 duplicate people. I merged 0, because a person should decide whether two records are the same human. The app flags them for review instead.
A small trial of 2,776 rows went first, into my dev environment, with 101 checks passed and 0 failed. The full run took 0.2 minutes to export, 0.1 to clean and reshape, 67.6 to load and 2.1 to verify. Dataverse sent no throttling responses (HTTP 429) during the run.
The first pass landed 119,996 rows, because 4 comments were lost to connection resets. I retried the four and the total came to 120,000. The load ran 2 more times in all, once for those comments and once after I took house numbers out of the invented addresses. Not one row was loaded twice: 0 duplicates.
Counts and a sample of rows can pass while one row has been swapped for another. So the final check compares the full key set of every table in both directions, and 145 of 145 checks pass.
| Measure | Value |
|---|---|
| Rows moved | 120,000 |
| Rows landed, first pass | 119,996 |
| Rows landed, after retrying 4 rows | 120,000 |
| Names with macrons kept | 203 of 203 |
| Rows loaded twice | 0 |
| Checks passed, after the retry | 145 of 145 |
| Total run time, first full pass | about 70 minutes |
| Run | 2026-10-31, Kove Staging, a Developer environment in Australia run as a Managed Environment, synthetic data only |
Rows landed and checks passed are after the re-run that retried the 4 comments. These figures come from one dated set of test runs on invented data, not from a client. I have not measured hours saved or payback, so this page claims neither.
Feedback from a real client on work I did for them:
"I was really impressed with how the solution turned out, and the migration went a lot more smoothly than I expected."
Operations lead, NZ environmental services firm
What the new base is for: AI, maps and integrations
With the data clean and in Dataverse, the things Kove Energy wanted can be built on top instead of worked around. That means AI insights from the job history, GPS and maps for the crews, integrations with other systems, and workflows that run without someone chasing them. Each one reads from the same tables and respects the same roles, so none of them needs another migration.
The demo already has some of them built on top: a map of every job, emails and reminders that go out on their own each morning, the risks pulled out of each design report, and written trends from the job history.
How I hand it over
When I do this for a client, the solution goes into your own Microsoft tenant, not mine. I train your IT team at each milestone as we deploy, so they know it by the time it goes live. They get the runbook I write for it. From there, they support it.
Balu
Try the demo
The demo is a clickable copy of the app, on invented data. It is a React copy for browsing, not the live Dataverse app, and it makes no network calls. You can switch between the three roles and reset it whenever you like. A replay steps through the real run, from the old table to the rule applied to the Dataverse row. Try logging a web enquiry for solar plus a battery, turning it into a job, and moving it through to Signed off.
The demo is built for a laptop screen. On a phone, it asks you to turn the screen sideways.
