English Version goes here Thx to Christian Jacob :-)

Wenn man mit Team Foundation Server täglich Builds erstellt, möchte man nicht jeden Tag die assemblyinfo.cs Files in den Projekten anfassen, um die Build Nummer zu inkrementieren oder?

Das Format der Assembly Version ist:
MajorVersion.MinorVersion.BuildNumber.Revision

Ziel ist, dass wir durch „right click“ > Properties auf das Assembly die Information zu erhalten, mit welchem Build das Assembly gebaut wurde, so dass wir jederzeit in Version Control den Source via BuildLabel finden können.

Was ist notwendig?

Erstemal wird bei jedem TFSBuild, eine BuildNumber von TFS vergeben, die im Default
sich aus dem BuildNamen + Datum + Revsion zusammensetzt.
z. B: ConsoleBuild_20070201.8

Dies ist dann auch das Label, welches automatisch in Version Control für diesen Build erzeugt wird, so dass wir jederzeit in Source Control auf den Source zugreifen können, der bei einem bestimmten TFSBuild zugrunde lag.

Das assemblyinfo.cs File, welches die Information enthält, was letztendlich in den AssemblyProperties verfügbar ist, wird hingegen nicht angefasst.
Daher gibt z.B keine automatische Erhöhung der BuildNummer in der Assembly Version.

Abschnitt des AssemblyInfo.cs files, welches die AssemblyVersion bestimmt:

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Wir müssen den Build Prozess erweitern, so dass das assemblyinfo.cs file automatisch
angefasst wird und die AssemblyVersion inkrementiert wird. Hierzu verwendet man
die MSBuild Erweiterung AssemblyInfoTask.

Wie den AssemblyInfoTask einbinden?

Nachdem der AssemblyInfo Task via Installer im GAC installiert ist, muss man den Task im TFSBuild.proj File via import Project einbinden: 

 

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v8.0\TeamBuild\Microsoft.TeamFoundation.Build.targets" />

<Import Project="$(MSBuildExtensionsPath)\Microsoft\AssemblyInfoTask\Microsoft.VersionNumber.Targets"/>

Zusätzlich binde ich ein Task ein, der es ermöglicht die Revisions Number, welche von TFS automatisch erzeugt wird zu extrahieren und in den AssemblyInfo Task zu übermitteln, so dass im Fall von mehreren Builds an einem Tag die Assembly Revision inkrementiert wird.

z. B:  ConsoleBuild_20070201.

Der ExtractRevision Task ist nichts anderes als ein custom BuildTask, später mehr dazu.
Die ExtractRevision.dll checke ich unter meinem TeamProject >TeamBuildTypes ein.

Im TFSBuild.proj File registriere ich den Task wie folgt:

<UsingTask TaskName="ExtractRevision.ExtractRevisionTask" AssemblyFile="ExtractRevision.dll"/>

In einer Property Group setze ich die AssemblyInfo Properties.
BuildNumberType ist DateString, also das aktuelle Datum im Format MMdd.
An dieser Stelle inkrementiere ich bei Bedarf auch die Major- oder MinorVersion.

Als Beispiel schreibe ich auch die TFS BuildNumber = Version Label, welche als Variable
$(BuildNumber) verfügbar ist in das Comment Property der Assembly.

Die AssemblyRevision überschreibe ich später.

<PropertyGroup>
<!-- Assembly version properties. Add others here -->
<AssemblyMajorVersion>1</AssemblyMajorVersion> <AssemblyMinorVersion>0</AssemblyMinorVersion> <AssemblyBuildNumber>0</AssemblyBuildNumber> <AssemblyRevision>0</AssemblyRevision> <AssemblyBuildNumberType>DateString</AssemblyBuildNumberType> <AssemblyBuildNumberFormat>MMdd</AssemblyBuildNumberFormat> <AssemblyRevisionType>NoIncrement</AssemblyRevisionType> <AssemblyRevisionFormat>0</AssemblyRevisionFormat> <AssemblyFileBuildNumberType>DateString</AssemblyFileBuildNumberType> <AssemblyFileBuildNumberFormat>MMdd</AssemblyFileBuildNumberFormat> <AssemblyFileRevisionType>NoIncrement</AssemblyFileRevisionType>

<!-- Dump the TFS BuildNumber to the Assembly Comment Prop --> <AssemblyDescription>$(BuildNumber)</AssemblyDescription>

