← Blog
.NET FrameworkAzure DevOpsCOMCI/CD

Eight Agents, One COM: Fixing a .NET Build in Azure DevOps

Published · 2 min read · Tomasz Dłuski

The client’s pipeline built a .NET Framework 4.8 project with a COMReference. It registered the COM component DLL before compilation and unregistered it afterward. With one job, the sequence worked. The client had eight Azure DevOps agents on one VM. Just two concurrent jobs were enough to trigger the race.

Separate workspaces, shared registry

Each agent had its own workspace, but the component registration was shared across the machine. COMReference resolved the dependency through the Windows registry, so one job’s setup affected the other. Our script treated an existing registration as sufficient for the second job to continue. Here is how that played out between two of the eight agents:

One VM, shared registry

Two of the eight agents shown

Hover over an element for details.

Before: a shared dependency

Agent A

checkout A

Build OK → cleanup

Unregisters COM

COM registry

one entry for the whole VM

Entry removed by A

Agent B

checkout B

Build failed

Cannot resolve COMReference

A cleans up before B reaches compilation. B loses its dependency and the build fails.

After: each job has its own interop assembly

Prepared beforehand: COM types → tlbimp.exe → .NET assembly

Agent A

checkout A

Own copy of the interop assembly

Cleanup removes only A’s files

Agent B

checkout B

Own copy of the interop assembly

Build succeeded

This diagram shows compilation. The application still needs COM at runtime.

Deleting a DLL does not itself unregister COM: it can leave a registry entry pointing to a missing file. Our problem was the setup and cleanup lifecycle of a shared dependency. Finding the component at the start of job B did not guarantee it would still be available during compilation.

The fix: an interop library

We moved the interop layer into a separate library interop.comproject.csproj. The main project gained a regular .NET dependency on that layer. Its build no longer needed to register and unregister COM on the shared agent.

We generated the wrapper with tlbimp.exe from the COM component’s type library. The resulting .NET assembly supplies the interop types needed for compilation. The application build uses that prebuilt library instead of resolving COMReference through the shared VM’s registry.

That stopped one job’s cleanup from breaking another job’s compilation. The application’s dependency on the COM component at runtime remained a separate concern.

Microsoft documentation: prebuilt interop assemblies and tlbimp.exeBack to the blog