code it

Martins Tech Blog

Zugriff auf WorkItems über das Objektmodell

Mit Hilfe des Objektmodells kann man recht simpel auf die WorkItems im Team Foundation Server zugreifen. Wie das geht, werde ich hier an einem kleinen Beispiel zeigen, in dem alle WorkItems eines TeamProjects aufgelistet werden sollen.

Zunächst werden Referenzen auf die Assemblies Microsoft.TeamFoundation.Client und Microsoft.TeamFoundation.WorkItemTracking.Client benötigt. Dann ist alles recht einfach. Mit den Klassen TfsTeamProjectCollection und WorkItemStore stellt uns das Objektmodell das Handwerkszeug bereit.

private WorkItemCollection GetAllWorkItems()
{
   string teamCollectionUrl = "http://tfs.mydomain:8080/tfs/defaultcollection";
   string projectName = "MyTestProject";

   var projectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(teamCollectionUrl));

   var workItemStore = new WorkItemStore(projectCollection);

   var wiq = string.Format("SELECT System.Title, System.Description FROM WorkItems WHERE System.TeamProject = '{0}'", projectName);

   var workItemCollection = workItemStore.Query(wiq);
   return workItemCollection;
}

Mit Hilfe der TfsTeamProjectCollectionFactory und der Url der Project-Collection lässt man sich die TfsTeamProjectCollection geben und kann dort dann eine WIQL-Query absetzen. WIQL steht für Work Item Query Language und ist eine Abfragesprache, die in einer SQL-ähnlichen Syntax das Abfragen von WorkItems erlaubt.

Eine weitere Möglichkeit sich mit der TfsProjectCollection zu verbinden ist der Konstruktor der TfsTeamProjectCollection. Vorteil dieser Methode ist, dass man hier zudem auch die Möglichkeit hat, abweichende Credentials zu hinterlegen.

private WorkItemCollection GetAllWorkItems()
{
   string teamCollectionUrl = "http://tfs.mydomain:8080/tfs/defaultcollection";
   string projectName = "MyTestProject";
   string username = "user";
   string password = "password";
   string domain =  "mydomain";

   var credentials = new NetworkCredential(username, password, domain);
   var projectCollection = new TfsTeamProjectCollection(new Uri(teamCollectionUrl), credentials);

   var workItemStore = new WorkItemStore(projectCollection);

   var wiq = string.Format("SELECT System.Title, System.Description FROM WorkItems WHERE System.TeamProject = '{0}'", projectName);

   var workItemCollection = workItemStore.Query(wiq);
   return workItemCollection;
}

Das ist beispielsweise dann sinnvoll, wenn man sich in einer anderen Domäne befindet als der Team Foundation Server, den man abfragen möchte.

Build Messages in CodeActivities in Teambuild 2010

Wer in TFS 2008 eigene Build-Tasks geschrieben hat und dabei von der abstrakten Basisklasse Task abgeleitet hat, wird wissen,dass es recht einfach war, Meldungen ins Log zu schreiben. Die Klasse stellte ein Objekt Log zur Verfügung, das über die passenden Methoden verfügte.

In TFS 2010 ist die Workflow-Engine Grundlage des Teambuilds. Dieser ist auch weiterhin erweiterbar - nur wird nun nicht mehr von der Klasse Task, sondern von der Klasse CodeActivity geerbt. Nur leider bietet diese Klasse eben kein solches Log-Objekt mehr.

Abhilfe schafft hier der Microsoft.TeamFoundation.Build.Workflow.Activities-Namespace. Ist dieser über eine using-Direktive eingebunden, so stehen auf dem context-Objekt drei Extension-Methods zur Verfügung, die das Logging übernehmen können.

using System.Activities;
using Microsoft.TeamFoundation.Build.Workflow.Activities;

namespace CustomActivityLibrary
{
    public sealed class CodeActivity1 : CodeActivity
    {
        protected override void Execute(CodeActivityContext context)
        {
            context.TrackBuildMessage("this is a message");
            context.TrackBuildWarning("this is a warning");
            context.TrackBuildError("this is an error");
        }
    }
}

