In this article, I’ll walk you through how we set up a sequential auto-incrementing of the app revision when we commit to the main branch using the Azure DevOps Pipeline.

We do this so that all users and developers can see which version of the product is deployed in a particular environment.

Task

Depending on the architecture of the application, the version number can be stored in different places. Here I will provide an example .NET Core application where the version number is in the AssemblyVersion tag of a project XML file with a .csproj extension. By default, this tag is not in the project; you can initiate its addition by setting its value in the project properties, for example, 1.0.0.1.

In “classic” .NET Framework applications, the version number is stored in the Properties \ AssemblyInfo.cs file. This is no longer an XML file. The way you find and edit the tag you want will be slightly different.

When working with the git repository, we use the trunk approach described here: trunkbaseddevelopment.com. In this approach, unlike GitFlow, developers create branches with a short lifecycle from the main branch. This methodology, in particular, is promoted by Microsoft, we use a slightly simplified approach compared to their Release Flow described in this article.

In our case, the code gets to the main branch as a result of pull requests, but without the control automatic build. Therefore, we run the CI build immediately after committing to the master. We want this assembly to increment the last digit of the application version number in the AssemblyVersion if this assembly succeeds. For example, it was 1.3.4.15, it is 1.3.4.16.

Solution

The pipeline script looks like this in general terms:

  1. Get the contents of the main branch.
  2. Update version number using PowerShell script. To do this, a new branch must be created on the agent. We will make all the changes in this branch, use it to build and then merge it into the main branch. This branch will remain local, it will not be pushed to the server repository. We’ll delete it at the end of the process.
  3. Build.
  4. If the build was successful, update the version number in the repository using the PowerShell script as well.

There are several important points worth mentioning.

  • From the moment the content of the main branch is received until the end of the build, new commits can get into the main branch. For this reason, the Azure agent does not receive the contents of the branch, but a specific commit. After receiving the code, the repository becomes HEAD detached. That is why it is necessary to update the version number in a separate branch.
  • The Azure agent that will update the repository must additionally authorize in it. To do this, you need to save the personal access key (PAT) of one of the authorized users at the pipeline level. This value is secret and must be properly maintained. It will be used when submitting a commit over HTTPS.
  • In order not to “force” the CI assembly when a new version number gets into main, you must specify [skip ci] in the comment when executing a commit.

Implementation

We create a CI assembly that should run automatically after the code gets into the main branch. Therefore, the pipeline script begins with the phrase:

trigger: – main

The following is the standard block for choosing an agent, setting base variables, and updating NuGet packages:

Pool:
vmImage: 'windows-latest'

variables:
solution: ‘**/*.sln’
buildPlatform: ‘Any CPU’
buildConfiguration: ‘Release’

steps:
– task: [email protected]
– task: [email protected]
inputs:
restoreSolution: ‘$(solution)’

Now that the agent has received the fresh content of the main branch from the repository, execute the PoweShell script.

task: PowerShell @ 2
displayName: ‘Assembly Version Generation’
# the first part – increasing the version number
# if the build crashes, the new version number is not saved in the repository
inputs:
targetType: ‘inline’

script: |

The details of this script may differ, for example, if you need to update the version number for several projects at once, or if you use a different principle of storing the version number.

The –quiet option when calling git is specified so that the pipeline does not perceive service messages from git as error messages and does not stop the process.

# the name of the XML project file to be modified
$ ProjectFile = ‘. \ Azure-devops-versioning \ azure-devops-versioning.csproj’

# create a new branch
git branch DevOps / test _ $ (Build.BuildNumber) –quiet
# switch to new branch
git checkout DevOps / test _ $ (Build.BuildNumber) –quiet

# looking for a string containing the <AssemblyVersion> tag
foreach ($ line in Get-Content -Path $ ProjectFile)
{$ AssemblyVersionLine = $ line | Select-String -Pattern ‘<AssemblyVersion>’ -CaseSensitive
if ($ AssemblyVersionLine) {break}
}

# look for the version number in the line using the -match operation
$ AssemblyVersionLine -match (‘(<AssemblyVersion>) (. +) (</AssemblyVersion>) $’)
# get the version string
$ Version = $ Matches [2]
# split the string into an array
$ VerParts = $ Version.split (‘.’)
# get the last part of the array and increase it by 1
$ VerParts [3] = ([int] $ VerParts [3] + 1)
# collecting back into a line through a dot
$ NewVersion = $ VerParts -join ‘.’
# get a new line with <AssemblyVersion> tag and new version number
$ NewAssemblyVersionLine = $ AssemblyVersionLine -replace $ Version, $ NewVersion

# replace the old line with the <AssemblyVersion> tag with a new one
(Get-Content $ ProjectFile) | Foreach-Object {$ _ -replace $ AssemblyVersionLine, $ NewAssemblyVersionLine} | Set-Content $ ProjectFile

# enter information about the user-agent
git config user.email “[email protected]
git config user.name “Azure Agent”
# index the modified file
git add $ ProjectFile
# commit changes to the local repository with a comment
git commit -m “[skip ci] Pipeline Modification: AssemblyVersion = $ NewVersion”

In this script, we created a new local branch using a build number that is unique in Azure DevOps. That is, with each CI build, a new local branch will be created on the agent. We switched to this new branch, found the required file and replaced the AssemblyVersion in it, increasing the last number by 1. We made a commit to the local repository and started the build using the building block:

– task: VSBuild @ 1
inputs:
solution: ‘$ (solution)’
msbuildArgs: ‘/ p: DeployOnBuild = true / p: WebPublishMethod = Package / p: PackageAsSingleFile = true / p: SkipInvalidConfigurations = true /p:DesktopBuildPackageLocation=”$(build.artifactStagingDirectory>\Web :App.zipI “Deploy Default Web Site “‘
platform: ‘$ (buildPlatform)’
configuration: ‘$ (buildConfiguration)’

If the build ends with an error, this is where the process ends. A local branch with a build number will remain on the agent, which can be deleted, but this is not critical for us, so we will not dwell on this here.

If the build is completed successfully, we run the PowerShell script.

– task: PowerShell @ 2
displayName: ‘Send new version to main’
# second part – after a successful build, the new version is sent to the server repository
inputs:
targetType: ‘inline’
script: |

This script will switch us to main, receive an update to main, merge our branch with the main branch, delete our branch and push to the central repository, logging in with the PAT token:

# switch to the main branch
git checkout main –quiet
# update local branch main
git pull –quiet

# merge our branch with the main branch, specify the version number in the comment
git merge DevOps / test _ $ (Build.BuildNumber) -m “[skip ci] Pipeline Modification: AssemblyVersion = $ NewVersion” –quiet

# delete the local branch
git branch -d DevOps / test _ $ (Build.BuildNumber)

# push the local main branch to the server repository
# PAToken is a pipeline variable containing the value of the private key
git push https: // kanailov: $ (PAToken) @ github.com / Kanailov / azure-devops-versioning.git main –quiet

In the above example, the git repository is on GitHub