Recently, in my day to day job I have been doing a lot of automation and customisation using JetBrains’ TeamCity product, specifically around automated project builds, unit testing, installer builds and installer updates.

One process I recently created a custom build step for on our TeamCity server, was to create an updater configuration file for one of our products, which uses Advanced Installer for it’s installation package. The product has an item in the main menu that lights up when a newer version is available. Clicking this launches the Advanced Installer updater.exe program which is injected into the application’s installer when the installer is built in another step. The updater program reads the updates.ini file to get info on the new version update as well as it’s location, which allows the user to then update the application directly from the main menu.

To automatically create this updates.ini file each time a build is run on TeamCity, I decided to go with PowerShell. I needed two bits of information for a start:

  • The assembly version number (which for this program is partly generated using TeamCity’s build counter incrementer)
  • The file size of the installer package once built

As a side note, the application’s assembly versions are handled by TeamCity’s “AssemblyInfo Patcher”, which is an extra feature you can add to your builds under the “Additional Build Features” area for your TC project. This ensures that the application gets the same version number that TeamCity assigns to each build, and therefore we insert the correct build number from TeamCity into our updates.ini file too.

To start, I created a new build step for the TC project, right at the end of the build process, and setup an initial Advanced Installer “template” updates.ini file in a specific location that the script refers to. I set the build step to use PowerShell, version 3.0, 64bit and changed the “Script” option to “Source code”, allowing me to paste my script directly into the build step page. Here is the actual script. Note the few variables at the top to define first – paths to the initial updates.ini template file, etc…

 

# Setup variables

$BuildArtifactSetupFile = "%system.teamcity.build.workingDir%\TestProject.Installer\TestProject.Installer\Express\DVD-5\DiskImages\DISK1\setup.exe"
$InstallerDestination = "C:\Sean\Dropbox\Public\Updates" 
$FileSizeBytes = (Get-Item $BuildArtifactSetupFile).Length
$VersionNumber = "0.1.0.%build.counter%"
$UpdateConfigLocation = "C:\Sean\Dropbox\Public\Updates\updates.ini"
$UpdateConfigLocationRedirect = "C:\Sean\Dropbox\Public\Updates\updates.ini"
$UpdateConfigTempLocation = "C:\Sean\Dropbox\Public\Updates\updates-temp.ini"

# Do the regex search and replaces and update the configuration file

$contentStep1 = (Get-Content -Path $UpdateConfigLocation | Out-String) -replace "\b(?<=Version = )([0-9]+.[0-9]+.[0-9]+.[0-9])", $VersionNumber
$contentStep2 = $contentStep1 -replace "\b(?<=Size = )(\d*)", $FileSizeBytes

# Place the modified regex search and replace content into the redirection file for now
Set-Content $UpdateConfigLocationRedirect $contentStep2

#  Remove any newline characters or blank lines from the content in the redirection file and place revised content into temp file
Select-String -Pattern "\w" -Path $UpdateConfigLocationRedirect | ForEach-Object { $_.line } | Set-Content -Path $UpdateConfigTempLocation

# Remove the original updates.ini file
Remove-Item $UpdateConfigLocation

# Rename the temp updates.ini file to the original file name.
Rename-Item -Path $UpdateConfigTempLocation -NewName $UpdateConfigLocation

# Now copy the build artefact installer file to target location...
Copy-Item -Path $BuildArtifactSetupFile -Destination $InstallerDestination

 

So, to explain the above, the high level process is as follows:

  • Setup our initial variables for the script – we assign the TC %build.counter% variable to our PowerShell $VersionNumber info, and setup the locations of our updates.ini and temporary updates.ini files, as well as the location that TeamCity builds and outputs an installer package to each time it builds a pushed commit. We also get the size in bytes of this setup.exe package and specify a destination to copy the installer to, so that the updater utility in our application can access the new version’s installer when users try to update
  • Regex search and replace – we use $contentStep1 to replace the template updates.ini file’s “Version” line with our new version number. We then use $contentStep2 to assign the correct file size for the setup.exe installation package to our updates.ini file on the “Size” line.
  • Next, we remove any empty lines in the file by searching for any lines on our updates.ini file that contain any word characters (letters, numbers, underscores) using “\w” as a pattern. Any lines matching this pattern (i.e. not the lines with blank spaces), get piped into our temporary updates.ini file using Set-Content. A note here: Using the Out-File cmdlet instead of Set-Content messes with the formatting of the file I found, and the Advanced Installer updater utility can’t read the updates.ini file correctly in this format, so I found using Set-Content worked just fine. The drawback to this, is that Set-Content places a lock on the file, whereas Out-File does not, hence the reason we use a temporary, staging file, remove the original updates.ini file, then rename the temporary file back to the original file name later.
  • As explained above, we then remove the older updates.ini file (used on previous builds), and rename our temporary updates.ini file to “updates.ini”
  • Finally, we copy the built setup.exe installer file from our TeamCity project’s output directory and place this in the same location that our updates.ini file exists in. This installer package will also be tagged with the same version number present in our updates.ini file. Hint: you can get Advanced Installer to tag installation package version numbers with an .exe / assembly version number in the Advanced Installer product – this can be done in the Product Information settings (ProductVersion property) using the AI GUI.

Here is how my build step looks:

teamcity-powershell-updater-config

 

This is the resulting updates.ini that gets automatically created by the above build step each time a build is run on the TeamCity build server:

updates-ini-example

The completed TeamCity build summary showing the build number:

testproject-teamcity-summary

Notes / improvements:

  • You shouldn’t necessarily leave your updates.ini file on your TeamCity server to be read by your product’s updater.exe utility – this could be on a remote web server, and your PowerShell script could simply just update the updates.ini files remotely on this server from your TeamCity build server.
  • You could parameterise the script and pass through TeamCity environment variables instead of a flat script as it is above – think about creating Functions that you can re-use elsewhere.

Leave a Reply

Your email address will not be published. Required fields are marked *