Automating Version Control for D365FO ISVs: A DevOps Engineer’s Approach

Automating Version Control for D365FO ISVs: A DevOps Engineer’s Approach

10 Dec. 2025
7 min read

As a DevOps Engineer working in the Microsoft Dynamics 365 Finance and Operations (D365FO) ecosystem, one of my most interesting challenges came when I was asked to automate versioning for our company’s ISV solutions.

We needed a reliable and transparent way to keep track of the versioning of our own ISVs—not just for internal housekeeping, but because it was absolutely critical for our development workflow and for our clients. Without consistent versioning, it becomes almost impossible to maintain clarity about what exactly is deployed where, which features are included in which build, or whether a client is running the most recent, stable version. Having a clear, always-up-to-date version wasn’t just “nice to have”; it was essential. It would allowed us to quickly identify issues, trace changes back to specific builds, avoid mismatches between environments, and ensure that the client side was never left running outdated or incompatible components. In short, accurate version tracking was the backbone of predictable delivery.

In theory, the solution seemed almost trivial:

“After each build, automatically update the ISV version and commit that change back into version control.”

Clean. Simple. Obvious.

At least, that’s what I thought at the beginning.

But once I started working through the details, it quickly became clear that this wasn’t just a small tweak—it was an entire automation puzzle living inside the Azure DevOps ecosystem. Each step came with its own quirks, dependencies, and hidden complexity, turning what looked like a one-liner into a much larger challenge.

The Challenge

Our setup was fully based on Azure DevOps, TFVC and PowerShell scripts — both for builds and deliveries.

The requirements were clear and specific:

  • Update the ISV descriptor file automatically with a new version number.
  • Use the build pipeline number format from build pipeline: $(Year:yyyy).$(Month).$(DayOfMonth)$(Rev:.r) Example: 2025.11.12.1
  • Сommit the updated descriptor file back into version control after every successful build.

So far, so good.

But here came the cornerstone problem — how to avoid an infinite build loop.

Every time the pipeline commits a change, the trigger picks it up and starts another build… which commits again… and so on. A DevOps version of Groundhog Day. 

The Insight

While exploring ways to reduce unnecessary pipeline noise, I stumbled onto a small but surprisingly powerful detail: adding ***NO_CI*** (or markers like [skip ci]) to a commit message completely suppresses CI triggers. It’s one of those features that feels almost hidden in plain sight — simple, unobtrusive, yet incredibly effective.

That realization became the real “Aha!” moment.

If a single keyword can prevent a pipeline from firing, then it can also stop automation from accidentally triggering itself. And if that mechanism is applied deliberately during automated check-ins, it creates a clean, controlled workflow where updates happen quietly in the background without setting off a chain reaction of pipeline executions.

This opened the door to an elegant idea: use ***NO_CI*** strategically to prevent redundant or self-generated pipeline runs altogether, cutting out the loop before it even begins.

The Solution — Step by Step

I designed a PowerShell-based solution that tied everything together.

1️⃣ Step 1. Detect Changes

Using the tf.exe module, the script checks whether there were any modifications in the ISV models.

The list of ISVs to check is stored in pipeline variables, so it’s flexible and easy to maintain.

2️⃣ Step 2. Update the Descriptor

If changes are found, the script automatically updates the version section of the ISV descriptor file.

The version format follows the built-in pipeline variable $(Build.SourceVersion)

3️⃣ Step 3. Check-In with “NO_CI”

Here’s where the real trick comes in.

When the script commit the updated files, it appends ***NO_CI*** to the commit message. This small but powerful marker tells Azure DevOps to ignore CI triggers for that commit, ensuring the pipeline doesn’t accidentally retrigger itself. It’s a simple safeguard that keeps the whole process from falling into an endless automation loop.

4️⃣ Step 4. Skip the Loop

Because the commit message includes ***NO_CI***, Azure DevOps automatically recognizes it and suppresses new pipeline run that would normally be triggered. No extra logic needed — the trigger itself does the work. This prevents the DevOps version of Groundhog Day, where the pipeline repeatedly restarts itself.

As a result:

  • The pipeline sees the ***NO_CI*** marker in the commit message and understands that this run should be skipped.
  • Instead of kicking off another build, it quietly steps aside — no redundant executions, no self-triggered loops.
  • The build queue stays clean and uncluttered, with only meaningful runs making it through.

This small check eliminates delays for other developers’ builds and keeps our environment clean and responsive.

Maintenance and Scalability

To keep everything maintainable, we adopted a unified scripts repository.

Each build downloads the latest PowerShell scripts from that repo at the start of the run.

After the build finishes — whether successful or failed — the scripts are deleted from the build agent.

This ensures:

  • Always up-to-date automation scripts
  • No manual updates on build agents
  • Consistent behavior across all pipelines

The Results

After implementing this approach, we immediately saw several benefits:

1) No more manual versioning

Developers no longer need to remember to update version numbers — it’s done automatically and reliably after every build.

2) Clear visibility for customers

Each ISV build version is precise, timestamped, and traceable. Customers can easily see which version they are using.

3) No performance impact

The new steps didn’t increase build duration or queue time.

4) Modular and customizable

The solution can be applied selectively to specific models and easily adapted to other pipelines or projects.

Lessons Learned

Sometimes in DevOps, the hardest problems aren’t about using new tools — they’re about using existing ones creatively.

Azure DevOps offers a surprising amount of flexibility once you understand the tools built into the platform. In our case, a bit of creativity — and the smart use of built-in tools— turned what could have been an ongoing headache into a simple, elegant solution.

It’s also a reminder that good DevOps isn’t just about automation — it’s about building systems that think for themselves, so your teams can focus on delivering value instead of fixing process issues.


Maksim Nosov
Maksim Nosov Author
IT professional with experience in IT service management, design, and architecture, based on ITIL practices. Has worked across the education sector and large international organizations, supporting structured and scalable IT environments.

Enter your contacts to download
the information
about us immediately

* - Required fields

Thank you. Your application form was received.

Click to download the file if it does not start automatically

Table of Contents:

Other articles on this topic


Using cookie files

We use cookies to collect information for various purposes, such as functional, statistical and marketing, as well as to improve user experience.

By pressing the “Accept everything” button, you consent to the use of cookies for all these purposes. You also have the opportunity to choose the specific goals for which you are ready to give consent, noting them with a flag and clicking “Save the settings”.