Set assembly and app version to a matching build name in TFS 2015 or VSO Build.VNext

One of the most common customizations in TFS XAML build templates was to automatically update the assembly version number. This can also be done in build vNext using a small power shell script. I also added the option to apply the version to SharePoint / Office 365 apps.

The out of the box build name for VNext builds is not very descriptive. It’s just a number that consists of the current date and a revision number: $(date:yyyyMMdd)$(rev:.r)
If you have many build this is not very helpful.  So its always a good thing to customize you build name and at least add the build definition name (you can use the variable  $(BuildDefinitionName) for this) to it. You can do this on the “General” tab under “Build Number Format”.

image

What you can do is to add variables that you define on the variable tab – like major and minor version.

image

You can then use the variables in the name of the build. Here is an example that consists of the build definition name, a major and minor version that are configured via build variables and a revision number that is build from the year and “dayofyear” to ensure a valid sorting of the builds:

$(BuildDefinitionName)_$(MajorVersion).$(MinorVersion).$(Year:yy)$(DayOfYear)$(rev:.rr)

You can then use a PowerShell script Set-AssemblyVersion.ps1 in a VNext build to set all assembly versions – and optionally the version of SharePoint / O365 Apps – to a build number that is extracted from the build name using a regular expression.

<#
.Synopsis
Sets the assembly version of all assemblies in the source directory.
.DESCRIPTION
A build script that can be included in TFS 2015 or Visual Studio Online (VSO) vNevt builds that update the version of all assemblies in a workspace.
It uses the name of the build to extract the version number and updates all AssemblyInfo.cs files to use the new version.
.EXAMPLE
Set-AssemblyVersion
.EXAMPLE
Set-AssemblyVersion -SourceDirectory $Env:BUILD_SOURCESDIRECTORY -BuildNumber $Env:BUILD_BUILDNUMBER
.EXAMPLE
Set-AssemblyVersion -SourceDirectory ".\SourceDir" -BuildNumber "Dev_1.0.20150922.01" -VersionFormat "\d+\.\d+\.\d+\.\d+"
#>