Erstellung einer StartRemoteProcess-Build-Activity

Die neuen Build-Workflows für den Team Foundation Server sind ein cooles Feature. Und wenn man Standard-Projekttypen verwendet funktioniert das auch alles super, da in den meisten Fällen Copy&Paste-Deployment ausreichend ist.

In meinem Anwendungsfall war es notwendig, am Ende des Builds eine Activity auszuführen, die auf einem Remote-Server (z.B. Integrationssystem) einen Prozess startet. Eine solche Activity habe ich im Standard nicht gefunden. Daher hab ich selbst eine geschrieben.

Eine eigene Activity zu erstellen ist gar nicht so schwer. Einen guten Einstieg geben die Posts von Jim Lamb und Ewald Hofman. Bei der Entwicklung der Activity hat sich die Projektaufteilung von Ewald als sehr praktisch erwiesen. Nur so war es mir möglich, die selbst erstellte Activity dem Workflow dann auch hinzuzufügen.

Um einen Prozess zu starten, sind folgende Informationen notwendig:

  • Name oder IP-Adresse des Remote-Servers
  • auszuführendes Command
  • Credentials (abweichend vom TFS-Service-Account)

Daher erhält die Activity in Summe fünf Input-Argumente: Command, RemoteMachine, Domain, UserName und Password. Damit sieht der Rumpf der Actitvity wie folgt aus:

 

[BuildActivity(HostEnvironmentOption.All)]
public sealed class StartProcessOnRemoteMachine : CodeActivity
{
    [RequiredArgument]
    public InArgument<string> Command
    {
        get;
        set;
    }

    [RequiredArgument]
    public InArgument<string> RemoteMachine
    {
        get;
        set;
    }

    [RequiredArgument]
    public InArgument<string> Username
    {
        get;
        set;
    }

    [RequiredArgument]
    public InArgument<string> Password
    {
        get;
        set;
    }

    [RequiredArgument]
    public InArgument<string> Domain
    {
        get;
        set;
    }
}

Bei der Implementierung sollte man sich überlegen, so sensible Daten wie Credentials ggf. anders abzubilden als per Input-Parameter - für dieses Beispiel soll diese Lösung aber ausreichend sein, damit es nicht zu komplex wird. Die eigentliche Arbeit übernimmt dann die im folgenden dargestellte Methode ExecuteProcessOnRemoteMachine, die per WMI einen Prozess auf einem anderen Rechner startet. Dazu ist es notwendig, die Assembly System.Management zu referenzieren.

private static void ExecuteProcessOnRemoteMachine(string remoteMachine, string username, string password, string domain, string commandLine)
{
    ConnectionOptions connectionOptions = new ConnectionOptions();
    connectionOptions.Authority = "ntlmdomain:" + domain;
    connectionOptions.Username = username;
    connectionOptions.Password = password;
    connectionOptions.Authentication = AuthenticationLevel.Default;
    connectionOptions.Impersonation = ImpersonationLevel.Impersonate;
    connectionOptions.EnablePrivileges = true;

    ManagementScope managementScope = new ManagementScope(string.Format(@"\\{0}\ROOT\CIMV2", remoteMachine), connectionOptions);
    managementScope.Connect();

    ManagementPath managementPath = new ManagementPath("Win32_Process");
    ManagementClass processClass = new ManagementClass(managementScope, new ManagementPath("Win32_Process"), new ObjectGetOptions());
    ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
    inParams["CommandLine"] = commandLine;

    ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null);
}

Der Rest ist trivial: In der Methode Execute werden die Input-Parameter entgegengenommen und an die Methode ExecuteProcessOnRemoteMachine übergeben.

protected override void Execute(CodeActivityContext context)
{
    string startProcessCommand = context.GetValue(this.Command);
    string remoteMachine = context.GetValue(this.RemoteMachine);
    string username = context.GetValue(this.Username);
    string password = context.GetValue(this.Password);
    string domain = context.GetValue(this.Domain);


    ExecuteProcessOnRemoteMachine(remoteMachine, username, password, domain, startProcessCommand);
}

