Click here to Skip to main content
15,881,882 members
Articles / Programming Languages / C#

Auto-Deployment Using Microsoft Web Deploy on Build Quality Change Event of TFS 2010

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
14 Feb 2011CPOL5 min read 44K   402   17   4
This article explains the auto-deployment of build using Microsoft Web deploy on build quality change event of TFS 2010

Prerequisites

  • .NET Framework 2.0 SP1 or greater
  • Web Deployment Tool
  • IIS 7.0

How It Began?

I was working with a team of developers on automation of build deployment. We had decided to go with Microsoft’s web deployment tool to host our web application and web services directly in to IIS on remote server on successful completion of our build.

In the middle of the discussion on deployment process, one of our team members raised his concern over the quality of build. He just mentioned what if our build quality doesn’t meet our expectations and still the build succeeds and the application will get deployed, but in this case we don’t want the build to get deployed. Here by the term expectations, I wanted that all unit test cases in the project should be passed and the Code Coverage result should be above the minimum value decided for the project.

We agreed to his points and we have decided that we will deploy the build only when we changed the quality of the build to ‘Ready for Deployment’ if it meets all the expectations.

How it is Implemented?

Before starting, please make sure that Web Deploy 2.0 is installed, Web Deployment Agent Service and Web Management Service are started on the deployment server.

For auto deployment on successful build, we have to pass the following parameters in build arguments of our build definition. As shown in Fig. 1.

/p:DeployOnBuild=True /p:Configuration=Debug /p:DeployTarget=MSDeployPublish 

/p:MSDeployPublishMethod=RemoteAgent /p:MsDeployServiceUrl=MachineName 

/p:DeployIisAppPath=TestSite/MySite /p:UserName=UserName /p:Password=Password 

Click to enlarge image

Fig.1

When we queue the build, the above mentioned build arguments will be passed to the Microsoft Build and Microsoft Build will call the Microsoft Web Deploy with these arguments to deploy the application in IIS.

But our requirement is to deploy on build quality change, so just remove the parameter called /p:DeployTarget=MSDeployPublish from the above mentioned parameter list. This actually enables the Microsoft Build to create a deployment package, but it will not deploy it to the server. As we will see later in the topic, we need other parameters left out to deploy on build quality change. This is the end of the first step.

Now, we will actually start our main stuff. As we know that we have to deploy our build on build quality change, somehow we have to subscribe to Build Quality Change Event of Microsoft Team Foundation Server 2010 (TFS 2010).

There is one interesting thing in Microsoft Team Foundation Server 2010 which is hard to find in the documentation and that thing is, actually you can write event subscriber for TFS 2010 which will be executed within the context of TFS 2010. This means now you don’t need to use TFS client object model to go back to the TFS server and get the required information. As now you are executing the event subscriber within the context of TFS, all the information will be available to you.

So let’s see how we have written our event subscriber for Build Quality Change Event.

For writing an event subscriber to handle TFS 2010 event, we have to implement ISubscriber Interface which is found in Microsoft.TeamFoundation.Framework.Server. When we implement ISubscriber Interface, there is one method called ProcessEvent of ISubscriber interface where actually we have to write our logic of deployment. For further explanation, let’s see the code below:

C#
public string Name
{
    get
    {
        return "BuildQualityChangedNotificationEventHandler";
    }
}

public SubscriberPriority Priority
{
    get
    {
        return SubscriberPriority.High;
    }
}

The Process Event method will actually do the deployment at deployment server. When Web Deployment tool creates a package containing the deployment content which includes config files, HTML files, ASP.NET files, etc., it also create one cmd file appended with the text “deploy.cmd”. When we execute this file through command line, it calls the Microsoft Web deploy to deploy the content of the package in IIS of deployment server. The machine name and other information for authentication will be taken from the command line arguments passed while executing the “*.deploy.cmd” file. So in the ProcessEvent method, we are actually retrieving the parameters passed in the Microsoft Build arguments in the build definition through the build definition object. Once we have the parameters with us, we will pass those parameters to the “*.deploy.cmd” file and we will execute the command using ProcessStartInfo and Process classes as shown in the code below.

For handling the Build Quality Change Event, we have to use the BuildQualityChangedNotificationEvent class which is present in Microsoft.TeamFoundation.Build.Server Assembly. The BuildQualityChangedNotificationEvent object will contain the information about build quality and build definition.

The “*.deploy.cmd“ file will be present in build drop location. We will get the build drop location from the build definition property in the BuildQualityChangedNotificationEvent class. So using the build drop location, we will find the “*.deploy.cmd” file and execute it.

C#
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, 
NotificationType notificationType, object notificationEventArgs, out int statusCode, 
out string statusMessage, out ExceptionPropertyCollection properties)
{
   BuildQualityChangedNotificationEvent buildEvent = 
   notificationEventArgs as BuildQualityChangedNotificationEvent;
   if (notificationType == NotificationType.Notification && 
   buildEvent != null && buildEvent.Build.Status == BuildStatus.Succeeded)
   {
       if (buildEvent.NewValue.Equals("Ready for Deployment", 
       StringComparison.OrdinalIgnoreCase))
       {
           DirectoryInfo directory = new DirectoryInfo(buildEvent.Build.DropLocation);
                    
           FileInfo[] deployFiles = directory.GetFiles("*.deploy.cmd", 
           SearchOption.AllDirectories);
           XmlDocument doc = new XmlDocument();
           doc.LoadXml(buildEvent.Build.Definition.ProcessParameters);
           string msDeployParameters = GetMSDeployParameters(doc);
           if (deployFiles.Length > 0)
           {
               ProcessStartInfo processStartInfo = null;
               for (int count = 0; count < deployFiles.Length; count++)
               {
                   processStartInfo = new ProcessStartInfo("cmd.exe", 
                   @"/C " + deployFiles[count].FullName + msDeployParameters);
                   processStartInfo.CreateNoWindow = false;
                   processStartInfo.UseShellExecute = false;
                             
                   Process process = Process.Start(processStartInfo);
               }
           }
        }
    }
    statusCode = 1;
    statusMessage = "Deployed";
    properties = null;
    return EventNotificationStatus.ActionApproved;
}
        
