Cyotek Development Bloghttps://devblog.cyotek.com/tag/teamcity/atom.xml2018-03-25T16:13:42ZUpdating AssemblyInfo.cs version information via batch fileurn:uuid:a4c9c694-22d3-4baf-b133-35f10ff97f8a2018-03-25T16:13:42Z2018-03-25T16:13:42Z<p>Over a year ago I wrote how to <a href="/post/using-a-jenkins-pipeline-to-build-and-publish-nuget-packages">build and publish NuGet packages
via Jenkins</a> in which I stated I would follow up with another
article on modifying <code>AssemblyInfo.cs</code> 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.</p>
<h2 id="dont-jenkins-and-teamcity-already-do-this">Don't Jenkins and TeamCity already do this?</h2>
<p>While both Jenkins and TeamCity include or have available
plugins for updating <code>AssemblyInfo.cs</code>, they both suffer from
the problem in that they can <em>write</em> a version into the file but
they can't <em>read</em> 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.</p>
<h2 id="reading-text-from-a-file-via-a-batch-script">Reading text from a file via a batch script</h2>
<p>The &quot;simplest&quot; 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 &quot;simplest&quot;? You'll see!</p>
<p>You can download a version compiled for Windows from
<a href="http://gnuwin32.sourceforge.net/packages/sed.htm" rel="external nofollow noopener">SourceForge</a>. 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.</p>
<h2 id="using-sed">Using sed</h2>
<p>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.</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
sed -i <span class="string">&quot;expression&quot;</span> filename
</pre>
</figure>
<blockquote>
<p>When using the <code>/i</code> 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 <em>really</em> annoying if you run it in
your source tree. You can always including a command to delete
the temporary files (for example <code>DEL sed*.</code>), assuming you
don't have real files with a similar pattern.</p>
</blockquote>
<h2 id="defining-an-expression-to-update-the-revision">Defining an expression to update the revision</h2>
<p>I may have lied when I said sed was simple! To modify our file,
we want to substitute part of the existing <code>AssemblyFileVersion</code>
or <code>AssemblyInformationalVersion</code> values. To do this we'll use
sed's <em>substitution</em> 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)</p>
<blockquote>
<p>Getting the expressions working in sed can take a lot of trial
and error. To easily test your expressions omit the <code>-i</code>
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.</p>
</blockquote>
<p>As a simple example, assume I wanted to replace the word
<em>Assembly</em> with <em>Library</em>. The expression <code>s/Assembly/Library/</code>
would handle this - <code>s</code> is the command to use.</p>
<blockquote>
<p>In the above example I'm using forward slash <code>/</code> to separate
the arguments - the final separator is also required. As well
as a slash you can also use the <code>^</code> character, which may be
easier to read.</p>
</blockquote>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
&gt; sed <span class="string">&quot;s/Assembly/Library/&quot;</span> Properties\AssemblyInfo.cs
[assembly: LibraryVersion(&quot;1.0.0.0&quot;)]
[assembly: LibraryFileVersion(&quot;1.4.3.1&quot;)]
[assembly: LibraryInformationalVersion(&quot;1.4.0.1&quot;)]
</pre>
</figure>
<p>Admittedly that's not a very useful example. So we'll now change
it to match the attribute instead.</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
&gt; sed <span class="string">&quot;s/Assembly\(Informational\|File\)Version/Library/&quot;</span> Properties\AssemblyInfo.cs
[assembly: AssemblyVersion(&quot;1.0.0.0&quot;)]
[assembly: Library(&quot;1.4.3.1&quot;)]
[assembly: Library(&quot;1.4.0.1&quot;)]
</pre>
</figure>
<blockquote>
<p>I'm deliberately <strong>not</strong> changing the assembly version given I
strong name all assemblies and definitely do not want bindings
to break from build number changes.</p>
</blockquote>
<p>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.</p>
<p>Now we'll extend the pattern further to include the version
information</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
&gt; sed <span class="string">&quot;s/\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.[0-9]\+\.\)[0-9]/Library/&quot;</span> Properties\AssemblyInfo.cs
[assembly: AssemblyVersion(&quot;1.0.0.0&quot;)]
[assembly: Library&quot;)]
[assembly: Library&quot;)]
</pre>
</figure>
<p>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 <code>\d</code> sequence for special characters - <code>\d34</code> is
the quote. Although it's awkward to read, we capture
<code>AttributeName(&quot;nnn.nnn.nnn.</code> in a separate capture group so we
can use it in our replacement expression.</p>
<p>Finally, lets actually replace the value with something useful.
Jenkins and TeamCity both set an environment variable named
<code>BUILD_NUMBER</code> so you can simply combine that with the group
captured by the expression.</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
&gt; <span class="keyword">set</span> BUILD_NUMBER=0325

&gt; sed <span class="string">&quot;s/\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.[0-9]\+\.\)[0-9]\+/\1%BUILD_NUMBER%/&quot;</span> Properties\AssemblyInfo.cs
[assembly: AssemblyVersion(&quot;1.0.0.0&quot;)]
[assembly: AssemblyFileVersion(&quot;1.4.3.0325&quot;)]
[assembly: AssemblyInformationalVersion(&quot;1.4.0.0325&quot;)]
</pre>
</figure>
<p>The <code>\1</code> 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.</p>
<p>And there we have it, a sed expression to update our assembly
information.</p>
<h2 id="updating-the-build-number-instead">Updating the build number instead</h2>
<p>The above expression works very well with versions that use four
components, e.g. <code>4.0.0.0</code>. However, if you follow <a href="https://semver.org/" rel="external nofollow noopener">Semantic
Versioning</a> 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).</p>
<p>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
<code>AssemblyInformationalVersion</code> to be a 3 part version, with the
last part always zero. I leave <code>AssemblyFileVersion</code> at 4 parts
with the third and fourth parts always zero.</p>
<p>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.</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
s/\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.\)[0-9]\+/\1<span class="string">%BUILD_NUMBER%</span>/
</pre>
</figure>
<h2 id="putting-it-all-together">Putting it all together</h2>
<p>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 <code>^</code> as the sed
separator rather than <code>/</code>.</p>
<p>The calls to <code>cecho</code> can be replaced with just <code>echo</code> (and also
remove the <code>{x}</code> sequences); this is a utility for printing to
the <a href="/post/colorecho-adding-colour-to-echoed-batch-text">console in colour</a>.</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
@ECHO <span class="keyword">OFF</span>

