Updating AssemblyInfo.cs version information via batch file
Over a year ago I wrote how to build and publish NuGet packages
via Jenkins in which I stated I would follow up with another
article on modifying AssemblyInfo.cs
via a batch file. Of
course, I forgot to write that post. Recently I was adding a
NuGet publish job to a TeamCity server which reminded me and
therefore finally here is the article.
Don't Jenkins and TeamCity already do this?
While both Jenkins and TeamCity include or have available
plugins for updating AssemblyInfo.cs
, they both suffer from
the problem in that they can write a version into the file but
they can't read from it first to derive a new value. However,
if you simply want to set a full version from within either CI
tool you can without having to bother with anything in this
post. As I wish to combine part of the existing version with a
CI supplied value, I need to look at alternatives.
Reading text from a file via a batch script
The "simplest" way of reading text from a file in a batch script is to use a Unix utility named sed (stream editor). Why did I quote "simplest"? You'll see!
You can download a version compiled for Windows from SourceForge. If you choose to download the portable Binaries distribution make sure you also pick up the Dependencies package as well as this contains required DLL's.
Using sed
Although sed can operate using pipes in the familiar manner for DOS/batch commands, it also has an inline editing mode which is more convenient for our purposes.
sed -i "expression" filename
When using the
/i
option, sed will leave a temporary file behind. This normally shouldn't be a concern if you're using a CI tool and have performed a fresh checkout or clean-up before building, but can become really annoying if you run it in your source tree. You can always including a command to delete the temporary files (for exampleDEL sed*.
), assuming you don't have real files with a similar pattern.
Defining an expression to update the revision
I may have lied when I said sed was simple! To modify our file,
we want to substitute part of the existing AssemblyFileVersion
or AssemblyInformationalVersion
values. To do this we'll use
sed's substitution command with a source pattern (a regular
expression) and then another pattern for the replacement. Due to
the way sed works, all 3 of these values need to be a single
string parameter. (You can use external files, but that is
beyond the scope of this example)
Getting the expressions working in sed can take a lot of trial and error. To easily test your expressions omit the
-i
switch from the command line - sed will then output the file to the console, allowing you to see the results of your expression without modifying the original file.
As a simple example, assume I wanted to replace the word
Assembly with Library. The expression s/Assembly/Library/
would handle this - s
is the command to use.
In the above example I'm using forward slash
/
to separate the arguments - the final separator is also required. As well as a slash you can also use the^
character, which may be easier to read.
> sed "s/Assembly/Library/" Properties\AssemblyInfo.cs
[assembly: LibraryVersion("1.0.0.0")]
[assembly: LibraryFileVersion("1.4.3.1")]
[assembly: LibraryInformationalVersion("1.4.0.1")]
Admittedly that's not a very useful example. So we'll now change it to match the attribute instead.
> sed "s/Assembly\(Informational\|File\)Version/Library/" Properties\AssemblyInfo.cs
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: Library("1.4.3.1")]
[assembly: Library("1.4.0.1")]
I'm deliberately not changing the assembly version given I strong name all assemblies and definitely do not want bindings to break from build number changes.
Notice how the regex capture group and logical or characters are escaped? If they aren't they won't function as a regular expression and no matches will be made.
Now we'll extend the pattern further to include the version information
> sed "s/\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.[0-9]\+\.\)[0-9]/Library/" Properties\AssemblyInfo.cs
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: Library")]
[assembly: Library")]
The lovely looking expression now captures the version as well.
Note that I couldn't get sed to accept a quote character in the
expression regardless of if I tried escaping it, but fortunately
it provides the \d
sequence for special characters - \d34
is
the quote. Although it's awkward to read, we capture
AttributeName("nnn.nnn.nnn.
in a separate capture group so we
can use it in our replacement expression.
Finally, lets actually replace the value with something useful.
Jenkins and TeamCity both set an environment variable named
BUILD_NUMBER
so you can simply combine that with the group
captured by the expression.
> set BUILD_NUMBER=0325
> sed "s/\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.[0-9]\+\.\)[0-9]\+/\1%BUILD_NUMBER%/" Properties\AssemblyInfo.cs
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.4.3.0325")]
[assembly: AssemblyInformationalVersion("1.4.0.0325")]
The \1
component of the replacement pattern states which
zero-based capture group to use, so in this example the second
group which excludes the final version part.
And there we have it, a sed expression to update our assembly information.
Updating the build number instead
The above expression works very well with versions that use four
components, e.g. 4.0.0.0
. However, if you follow Semantic
Versioning then you probably only use versions containing 3
components, in which case you'll want to update the 3rd part
(build) instead of the 4th (revision).
Although I still use 4 part versions for product versions and
for assemblies that aren't currently packaged, those that are
try to follow SemVer. For these assemblies, I set
AssemblyInformationalVersion
to be a 3 part version, with the
last part always zero. I leave AssemblyFileVersion
at 4 parts
with the third and fourth parts always zero.
The following expression can be used to update the third part of a version - it's identical to the 4 part version, except for dropping one set of captured digits.
s/\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.\)[0-9]\+/\1%BUILD_NUMBER%/
Putting it all together
Below is an example batch script that I've been using for just
over two years at time of writing to handle updating the version
of my components. I have two versions of this file, one for when
I want to update the third part of a version, and another for
updating the fourth. I also prefer using ^
as the sed
separator rather than /
.
The calls to cecho
can be replaced with just echo
(and also
remove the {x}
sequences); this is a utility for printing to
the console in colour.
@ECHO OFF
SET SRC=%1
IF "%~1"=="" GOTO :error
IF "%BUILD_NUMBER%"=="" GOTO :notset
IF NOT EXIST "%SRC%" GOTO :notfound
SED -i "s^\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.[0-9]\+\.\)[0-9]\+^\1%BUILD_NUMBER%^" "%SRC%"
IF %ERRORLEVEL% NEQ 0 GOTO :failed
GOTO :eof
:notset
CECHO {0e}WARNING: BUILD_NUMBER environment variable not set, performing no action{#}{\n}
EXIT /b 0
:failed
CECHO {0c}ERROR : Failed to process command{#}{\n}
EXIT /b 1
:notfound
CECHO {0c}ERROR : Source '%1' not found{#}{\n}
EXIT /b 1
:error
CECHO {0c}ERROR : Source not specified{#}{\n}
EXIT /b 1
Updating all AssemblyInfo.cs files
Although the above batch scripts are quite handy when updating a single file, what happens if you have multiple files to update? I recently started applying build numbers to the versions of our product suites and I had no intention of manually keeping track of which files to update.
Modern version of Windows include the forfiles.exe
utility
which "Selects a file (or set of files) and executes a command
on that file. This is helpful for batch jobs.". And helpful it
is for numerous scenarios - as well as file based matching it
can also search by date and so another task I use it for is
clearing old temporary files.
In the below example, I use the /S
flag to search
sub-directories, and the /M
parameter to specify I want to
match AssemblyInfo.cs
. And finally, I set the command to run
our updater batch file. This lets me update everything in a
single directory tree.
@FORFILES /S /M AssemblyInfo.cs /C "CMD /C CALL updateversioninfo.cmd @path"
In some of my products, I have a shared
AssemblyInfo.cs
, imaginatively namedSharedAssemblyInfo.cs
. To have this picked up by the above command, change the mask argument to be*AssemblyInfo.cs
.
What about PowerShell?
If you wanted a vanilla option which didn't require a third party program you could use PowerShell. As I'm slightly old school in regards to how I set up my build files, I still mainly use batch and so I haven't explored this option.
What about Visual Basic?
While I haven't programmed in Visual Basic .NET for over a
decade, everything in this article can be used just as easily
with VB projects - you'd just need to adjust the expression to
cover how you define attributes in VB.net, and of course change
.cs
to .vb
What about Visual Studio 2017 projects?
Some projects created with Visual Studio 2017 store the assembly
information directly in the XML project file. You could use the
above technique with these projects too, but it's not something
I've looked at as I still haven't fully switched to Visual
Studio 2017 yet, and the projects that I do use it with, I
deliberately choose to continue to have the meta data stored in
AssemblyInfo.cs
files.
Update History
- 2018-03-25 - First published
- 2020-11-22 - Updated formatting
Leave a Comment
While we appreciate comments from our users, please follow our posting guidelines. Have you tried the Cyotek Forums for support from Cyotek and the community?