A business owner I'll call Diego — not his real name — came to me with a VB.NET system that ran most of his operations, and three pieces of advice that flatly contradicted each other. A consultant had told him to rewrite the whole thing from scratch, "clean slate, do it right." A second one told him to leave it alone entirely — "if it works, don't touch it." A developer he'd hired for a small fix told him the real answer was to just translate the VB.NET into C#, module by module, and call it done.

All three were partly right, and that's exactly the problem. Diego's system wasn't one thing. It was a handful of modules, built at different times, doing different jobs, aging at different rates. The honest answer to "what should we do with it" was never going to be a single verdict for the whole system. It was going to be a different answer for each piece.

Why "the whole system" is the wrong unit of decision

Most VB.NET systems I look at were not built in one sitting. They grew — a core module first, then a reporting add-on two years later, then an integration bolted on when a new vendor showed up, then a dashboard someone needed for a board meeting. Each of those pieces has its own age, its own rate of change, its own blast radius if something breaks.

Treating the whole thing as one decision means the loudest, most visible module (usually the one someone just complained about) drags every other module into the same verdict, whether or not it deserves it. A quiet reporting module that hasn't needed a change in three years doesn't need the same treatment as the order-processing core that gets touched every month.

The three real options

Keep and stabilize. This is the right call for a module that works, rarely changes, and isn't where your business is growing. The goal here isn't to modernize it — it's to make sure it keeps running safely: confirm it runs on a supported runtime, add tests around the behavior that actually matters so a future change doesn't break it silently, and make sure deployment is repeatable rather than something only one person remembers how to do. You're not improving it. You're making sure it doesn't become a problem by neglect.

Migrate to C# / modern .NET. This is the right call for a module that's actively used, actively changed, and where the business depends on it continuing to evolve. Migration here doesn't mean a single risky rewrite — it means going module by module, with tests written around each piece before it's touched, so you can prove the new version behaves the same way the old one did. A detail that surprises a lot of owners: the business logic sitting in VB.NET libraries can often be reused as-is while just the web layer around it gets rebuilt in C#, which cuts the actual amount of rewriting dramatically.

Replace entirely. This is the right call when the module no longer fits how the business actually runs — when it was built for a process you've since outgrown, and keeping it alive costs more, in workarounds and manual patches, than building something new would cost. This is the option people reach for too early (rewriting feels more satisfying than stabilizing) and also, sometimes, too late (propping up something the business has already outgrown, out of attachment rather than any real fit).

Diego's system, worked through

Diego's system had three real modules once we looked closely.

The first handled inventory tracking — stable, boring, touched maybe twice a year, and doing exactly what it needed to do. We kept it and stabilized it: confirmed the runtime, added a handful of tests around the parts that mattered, documented the deployment that used to live only in one developer's memory. Total cost: a few days. Ongoing risk: low.

The second handled order processing — the module that changed almost every month as the business added new sales channels. This was the clear migration candidate. We moved it to modern .NET module by module, kept the core VB.NET business logic largely intact where it still made sense, and rebuilt the web layer around it in C#, with tests proving each piece behaved the same before and after.

The third was a scheduling tool, bolted on years earlier for a process the business didn't really run anymore. Nobody loved it, and everyone had a workaround for the parts that didn't fit. We replaced it — not with a dramatic system-wide rewrite, just with a small, purpose-built piece of new software that actually matched how scheduling worked today.

Three modules, three different verdicts, none of them requiring the other two to move first.

Why VB.NET systems get harder to leave alone over time

Microsoft isn't pulling support for VB.NET — it's a supported language and will likely stay that way. What it isn't doing is evolving the language further; new language features go to C#. That alone doesn't force anyone's hand. What does add up, quietly, is everything downstream of that choice: fewer developers want to build their careers around VB.NET, which makes hiring and onboarding slower every year; new libraries, tutorials, and examples are written with C# in mind; and modernizing the platform underneath a VB.NET module (the runtime, the hosting, the tooling) very often means touching the VB.NET code itself anyway, so "we'll deal with it later" rarely stays free.

Taking inventory before deciding anything

The process that worked for Diego, and works in general, starts before any decision gets made: take inventory of what each module actually does, who depends on it, what breaks when it breaks, and what it costs — in time, in workarounds, in risk — to keep it exactly as it is. Only after that does the keep/migrate/replace decision get made, module by module, not as one verdict handed down for the whole system.

That inventory is exactly what a structured audit produces: a clear picture of which modules are fine as they are, which ones are worth migrating, and which ones have quietly outlived their usefulness — before you commit to a direction based on whichever consultant happened to talk to you last.

Let's talk through your situation.