Die Activity wird nun in den Workflow eingebunden und die notwendigen Daten bereitgestellt.

Am Ende des Builds werden nun die Dateien aus dem DropFolder genommen und mittels der Standard-Activity CopyDirectory auf den Integrationsserver kopiert. Die neu erstellte Activity führt dann die Installation durch.

Wo sind schon wieder meine Code Coverage-Ergebnisse?

Mal wieder gab es ein Problem mit den Code-Coverage-Ergebnissen - dieses Mal allerdings nicht bin Desktop-Build wie in meinem letzten Post als die Code-Coverage-Ergebnisse fehlten. Im aktuellen Fall waren sie im aktuellen Projekt beim Teambuild nicht vorhanden. Das war etwas ernüchternd, denn genau diese Kennzahlen sollten Teil des Projektstatusberichtes sein.

In meinem Fall steuert die tfsbuild.proj den Serverbuild. Bereits enthalten waren die Tags RunTest und RunCodeAnalysis. Mit diesen Einstellungen war ich davon ausgegangen, dass auch die Tests durchgeführt und Code-Coverage berechnet werden kann. Aber weit gefehlt: Zwar wurden Tests durchgeführt, aber als Ergebnis der Code-Coverage kam die Meldung "No coverage result".

Nach einigem Suchen war das Problem gefunden: Damit die Ergebnisse auch erzeugt werden, benötigt der Build die Angabe einer *.testrunconfig-Datei. In dieser Datei stehen die Informationen von welchen Dateien Code-Coverage-Ergebnisse erzeugt werden sollen. Eine solche Datei wird in der Regel auch schon automatisch angelegt, wenn man ein Testprojekt erstellt und genau eine Datei mit diesem Schema muss auch in der tfsbuild.prj-Datei unter dem Tag RunConfigFile angegeben werden. Ist dieser Tag in der prj-Datei nicht vorhanden, so muss er komplett neu hinzugefügt werden.

<!--  TESTING
 Set this flag to enable/disable running tests as a post-compilation build step.
-->
<RunTest>true</RunTest>

<!--  CODE ANALYSIS
 Set this property to enable/disable running code analysis. Valid values for this property are 
 Default, Always and Never.
     Default - Perform code analysis as per the individual project settings
     Always  - Always perform code analysis irrespective of project settings
     Never   - Never perform code analysis irrespective of project settings
 -->
<RunCodeAnalysis>Default</RunCodeAnalysis>

<RunConfigFile>$(SolutionRoot)\MyProject\testrun.testrunconfig</RunConfigFile>

Mit dem so konfigurierten Build werden nun auch Code-Coverage-Ergebnisse beim Teambuild erzeugt.

"Checked out by somesone else or in another place"... aber wer und wo?

Bei der Arbeit mit dem TFS ist mir aufgefallen, dass an einigen Dateien der Status "Checked out by somesone else or in another place" gesetzt ist. 

Zum einen wird man aus dieser Nachricht nicht viel schlauer, zum anderen erscheint sie manchmal auch dann, wenn man allein an einem Projekt arbeitet.

Der erste der genannten Fälle ist ein Bug im TFS - Dateien, für die ein Shelveset existiert werden fälschlicherweise mit diesem Status angezeigt.

Ob eine Datei wirklich von jemand anders angecheckt ist, kann man über das Kommandozeilentool tf.exe erfahren. Dazu öffnet man den Visual Studio 2008 Command Prompt. Der Befehl "tf status $/project /user:* /recursive" zeigt in einer Übersicht mit den Spalten File name, Change, User und Local path alle augechecken Dateien aller Nutzer des angegebenen Projektes an:

 

Historie gelöschter Objekte im TFS ermitteln

Löscht man Objekte, die unter Source Control stehen, so hat man auf den ersten Blick keine Möglichkeit, diese wiederherzustellen oder sich die Historie anzuschauen, um so herauszufinden wer das Objekt mit welchem Changeset gelöscht hat.

