Doug Neumann's TLN301 presentation, VSTS: Behind the Scenes of Visual Studio 2005 Team Foundation Server (slides), featured a demonstration of how to use the server's check-in event notification to kick off a build for a continuous integration build system using Team Build.  A number of people asked for it, so we've decided to post it here.

Doug's demonstration used the July CTP, but since beta 3 will hopefully be released this week, the code has been modified to run on beta 3.  You'll need to create a web service and put the following code into it.  Here are the assemblies you'll need to reference.

Microsoft.TeamFoundation.Build.Client.dll
Microsoft.TeamFoundation.Build.Common.dll
Microsoft.TeamFoundation.Client.dll
Microsoft.TeamFoundation.VersionControl.Client.dll
Microsoft.TeamFoundation.VersionControl.Common.dll
Microsoft.TeamFoundation.WorkItemTracking.Client.dll

The code is intentionally simplified to meet the needs of a demo (e.g., a real continuous integration system would need more intelligence in handling check-in events that come in while the current build is running, etc.), but it's a good example of how to hook into the Team Foundation server's events and build useful extensions.

Once you've built and deployed your web service using VS 2005, you'll need to add a subscription for your web service.  The check-in event is sent to your continuous integration web service via SOAP.  The following command, with changes as necessary for the name of your machine (here, I use localhost), must be run on the application tier to add an event subscription for your service.

bissubscribe /eventType CheckinEvent /userId mydomain\myusername /address http://localhost:8080/ContinuousBuild/Service.asmx /deliveryType Soap /domain localhost

Now when you check code into your server, your continuous integration service will kick off a build.

The Team Build team plans to post a more elaborate continuous integration example.

[UPDATE 2/20/06]  I updated the version numbers in the SoapDocumentMethod attribute to work with RC and RTM releases.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml;
using System.Xml.Serialization;
using Microsoft.Win32;
using Microsoft.TeamFoundation.Build.Common;
using Proxy = Microsoft.TeamFoundation.Build.Proxy;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Common;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
    public Service ()
    {
    }

    [SoapDocumentMethod("http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", 
                        RequestNamespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
    [WebMethod]
    public void Notify(string eventXml)     // Do not change the name of this argument.
    {
        ThreadPool.QueueUserWorkItem(CallBuild, eventXml);
    }

    public void CallBuild(object state)
    {
        string eventXml = (string)state;

        // De-serializing the event
        XmlDocument Xmldoc = new XmlDocument();
        Xmldoc.LoadXml(eventXml);

        string teamProject = Xmldoc.DocumentElement["TeamProject"].InnerText;
        string owner = Xmldoc.DocumentElement["Owner"].InnerText;

        // NOTE: hard-code info for demo
        string teamFoundationServer = "http://localhost:8080";
        string buildType = "Continuous Integration Build";
        string buildMachine = "localhost";
        string buildDirectoryPath = "c:\\builds";

        Proxy.BuildController controller = Proxy.BuildProxyUtilities.GetBuildControllerProxy(teamFoundationServer);
        Proxy.BuildStore store = Proxy.BuildProxyUtilities.GetBuildStoreProxy(teamFoundationServer);
        Proxy.BuildParameters buildParams = new Proxy.BuildParameters();
        buildParams.TeamFoundationServer = teamFoundationServer;
        buildParams.TeamProject = teamProject;
        buildParams.BuildType = buildType;
        buildParams.BuildDirectory = buildDirectoryPath;
        buildParams.BuildMachine = buildMachine;

        string buildUri = controller.StartBuild(buildParams);

        // wait until the build completes
        BuildConstants.BuildStatusIconID status;
        bool buildComplete = false;
        do
        {
            Proxy.BuildData bd = store.GetBuildDetails(buildUri);
            status = (BuildConstants.BuildStatusIconID)bd.BuildStatusId;
            buildComplete = (status == BuildConstants.BuildStatusIconID.BuildSucceeded ||
                status == BuildConstants.BuildStatusIconID.BuildFailed ||
                status == BuildConstants.BuildStatusIconID.BuildStopped);
        } while (!buildComplete);

        if (status == BuildConstants.BuildStatusIconID.BuildFailed)
        {
            // create a workitem for the developer who checked in
            CreateWorkItem(teamFoundationServer, teamProject, owner);
        }
    }

    public void CreateWorkItem(string server, string projectName, string owner)
    {
        TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(server);
        WorkItemStore store = (WorkItemStore) tfs.GetService(typeof(WorkItemStore));

        WorkItemTypeCollection workItemTypes = store.Projects[projectName].WorkItemTypes;

        // Enter the work item as a bug
        WorkItemType wit = workItemTypes["bug"];
        WorkItem workItem = new WorkItem(wit);

        workItem.Title = "The changes submitted have caused a build break - please investigate";
        string[] ownerSplit = owner.Split('\\');
        owner = ownerSplit[ownerSplit.GetLength(0) - 1];
        workItem.Fields["System.AssignedTo"].Value = owner;
        workItem.Fields["Microsoft.VSTS.Common.Priority"].Value = 1;
        workItem.Save();
    }
}