VB6 to .NET Migration Services — For the App Nobody Left Can Open
Your production scheduler, quoting tool, label printer or job-costing system was written in Visual Basic 6 around twenty years ago by somebody who has since left. It still runs the business. It will not install on Windows 11, the IDE will not start on any machine your IT team is willing to support, and the numbers it prints have to keep coming out the same. That is the job we take off your desk.
Every engagement starts with a 14-day risk-free trial — you pay only if you are satisfied.
Something broke, or is about to
Nobody searches for VB6 migration out of curiosity. The search is made after an event, and the event is usually one of a short list. A fleet refresh has been scheduled and this one application is the only thing standing in the way. A driver was updated and the app stopped talking to the scale, the scanner or the label printer. An auditor or an insurer has written down that a business-critical application is running on an unsupported platform, and now the finding has an owner and a date.
The pattern is consistent enough to list. Any of the following is a legitimate reason to start looking, and several of them tend to arrive together.
Windows will not cooperate
The installer fails on Windows 11 or a current Windows Server build, or the app installs and then misbehaves in ways nobody can reproduce on the old machines. Compatibility shims are being used as a permanent fix, which they are not.
The IDE is gone
The Visual Basic 6 development environment will not run on any machine IT is prepared to support, so no change can be made at all. A one-line label correction has become a project. The build machine is a tower under somebody's desk that must never be switched off.
A control will not register
An OCX or ActiveX control refuses to register on a fresh machine, or its vendor no longer exists and the licence key cannot be reissued. The grid, the chart, the calendar picker or the comms component has become the blocker.
32-bit meets 64-bit
A 64-bit-only database client, a 64-bit Office install or a 64-bit device driver has collided with an application that has always assumed 32-bit. Two versions of a runtime are now being kept side by side purely to keep this one app alive.
Hardware has moved on
A printer, scale, scanner, barcode encoder or laboratory instrument has been replaced, and the new one has no 32-bit driver path. The workaround is an old PC kept in a cupboard, and the spare for that PC no longer exists.
Nobody can read it
The person who wrote it has retired. The person who patched it after that has left. What remains is a folder of forms, a few modules and a printed manual from 2009. Change requests are declined because nobody is willing to touch it.
One correction is worth making, because the two things get blurred constantly and the difference matters to your decision. The Visual Basic 6 development environment has been out of mainstream support since 2008, which is why no fix, no security patch and no compatibility work is coming for the IDE. The VB6 runtime that ships inside Windows sits under a separate and considerably more generous support position, which is why your compiled executable often keeps running long after the IDE became unusable. Your application is not about to stop working overnight. What has already stopped is your ability to change it.
Decide where the application should land
Four destinations are realistic. Each is right for a different set of circumstances, and the choice is worth making deliberately rather than defaulting to whatever the first vendor sells.
VB.NET on modern .NET with WinForms
The closest thing to the original. Form layouts, tab order, keyboard shortcuts and the deployment model are preserved, so the people using the application every day are not retrained. Language familiarity is retained for whoever maintains it next. This is normally the fastest way off the unsupported platform, and it is the right default when the pressure is a Windows upgrade rather than a business change.
C# on modern .NET, WinForms or WPF
More developers write C# than VB.NET, so long-term recruitment is easier, and most .NET examples and libraries are written in it. The cost is retraining now for whoever maintains the system internally. Worth saying plainly: language choice matters far less than architecture. A well-separated VB.NET application will be easier to live with than a C# one that kept every VB6 habit intact.
A web application on ASP.NET Core or Blazor
Desktop deployment disappears entirely, remote and multi-site access becomes possible, and locked-down shop-floor machines stop being a constraint. This is the only option that genuinely solves "we are not allowed to install anything on those PCs". It is also the largest change to how people work, the most likely to trigger resistance from long-serving operators, and the one where hardware attached to a local machine needs a new answer.
Replace it with a commercial product
If the application is generic inventory, stock control, quoting or accounting, a packaged product may end the problem more cheaply than any migration, and it comes with a vendor who patches it. Migration earns its cost when the software encodes something specific to how your business works. If it does not, that should be established before money is spent moving it.
The criteria that actually decide it
Form count is the first number. A dozen forms and a rebuild becomes competitive with a migration. Two hundred forms and the migration path is almost always cheaper. The second question is where the logic lives: if pricing, scheduling and validation rules sit inside button-click handlers rather than in modules, more of the work is untangling than translating, and that changes the estimate more than total line count does. Hardware and driver dependencies pull the decision towards the desktop. A stated need for remote or multi-site access pulls it towards the web. And the last question is who maintains it afterwards, because a stack your own team cannot support simply relocates the problem.
This decision is made during assessment and written down with reasons, before any code is produced. If the wider estate has several old applications rather than one, the same reasoning belongs at portfolio level, which is covered in our legacy application modernization service.
What automated converters do, and what they leave you with
Automated VB6 to .NET conversion tools exist, they are heavily marketed, and dismissing them is as dishonest as claiming they finish the job. What they genuinely do well is mechanical translation at speed. Syntax is converted, form layouts and control properties are carried across, declarations are rewritten, and thousands of lines that a person would otherwise retype by hand appear in a compiling .NET project in a fraction of the time. On a large codebase that saves real weeks, and refusing to use one out of pride is a way of spending your money on typing.
What they cannot do is judge. A converter has no opinion about whether a 900-line click handler should have been three services. It cannot tell that a swallowed error was masking a genuine failure. It preserves the Variant, the default property, the control array workaround and the copy-pasted validation block in all fourteen places, because preserving them is exactly what it was built to do. The result compiles and runs and is, in every way that matters, a VB6 application wearing .NET clothing. Sometimes it is harder to maintain than the original, because the shims and compatibility wrappers the tool inserted are now a second layer of unfamiliar code sitting on top of the first.
The pragmatic middle path is the one we use. Mechanical conversion is run first and treated as scaffolding, not as a deliverable. The modules that carry money, safety or regulatory weight are then rearchitected on purpose: data access is lifted out of the forms, error handling is made explicit, calculations are isolated so they can be tested. Screens that simply display a list and have not changed since 2011 are left broadly alone, because rewriting them buys nothing. Deciding which parts get attention is the actual expertise, and that decision is made from the assessment rather than from a sales pitch. No proprietary tool is claimed here and none is sold to you.
The VB6 constructs that cause the trouble
These are the specifics that decide how long a migration takes and where it goes wrong. Each one is found and priced during assessment rather than discovered halfway through.
COM and ActiveX controls
Third-party OCX controls are the single most common blocker. Each one is triaged into keep-via-interop, replace with a supported .NET component, or reimplement. Interop is cheapest but keeps you tied to a 32-bit process and a registration step. Replacement is usually right for grids, charts and date pickers. Reimplementation is reserved for controls doing something custom, and licensing held by a vendor that no longer trades often forces that outcome.
Variant typing and implicit coercion
A Variant holding a string that looks like a number behaves one way in VB6 and another under .NET. Comparisons, concatenation and arithmetic on loosely typed values are where silent behaviour changes hide. Every Variant is given a real type, and the places where the old coercion rules were load-bearing are identified rather than assumed away.
Default properties
Assignments such as Text1 = "x" rely on a control exposing a default property, which .NET has no equivalent for. Every implicit reference has to be made explicit. The count is usually far higher than expected in older code, and a converter will handle most of them, though the ones involving object variables need a person to resolve.
On Error Resume Next
A great deal of VB6 code depends on errors being quietly swallowed. Replacing that with structured exception handling can surface failures the business never knew it had, sometimes on the first day of a parallel run. Each site is reviewed individually and the intended behaviour is agreed with you, because turning a hidden failure into a visible one is a business decision, not a technical detail.
Control arrays
WinForms has no control arrays. Twenty text boxes sharing one index-driven event handler must be rebuilt as a collection with wired-up handlers, or as a data-bound grid where that fits better. Data-entry screens built in the 1990s tend to lean on this heavily.
Fixed-length strings and Type blocks
Where user-defined Type blocks are written to binary files, the byte layout has to stay identical or historic files stop being readable. Padding, alignment and character encoding are all in play. The old files are read and re-read after migration as part of verification, not treated as a code detail.
Declare statements and the Win32 API
Direct API declarations carry pointer-sized assumptions that break the moment a 64-bit build is considered. Handles, structure sizes and string marshalling all need review. This is also where a decision gets forced: stay 32-bit and keep the shortcuts, or go 64-bit and rework every declaration properly.
DAO, RDO, ADO and bound data controls
Old data access has no future on .NET and is replaced regardless of which target is chosen. Bound data controls that wrote straight to a recordset from a grid are the fiddly part, because the validation and defaulting behaviour they provided has to be reproduced explicitly somewhere.
Crystal Reports and printing
Reports from the Crystal Reports 8 era are consistently the most underestimated item on the list. Printing that depends on a specific driver, tray, paper size or pre-printed stationery needs physical testing on the actual printer. Regulated and customer-facing layouts must match to the millimetre, which turns a report into a small project of its own.
GoSub, Option Base and array indexing
GoSub and Return have no direct .NET equivalent and must be restructured into methods. Option Base 1 modules mixed with zero-based ones are a reliable source of off-by-one defects, and they surface as a subtly wrong total rather than a crash.
Logic living inside event handlers
When pricing, scheduling or validation rules sit inside a button click, there is no clean layer to migrate. The rule has to be extracted, named, tested and then called. This is the work that actually improves maintainability, and it is also the work a converter will never do for you.
Registry, INI files and per-machine COM
Deployment often depends on registry keys, INI files beside the executable and COM components registered per machine by an installer nobody has the source for. All of it is documented during assessment, because a migration that cannot be installed on a clean machine is not finished.
Single-threaded apartment assumptions
VB6 code was written for a single-threaded apartment world. Adding parallelism to a migrated build because it is now easy is a reliable way to change behaviour under load. Threading is introduced only where it is needed and where the surrounding code has been made safe for it.
Locale and date handling
Applications that worked because every machine had identical regional settings break the moment a laptop with a different locale is introduced. Date parsing, decimal separators and string sorting are pinned explicitly rather than left to the operating system.
The reason this list matters is that VB6 migrations rarely fail loudly. The application builds, it opens, the screens look right and everybody relaxes. Three weeks later a rounding rule has drifted by a penny on high-value orders, a report subtotal excludes one category, or a printed label has the batch code in the wrong field. Compilation proves nothing about correctness. Numeric and output reconciliation against the old system is the acceptance test, and it is built before the code is touched.
How the migration is run
Eight stages, in this order. The first two produce a written document you own whether or not the rest of the work is placed with us.
1. Assessment
The source is inventoried: forms, modules, class modules, user controls, total lines, and whether the whole thing can still be built at all. Third-party components are catalogued with versions and vendor status. Database objects, report definitions, file formats and hardware touchpoints are listed. What comes out is a written picture of what you actually have, which in most cases is different from what the last documentation says.
2. Dependency and risk mapping
Every external dependency is mapped and rated: available and supported, available but abandoned, or unobtainable. Modules are scored by business criticality against migration difficulty, which is what produces the order of work. A module handling regulated output with an obsolete OCX and no source comments is where the risk sits, and it gets scheduled early rather than left to the end.
3. Target architecture
Language, UI framework, .NET version, data access approach, reporting engine and deployment model are chosen and written down with the reasoning behind each. Depth of rearchitecture is agreed per module rather than applied uniformly. You approve this document before implementation starts, and it is the reference the rest of the project is measured against.
4. Incremental or big bang
An incremental approach replaces the application in slices, old and new sharing a database while both are live. It lowers the risk of any single release and extends the total timeline, and it requires a seam clean enough to split. A single cutover is often the honest choice for a tightly coupled desktop application where forcing a seam costs more than the risk it removes. The tradeoff is presented with both timelines rather than decided for you.
5. Data migration
Schema and data are moved, whether that is Jet to SQL Server or one SQL Server generation to another. Types are mapped explicitly, dates and nulls are checked, orphaned records inherited over two decades are found and a decision is made about each. Migration scripts are run repeatedly against copies until two consecutive runs produce identical results, because cutover day is not when a data script should be exercised for the first time.
6. Parallel run
Both systems process the same real work for an agreed period while outputs are compared field by field. Every difference is triaged into an intended fix or a regression. This is the stage that catches the quiet failures, and it is also the stage most often cut short under schedule pressure. Cutting it short is how a rounding difference reaches a customer invoice.
7. Cutover
Scheduled around your operational reality, which for a plant usually means a shutdown window or a weekend. Deployment is rehearsed on clean machines beforehand, hardware is tested against the real printers and instruments, and a written rollback path exists with a decision point and a named person who owns the call.
8. Post-cutover support
Hypercare through the first month-end, quarter-end or batch cycle, because that is when the calculations nobody mentioned finally run. Handover covers build instructions, deployment steps, the dependency map and the reconciliation harness itself, so your team or any other supplier can pick the system up afterwards.
Proving the new build gives the same answers
Twenty-year-old VB6 applications effectively never have automated tests. That absence is a cost line in the estimate, because the harness has to be built before anything can be safely changed. Real inputs are captured from the live system across a period long enough to include the awkward cases: month-end, a credit note, a rush order priced by hand, a batch that failed inspection. Those inputs are replayed through the migrated build and every output is compared, including the ones people forget are outputs, such as printed labels, exported files and interface messages sent to another system.
The harness earns its cost twice. During the migration it is the acceptance criterion, and afterwards it becomes the regression suite that makes future change affordable, which is the difference between a modernised application and a new legacy application. Where the reconciliation work needs deeper coverage, performance testing or a formal test strategy for a regulated environment, our QA and testing services take that on as a separate workstream running alongside the migration.
When you should not migrate this application
Three conditions make a rewrite the better spend, and they are worth checking before an assessment is even booked.
The first is when the process the software encodes has already moved on. If staff keep a spreadsheet beside the application, if two screens are never opened, if orders are entered under a dummy customer because the real workflow changed in 2016, then migration preserves a description of a business you no longer run. Paying to carry that forward is the expensive option. The second is form count. Below roughly fifteen to twenty forms, a clean rebuild against current requirements frequently costs less than untangling twenty years of patches, and it produces something the next person can read. The third is a packaged product that covers the domain. Generic stock control, purchase ordering or bookkeeping is a solved problem, and buying it hands the maintenance burden to a vendor.
And when doing nothing for another year is defensible
Waiting is a legitimate decision when all of the following hold at once. The application runs on a small number of controlled machines that are not being refreshed. Spare hardware and disk images exist and have been tested by actually restoring one. No Windows or Server upgrade is scheduled that touches those machines. No auditor, insurer or customer has raised it. Under those conditions the money is better spent elsewhere this year, and any consultant telling you otherwise is selling.
Two caveats attach to that. The position depends entirely on somebody still being able to open the source and produce a working build, so if that has already been lost, you are not deferring a migration, you are deferring an archaeology project which is a different and larger cost. And the risk profile is a step function rather than a slope. Nothing degrades gradually. One driver update, one failed motherboard with no spare, or one insurer questionnaire moves you from comfortable to urgent in a week, and urgent migrations are the expensive kind. Knowing which condition would trip first, and watching that one, is the whole of the strategy.
What moves the price
No rate card is published here, because a number quoted before the code has been read is a guess dressed as a quote. What can be given honestly is the list of factors that move an estimate, roughly in order of impact. Smaller single-application engagements typically start in the low five figures in USD terms; the assessment produces the real figure.
Forms, modules and total lines
The baseline, though it scales less cleanly than expected. Thirty simple lookup screens cost less than eight dense forms carrying pricing logic. Both counts are taken, and the ratio between them says more than either alone.
Third-party COM and OCX dependencies
Count matters, obscurity matters more. A well-known grid with a supported .NET successor is routine. A comms control from a company that closed in 2004, with licensing tied to a key nobody can reissue, is the item most likely to force reimplementation and blow through the estimate.
Database coupling
A Jet MDB with logic in the forms is a different job from SQL Server with three hundred stored procedures. Both are manageable. Stored-procedure volume in particular is often left out of the first conversation and then reappears as a third of the work.
Existing test coverage
In practice this is always zero, so the reconciliation harness is built from scratch and priced as a real deliverable. Anyone omitting it from a quote has either not thought about verification or is planning to skip it.
Regulatory scope
FDA and GxP validation, ISO scope, food-safety traceability or financial reporting obligations add documentation, formal test protocols and evidence packs. The engineering may be identical; the compliance wrapper around it is not, and it is a substantial line rather than a rounding item.
Hardware and driver integration
Scales, scanners, label printers, PLCs and laboratory instruments have to be tested against the real device. Remote testing only goes so far, and site access windows drive both cost and schedule.
Source completeness
Complete source that still builds is the cheap case. Partial source, or source that no longer matches the deployed binary, adds a reconstruction phase before migration can begin. This is checked first because it changes everything downstream.
Report count
Consistently the most underestimated number in the whole exercise. Reports are remembered as "about a dozen" and turn out to be forty, half of them variants for a specific customer or regulator, several of them pixel-sensitive against pre-printed stationery.
Timeline shapes, and what makes them slip
Shapes rather than promises, because a date given before assessment is not worth having. A single-module pilot, chosen to prove the approach and the reconciliation method on something real but contained, runs in weeks. A mid-size line-of-business application with a few dozen forms, a handful of third-party controls and a working database runs in months. An estate of several interlinked applications sharing data, where the sequencing between them is itself a design problem, runs in quarters and is planned as a programme rather than a project.
What causes slippage is predictable enough to plan against. A COM dependency with no replacement, discovered after implementation has started, because the assessment could not reach a machine where it was still installed. Source that will not build, which is only ever confirmed by trying. An undocumented calculation that nobody in the building can explain, where the only path is reverse-engineering it from historic output and getting the result signed off. Hardware that can be tested only on site during a shutdown, which turns a two-day task into a six-week wait. A report that must match a regulated or customer-facing layout exactly, where "close enough" is rejected. An operator population that resists a changed screen and escalates rather than adapts. And a plant that cannot take downtime except in one fixed window, which means missing it costs a quarter rather than a week.
Each of these is surfaced in the assessment where it can be, and flagged as an open risk with a cost range where it cannot. Where an application needs new capability alongside the migration rather than a like-for-like move, that scope is separated and estimated through our custom software development team, so a migration and a feature project are never billed as one indistinguishable number.
VB6 to .NET migration questions
Can an automated converter do the whole migration on its own?
A converter translates syntax, form layouts and declarations quickly, and that removes weeks of mechanical typing. What it cannot do is judge. The output is a .NET project that still thinks in VB6: Variants everywhere, swallowed errors, business logic buried in button handlers. Compilation is not correctness either, because behaviour still has to be reconciled against the old system. Conversion is best treated as scaffolding, after which the modules that carry money, safety or regulatory weight are rearchitected deliberately and the rest is left alone.
What happens to an ActiveX control whose vendor has disappeared?
Three paths are available and the choice is made control by control. The control can be kept and called through COM interop, which works while the OCX still registers and a 32-bit process is acceptable. It can be swapped for a supported .NET component, which is usually right for grids, charts, calendars and masked entry fields. Or its behaviour can be reimplemented, which is reserved for controls doing something genuinely custom such as instrument communication or barcode encoding. Licence keys held by a dead vendor often force the second or third path.
Does the application have to become a web app, or can it stay on the desktop?
It can stay on the desktop. WinForms on modern .NET keeps the layout, the keyboard flow and the deployment model your operators already know, and it is the shortest route off the unsupported platform. A web application earns the extra disruption only when remote or multi-site access is wanted, or when nothing can be installed on shop-floor machines any more. Web is also the option most likely to meet resistance from people who have used the same screen for fifteen years, so retraining belongs in the decision rather than after it.
Do we have to move the Access database at the same time?
Not necessarily, and separating the two decisions usually lowers risk. The data access layer is rewritten either way, because DAO, RDO and bound data controls have no future on .NET, but the Jet MDB file can stay where it is for the first release. A move to SQL Server is worth doing early when the file is shared across a network, when locking conflicts already happen daily, or when compacting no longer keeps the file healthy. Otherwise the database migration is run as a second, separately verified step.
How is correctness proven when the original application has no tests?
A reconciliation harness is built before application code is touched. Real inputs are captured from the live system, such as quotes, works orders, batch records and label runs, and the same inputs are pushed through the migrated build so outputs can be compared field by field. Totals, rounding, date boundaries, report subtotals and printed layouts are the things that break quietly. Differences are triaged into intended fixes and genuine regressions. Acceptance is a clean comparison run across a representative period, not a successful build.
What if the source code is missing or will not compile?
The assessment answers this first, because every later decision depends on it. Where partial source exists, the gap is measured by comparing the shipped binaries against what the source actually produces. Where a module is missing entirely, its behaviour is recovered from the database schema, the file formats it writes, printed output and interviews with the people who use it daily. Decompilation is possible for VB6 and sometimes helps, though the result serves as a specification aid rather than a maintainable starting point. A source-less module is normally rebuilt from observed behaviour.
Is there a trial period before the migration work is committed to?
The assessment is free, and every engagement that follows starts with a 14-day risk-free trial — you pay only if you are satisfied. Those two weeks go into establishing what actually exists: which source builds, which dependencies are unobtainable, and which screens are genuinely in use. The scope is written from that, not from an assumption.
Start with the assessment, not the quote
Bring the source folder, the list of screens people actually use, and the reason this became urgent. What comes back is a written picture of what you have, which dependencies are genuinely unobtainable, which target makes sense for your situation, and an honest view on whether migrating is the right spend at all. If the answer is a packaged product or another year of waiting, that is what you will be told.
Chat for a quick question. Use the form to send a brief, a spec or your requirements in detail.