[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
[Alias()]
[OutputType([int])]
Param
(
# The path to the source directory. Default $Env:BUILD_SOURCESDIRECTORY is set by TFS.
[Parameter(Mandatory=$false, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$SourceDirectory = $Env:BUILD_SOURCESDIRECTORY,

# The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex.
[Parameter(Mandatory=$false, Position=1)]
[ValidateNotNullOrEmpty()]
[string]$BuildNumber = $Env:BUILD_BUILDNUMBER,

# The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex.
[Parameter(Mandatory=$false, Position=2)]
[ValidateNotNullOrEmpty()]
[string]$VersionFormat = "\d+\.\d+\.\d+\.\d+",

# Set the version number also in all AppManifest.xml files.
[Parameter(Mandatory=$false)]
[switch]$SetAppVersion
)

<#
.Synopsis
Sets the assembly version of all assemblies in the source directory.
.DESCRIPTION
A build script that can be included in TFS 2015 or Visual Studio Online (VSO) vNevt builds that update the version of all assemblies in a workspace.
It uses the name of the build to extract the version number and updates all AssemblyInfo.cs files to use the new version.
.EXAMPLE
Set-AssemblyVersion
.EXAMPLE
Set-AssemblyVersion -SourceDirectory $Env:BUILD_SOURCESDIRECTORY -BuildNumber $Env:BUILD_BUILDNUMBER
.EXAMPLE
Set-AssemblyVersion -SourceDirectory ".\SourceDir" -BuildNumber "Dev_1.0.20150922.01" -VersionFormat "\d+\.\d+\.\d+\.\d+"
#>
function Set-AssemblyVersion
{
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
[Alias()]
[OutputType([int])]
Param
(
# The path to the source directory. Default $Env:BUILD_SOURCESDIRECTORY is set by TFS.
[Parameter(Mandatory=$false, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$SourceDirectory = $Env:BUILD_SOURCESDIRECTORY,

# The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex.
[Parameter(Mandatory=$false, Position=1)]
[ValidateNotNullOrEmpty()]
[string]$BuildNumber = $Env:BUILD_BUILDNUMBER,

# The build number. Default $Env:BUILD_BUILDNUMBER is set by TFS and must be configured according your regex.
[Parameter(Mandatory=$false, Position=2)]
[ValidateNotNullOrEmpty()]
[string]$VersionFormat = "\d+\.\d+\.\d+\.\d+",

# Set the version number also in all AppManifest.xml files.
[Parameter(Mandatory=$false)]
[switch]$SetAppVersion
)

if (-not (Test-Path $SourceDirectory)) {
throw "The directory '$SourceDirectory' does not exist."
}

$Version = Get-Version -BuildNumber $BuildNumber -VersionFormat $VersionFormat

$files = Get-Files -SourceDirectory $SourceDirectory

Set-FileContent -Files $files -Version $Version -VersionFormat $VersionFormat

if ($SetAppVersion.IsPresent)
{
$files = Get-AppManifest -SourceDirectory $SourceDirectory
Set-AppManifest -Files $files -Version $Version
}
}

function Get-Version
{
[CmdletBinding()]
[Alias()]
[OutputType([string])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$BuildNumber,

[Parameter(Mandatory=$true, Position=1)]
[ValidateNotNullOrEmpty()]
[string]$VersionFormat
)

$VersionData = [regex]::matches($BuildNumber,$VersionFormat)

if ($VersionData.Count -eq 0){
throw "Could not find version number with format '$VersionFormat' in BUILD_BUILDNUMBER '$BuildNumber'."
}

return $VersionData[0]
}

function Get-Files
{
[CmdletBinding()]
[Alias()]
[OutputType([System.IO.FileSystemInfo[]])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$SourceDirectory
)

$folders = Get-ChildItem $SourceDirectory -Recurse -Include "*Properties*" | Where-Object { $_.PSIsContainer }

return $folders | ForEach-Object { Get-ChildItem -Path $_.FullName -Recurse -include AssemblyInfo.* }
}

function Get-AppManifest
{
[CmdletBinding()]
[Alias()]
[OutputType([System.IO.FileSystemInfo[]])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[ValidateNotNullOrEmpty()]
[string]$SourceDirectory
)

return Get-ChildItem -Path $SourceDirectory -Recurse -Filter "AppManifest.xml"
}

function Set-FileContent
{
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
[OutputType([int])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[System.IO.FileSystemInfo[]]$Files,

[Parameter(Mandatory=$true, Position=1)]
[string]$Version,

[Parameter(Mandatory=$true, Position=2)]
[string]$VersionFormat
)

foreach ($file in $Files)
{
$filecontent = Get-Content $file

if ($PSCmdlet.ShouldProcess("$file", "Set-AssemblyVersion"))
{
attrib $file -r
$filecontent -replace $VersionFormat, $Version | Out-File $file
Write-Verbose -Message "Applied Version '$Version' $($file.FullName) - version applied"
}
}
}

function Set-AppManifest
{
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
[OutputType([int])]
Param
(
[Parameter(Mandatory=$true, Position=0)]
[System.IO.FileSystemInfo[]]$Files,

[Parameter(Mandatory=$true, Position=1)]
[string]$Version
)

foreach ($file in $Files)
{
[xml]$xml = Get-Content $file

$xml.App.Version = $Version

if ($PSCmdlet.ShouldProcess("$file", "Set-AppManifest")){
$xml.Save($file.FullName)
}
}
}

if (-not ($myinvocation.line.Contains("`$here\`$sut"))) {
Set-AssemblyVersion -SourceDirectory $SourceDirectory -BuildNumber $BuildNumber -VersionFormat $VersionFormat
}

Just add a “PowerShell Script” build step for your actual build step and reference the script in your repository.

image

I really love the new build system. It’s so easy and customizable. With a little script as the starting point you can configure everything according to the needs of your customers.

19 thoughts on “Set assembly and app version to a matching build name in TFS 2015 or VSO Build.VNext

  1. Man.
    I would like to thank you for that post. But there is something that make me lose my mind for some time.
    in the script the regex you show is “\d+\.\d+\.\d+\.\d+” but in the examples the build number is build with that line
    $(BuildDefinitionName)_$(MajorVersion).$(MinorVersion)_$(Year:yy)$(DayOfYear)$(rev:.rr) that definition will not work with the regex because there is an “_” insted of a dot (“.”) that make me craaazy for a while 🙂 but i eventualy findout the issue and change that for $(BuildDefinitionName)_$(MajorVersion).$(MinorVersion).$(Year:yy)$(DayOfYear)$(rev:.rr) and it work perfectly.
    Thanks.

  2. Hi,

    Can I set the product version to a string? lets said “My software Beta 1 [1234]”.

    Thanks & Best Regards,
    Ken

    1. What do you mean with product version? The build name can be a string – as long as you can extract the version numbers for the assemblies. The assembly version must match the pattern Major.Minor.Build.Revision like 1.0.0.0

  3. I am trying to assign the build number from TFS build definition, but the assembly is a ASP.NET Core project. Then the version is gather from project.json. So your script doesn’t apply ¿right?

    1. You have to modify Get-Files to return the Project.json instead of the AssemblyInfo.cs. Then check that your regex in $VersionFormat is correct that the Set-FileContent does not mess up your file.

  4. I tried this, and the Script writes that the Version is applied.
    (Applied Version ‘1.3.16244.01’ C:\Users\Josma\Downloads\agent(1)\_work\1\s\source\mbit.merpa.common\Properties\AssemblyInfo.cs – version applied)
    The ‘Visual Studio-Build’ Task starts after the PowerShell Task

    But in my case there is almost the old version Number in the *.dll and *.exe files. All AssemblyVersion Files have there Originial Versionnumber.

    What mistake i had made?
    Must i also define a Variable in the Assemblyversion.cs file?
    in my case there stands:
    [assembly: AssemblyVersion(“1.2.*”)]

      1. Thanks Mike!
        I changed the AssemblyVersion form 1.2.* to 1.2.0.0 and it worked!

        Regex – thats an own mystery 😉

  5. I am able to run the script in TFS 2015 successfully and make the changes locally. I added code to open connection to TFS and check in the code, but changes are not applied into TFS.

    $url = “$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/definitions/$($env:SYSTEM_DEFINITIONID)?api-version=2.0”
    Write-Host “URL: $url”
    $definition = Invoke-RestMethod -Uri $url -Headers @{
    Authorization = “Bearer $env:SYSTEM_ACCESSTOKEN”
    }
    Write-Host “Definition = $($definition | ConvertTo-Json -Depth 1000)”

    1. What do you mean with changes are not applied? If you want your repository to reflect the versions you have to do a “git commit” “git push” or use git version (see link above).

  6. Thanks Mike for your reply. I was trying to say I want the changes reflected in repo, our repo is Team Foundation Version Control.

  7. Hi dear , It is working perfectly well but if i need to apply assembly version on 5 projects out of 20 , so what changes should i have to done ? That 5 projects can be hard coded path .

    1. Sorry I missed your comment. You woul dhave to change the Get-Files function and add additional filters. But as I mentioned earlier – there is an extension now in the marketplace. The extension supports patterns like **/src/**/*.cs

  8. Thank you for the great script. However, I was wondering what the empty [Alias()] attribute is good for?

Leave a reply to Samuel Egger Cancel reply