Möchte man die gelöschten Objekte sehen, so muss man dies zunächst aktivieren. Die Option dazu findet man unter Tools -> Options -> Source Control -> Visual Studio Team Foundation Server. Dort aktiviert man den Haken "Show deleted items in Source Control Explorer".

 

Nun sieht man im Source Control Explorer auch gelöschte Dateien.

TFS-Auswertung mit Hilfe von Excel erstellen über CheckIn-Policy-Overrides

Mit Hilfe von CheckIn-Policies kann sichergestellt werden, dass bestimmte Regeln beim CheckIn eingehalten werden – die bekanntesten Regeln sind sicher Work-Item-Zuordnung, erfolgreicher Build oder Kommentierung. Allerdings besteht hier auch die Möglichkeit, einen Policy Override durchzuführen – heißt diese Policy zu ignorieren.

Für den Projektleiter ist es nun interessant, wer solche Overrides durchgeführt hat – und warum. Denn sicher gibt es immer mal wieder Notwendigkeiten dafür, manchmal siegt aber auch die Faulheit und das geht meist zu Lasten der Qualität.

Christian Binder hat dazu einen schönen Blog-Eintrag geschrieben, der beschreibt, wie man mit Hilfe von Excel-Pivot-Tabellen auf den Cube im Data Warehouse zugreift und so eine Auswertung über Policy-Overrides bekommen kann.

Upgrade mit dem Visual Studio Team System Information Day

Heute konnte ich mein Wissen zum Thema Visual Studio Team System und Team Foundation Server etwas auffrischen, denn ich war zu Besuch beim VSTS Information Day in Berlin.

Frank Maar (Technologieberater) und  René Rösel (Sales & Business Operations Manager) stellten VSTS in der aktuellen Version vor, gaben einen Ausblick auf die Features von VSTS 2010 und klärten viele Fragen in Bezug auf Funktionsumfang, Integration in bestehende IT-Landschaften und Lizenzierungsmodelle.

Die Veranstaltung war eher workshopmäßig aufgebaut; so konnten wir Teilnehmer die beiden mit oft kritischen Fragen zu den Möglichkeiten befragen die TFS bietet und sich die Lösung (oder den Ansatz) direkt am System zeigen lassen. Dabei kam zum Vorschein, was Team System bietet und was nicht. Aber auch untereinander wurde konnten einige Teilnehmer ihre Erfahrungen austauschen und so den ein oder anderen Tipp an den Mann oder die Frau bringen.

Alles in allem war es für mich interessant und ich hab ein paar wissenswerte Informationen mitgenommen – auch wenn ich aufgrund des Umstandes dass ich TFS schon einsetze die meisten Dinge schon in anderen Präsentationen gehört oder selbst herausgefunden habe.

Go Offline?

Eine sehr praktische Möglichkeit bei der Arbeit mit Visual Studio in Verbindung mit dem Team Foundation Server ist es, dass man auch Offline arbeiten kann und dann später im Online-Modus die Änderungen zusammengesucht werden und man einchecken kann.

Ist keine Verbindung zum TFS möglich, wenn ein entsprechendes Projekt geöffnet wird, geht Visual Studio automatisch in den Offline-Modus und man kann mittels des “Go Online”-Buttons in den Online-Modus wechseln.

Leider funktioniert der umgekehrte Weg nicht. Ist man Online und möchte in den Offline-Modus wechseln, sucht man den “Go Offline”-Button vergeblich.

Doch wo ein Wille ist, ist auch ein Weg – der Zauberbefehl lautet tfpt tweakui:

  • Visual Studio schließen
  • die aktuellsten Team Foundation Power Tools herunterladen und installieren
  • Visual Studio Command Prompt öffnen
  • tfpt tweakui eingeben
  • den Server markieren und auf Edit klicken
  • Server ist offline anhaken
  • Visual Studio neu öffnen

Visual Studio öffnet das Projekt nun im Offline-Modus.