<span class="keyword">SET</span> SRC=<span class="string">%1

IF &quot;%</span>~1<span class="string">&quot;==&quot;</span><span class="string">&quot; GOTO :error
IF &quot;</span><span class="string">%BUILD_NUMBER%</span><span class="string">&quot;==&quot;</span><span class="string">&quot; GOTO :notset
IF NOT EXIST &quot;</span><span class="string">%SRC%</span><span class="string">&quot; GOTO :notfound

SED -i &quot;</span>s^\(Assembly\(Informational\|File\)Version(\d34[0-9]\+\.[0-9]\+\.[0-9]\+\.\)[0-9]\+^\1<span class="string">%BUILD_NUMBER%</span>^<span class="string">&quot; &quot;</span><span class="string">%SRC%</span>&quot;

<span class="keyword">IF</span> <span class="string">%ERRORLEVEL%</span> NEQ 0 <span class="keyword">GOTO</span> :failed

<span class="keyword">GOTO</span> :eof

:notset
CECHO {0e}WARNING: BUILD_NUMBER environment variable <span class="keyword">not</span> <span class="keyword">set</span>, performing no action{#}{\n}
<span class="keyword">EXIT</span> /b 0

:failed
CECHO {0c}ERROR : Failed to process command{#}{\n}
<span class="keyword">EXIT</span> /b 1

:notfound
CECHO {0c}ERROR : Source <span class="string">&#39;%1&#39;</span> <span class="keyword">not</span> found{#}{\n}
<span class="keyword">EXIT</span> /b 1

:error
CECHO {0c}ERROR : Source <span class="keyword">not</span> specified{#}{\n}
<span class="keyword">EXIT</span> /b 1
</pre>
</figure>
<h2 id="updating-all-assemblyinfo.cs-files">Updating all AssemblyInfo.cs files</h2>
<p>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.</p>
<p>Modern version of Windows include the <code>forfiles.exe</code> utility
which &quot;Selects a file (or set of files) and executes a command
on that file. This is helpful for batch jobs.&quot;. 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.</p>
<p>In the below example, I use the <code>/S</code> flag to search
sub-directories, and the <code>/M</code> parameter to specify I want to
match <code>AssemblyInfo.cs</code>. And finally, I set the command to run
our updater batch file. This lets me update everything in a
single directory tree.</p>
<figure class="lang-bat highlight"><figcaption><span>bat</span></figcaption><pre class="code">
@FORFILES /S /M AssemblyInfo.cs /C <span class="string">&quot;CMD /C CALL updateversioninfo.cmd @path&quot;</span>
</pre>
</figure>
<blockquote>
<p>In some of my products, I have a shared <code>AssemblyInfo.cs</code>,
imaginatively named <code>SharedAssemblyInfo.cs</code>. To have this
picked up by the above command, change the mask argument to be
<code>*AssemblyInfo.cs</code>.</p>
</blockquote>
<h2 id="what-about-powershell">What about PowerShell?</h2>
<p>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.</p>
<h2 id="what-about-visual-basic">What about Visual Basic?</h2>
<p>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
<code>.cs</code> to <code>.vb</code></p>
<h2 id="what-about-visual-studio-2017-projects">What about Visual Studio 2017 projects?</h2>
<p>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
<code>AssemblyInfo.cs</code> files.</p>
<h2 id="update-history">Update History</h2>
<ul>
<li>2018-03-25 - First published</li>
<li>2020-11-22 - Updated formatting</li>
</ul>

<p><small>
All content <a href="https://devblog.cyotek.com/copyright-and-trademarks">Copyright (c) by Cyotek Ltd</a> or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.<br />Original URL of this content is https://devblog.cyotek.com/post/updating-assemblyinfo-cs-version-information-via-batch-file .
</small></p>Richard Mosshttps://www.cyotek.com/richard.moss@cyotek.com