<!-- TF.exe -->
<TF>&quot;$(TeamBuildRefPath)\..\tf.exe&quot;</TF>
<!-- AssemblyInfo file -->
<AssemblyInfoSpec>AssemblyInfo.cs</AssemblyInfoSpec>
</PropertyGroup>

Nun verwende ich das Target CheckSettingsforEndToEndIteration, um via
ExtractRevison Task die AssemblyRevision zu setzen. Hierbei übergebe
ich die $(BuildNumber) dem Task, welcher die Revision extrahiert
und mit der Revision $(BuildRevision) das AssemblyRevision Property überschreibe.

<!-- Use the ExtractRevionTask to get the RevionNumber out of the BuildNumber.
Update the AssemblyInfoTask Properties with the result BuildRevision
. -->

<Target Name="CheckSettingsForEndToEndIteration">
<ExtractRevisionTask BuildNumber="$(BuildNumber)">
<Output TaskParameter="BuildRevision" PropertyName="BuildRevision" /> </ExtractRevisionTask>

<CreateProperty Value="$(BuildRevision)">
<Output TaskParameter="Value" PropertyName="AssemblyRevision"/>
</CreateProperty>

<CreateProperty Value="$(BuildRevision)">
<Output TaskParameter="Value" PropertyName="AssemblyFileRevision"/> </CreateProperty>
</Target>

Jetzt verwende ich das AfterGet Target, um mit TF.exe die assemblyinfo.cs Files auszuchecken:

<!-- Use TF to check-out/In the assemblyinfo files to persist updated Version Info --> <Target Name="AfterGet" Condition="'$(IsDesktopBuild)'!='true'">

<!-- Set the AssemblyInfoFiles items dynamically -->
<CreateItem Include="$(SolutionRoot)\**\$(AssemblyInfoSpec)">
<Output ItemName="AssemblyInfoFiles" TaskParameter="Include" />
</CreateItem>

<Exec WorkingDirectory="$(SolutionRoot)"
Command="$(TF) checkout /recursive $(AssemblyInfoSpec)"/>
</Target>

Mit dem AfterCompile Target checke ich die inkrementierten assemblyinfo.cs Files via TF.exe wieder ein:

<Target Name="AfterCompile" Condition="'$(IsDesktopBuild)'!='true'">
<Exec WorkingDirectory="$(SolutionRoot)"

Command="$(TF) checkin /comment:&quot;Auto-Build: Version Update&quot; /noprompt /override:&quot;Auto-Build: Version Update&quot; /recursive $(AssemblyInfoSpec)"/>
</Target>

Im Fehlerfall nutze ich das BeforeOnBuildBreak Target und verwerfen unsere Pending
Changes via TF.exe Undo:

<!-- In case of Build failure, the AfterCompile target is not executed. Undo the changes --> <Target Name="BeforeOnBuildBreak" Condition="'$(IsDesktopBuild)'!='true'">
      <Exec WorkingDirectory="$(SolutionRoot)"
      Command="$(TF) undo /noprompt /recursive $(AssemblyInfoSpec)"/>
</Target>


Wie den ExtractRevision Task erstellen?

Ein BuildTask ist ein Lib Project mit entsprechenden Referenzen.
Hier die Class, welche von Task erbt, wobei die Logic in der Execute Method ist. 
Noch zu erwähnen wäre das BuildRevision Property, welches als [Output]
deklariert ist.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

namespace ExtractRevision
{
public class ExtractRevisionTask : Task
{
public override bool Execute()
{
     
int indexOfDot = buildNumber.LastIndexOf(".");
      if (indexOfDot != -1 & indexOfDot != buildNumber.Length - 1)
      {
         buildRevision = buildNumber.Substring(indexOfDot + 1, buildNumber.Length -
         (indexOfDot + 1));
      }
      else
      {
         //there is no char following the dot or we can't find a dot
         buildRevision = "0"
}
return true;
}

private string buildRevision;
private string buildNumber;

public string BuildNumber
{
      set { buildNumber = value; }
}

[Output]
public string BuildRevision
{
     get { return buildRevision; }
}
}
}

Am Ende des Tages erhält man dann als AssemblyVersion z.B: 1.0.0212.1
für den 12.2. Ein weiterer Build am 12.2 würde folgende Version erzeugen
1.0.0212.2

Viel Spass,
Chris