private string GetMSDeployParameters(XmlDocument xmlDoc)
{
    StringBuilder msDeployParameters = new StringBuilder(@" /Y");
            
    string[] msBuildArgumentsArr = GetMSBuildArguments(xmlDoc);
    if (msBuildArgumentsArr != null && msBuildArgumentsArr.Length > 0)
    {
        for (int count = 0; count < msBuildArgumentsArr.Length; count++)
        {
            string[] msBuildArgsKeyValueArr = msBuildArgumentsArr[count].Split('=');
            if (msBuildArgsKeyValueArr.Length > 1)
            {
                if (msBuildArgsKeyValueArr[0].Equals
                ("MsDeployServiceUrl", StringComparison.OrdinalIgnoreCase))
                {
                    msDeployParameters.Append(@" /M:" + msBuildArgsKeyValueArr[1]);
                }
                if (msBuildArgsKeyValueArr[0].Equals
                ("UserName", StringComparison.OrdinalIgnoreCase))
                {
                    msDeployParameters.Append(@" /U:" + msBuildArgsKeyValueArr[1]);
                }
                if (msBuildArgsKeyValueArr[0].Equals
                ("Password", StringComparison.OrdinalIgnoreCase))
                {
                    msDeployParameters.Append(@" /P:" + msBuildArgsKeyValueArr[1]);
                }
            }
        }
    }
    return msDeployParameters.ToString();
}
C#
private string[] GetMSBuildArguments(XmlDocument xmlDoc)
{
    string[] msBuildArgumentsArr = null;
    string msBuildArguments = string.Empty;
    XmlNode msBuildArgumentNode = GetMSBuildArgumentNode(xmlDoc);
    if (msBuildArgumentNode != null)
    {
        msBuildArguments = msBuildArgumentNode.InnerText;
        if (!string.IsNullOrEmpty(msBuildArguments))
        {
            msBuildArgumentsArr = msBuildArguments.Split(new string[] 
            { "/p:" }, StringSplitOptions.RemoveEmptyEntries);
        }
    }
    return msBuildArgumentsArr;
}

private XmlNode GetMSBuildArgumentNode(XmlDocument xmlDoc)
{
    XmlNode msBuildArgumentNode = null;
    XmlNodeList xmlNodeList = xmlDoc.GetElementsByTagName
    ("String", @"http://schemas.microsoft.com/winfx/2006/xaml");
    if (xmlNodeList.Count > 0)
    {
        foreach (XmlNode xmlNode in xmlNodeList)
        {
            XmlNode attributeNode = xmlNode.Attributes.GetNamedItem
            ("Key", @"http://schemas.microsoft.com/winfx/2006/xaml");
            if (attributeNode.Value.Equals
            ("MSBuildArguments", StringComparison.OrdinalIgnoreCase))
            {
                msBuildArgumentNode = xmlNode;
                break;
            }
        }
    }
    return msBuildArgumentNode;
}

public Type[] SubscribedTypes()
{
    return new Type[1] { typeof(BuildQualityChangedNotificationEvent) };
}

Just copy the above given code in your class and compile it. To compile it successfully, you have to add three assemblies Microsoft.TeamFoundation.Common, Microsoft.TeamFoundation.Build.Server and Microsoft.TeamFoundation.Framework.Server. Microsoft.TeamFoundation.Build.Server and Microsoft.TeamFoundation.Framework.Server assembly will be found in the path C:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin.

Now you have your compiled assembly with you which will handle the Build Quality Change Event of the TFS 2010 but how TFS 2010 will come to know where to look up for this assembly. As we know, TFS 2010 is a collection of Web Services, so you have to put this assembly in C:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins folder. TFS 2010 will take this assembly from this location and will execute the event accordingly. Any changes to the Plugins folder will restart the TFS so your assembly will get loaded automatically once it is dropped to the plugins folder. If it is not loaded, then just restart the IIS and it will be loaded.

Now we have everything in place, just queue a new build and change its build quality to “Ready for Deployment”, and our web application will be deployed at the deployed server.

Conclusion

Download the zipped project attached with the article and compile it. After compilation, copy the DLL to C:\Program Files\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins folder. Restart the IIS, your Auto deployment on build quality change will start working.

All is well. :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
India India
I am a Microsoft technology enthusiast having experience on various flavors of Microsoft Technologies which includes ASP.Net, Winforms to Silverlight, WPF.

I hold MCTS Certification on .Net 2.0 and Silverlight 4.0

Comments and Discussions

 
GeneralMy vote of 5 Pin
Mikey M.18-Jan-12 2:56
Mikey M.18-Jan-12 2:56 
GeneralMy vote of 5 Pin
Vimalsoft(Pty) Ltd29-Sep-11 4:19
professionalVimalsoft(Pty) Ltd29-Sep-11 4:19 
Questionnice article Pin
Vimalsoft(Pty) Ltd29-Sep-11 4:19
professionalVimalsoft(Pty) Ltd29-Sep-11 4:19 
QuestionQuestion on where to place the DLL Pin
ManiMadhan24-Jun-11 3:19
ManiMadhan24-Jun-11 3:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.