Friday, October 27, 2006

Since my post yesterday I just decided to update the code and also include Buck's comments.  I even added a simple installer as a bonus since that last post was just useless.  Here is the new version and this time in a zip ;)

Microsoft.Services.TfsPolicies-installer.zip (159.25 KB)

Microsoft.Services.TfsPolicies-source.zip (37.17 KB)
posted on Friday, October 27, 2006 11:56:05 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
 Thursday, October 26, 2006

It's late, your tired and yet somehow you got stuck fixing the build because junior engineer bob just fired off a check-in and took off.  It looks like the build is now broken because junior hasn't done a get latest in a month.  In the meantime 10 other people check-in and just end up compounding the problem.

Unfortunately things like this happen.  One day I just got so frustrated, I just wrote a custom TFS check-in policy which gets the last build status and validates a particular build type was actually passing before you check-in.  I started with Jeff Atwood and James Manning posts where they have done a great job explaining how to write a simple custom check-in policies.

Let's Code!!

There are two things you have to do for any check-in policy, define the policy and configure it.  This sample isn't much different than the code comment examples. 

First lets look at the policy definition. The real work is in the Evaluate method.  This is where we call TFS and find out what state the build is in. This sample could further be updated and remove the requirement to configure the TfsServer.  You can actually get that info from the workspace IPendingCheckin supplied in Initialize() (_pendingCheckin.PendingChanges.Workspace).  From the Workspace object, it’s workspace.VersionControlServer.TeamFoundationServer.

using System;
using System.Diagnostics;
using System.Windows.Forms;
using Microsoft.TeamFoundation.VersionControl.Client;

using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Build.Proxy;
using System.Text;

namespace Microsoft.Services.TfsPolicies
{
    [Serializable]
    public class BuildStatusPolicy : PolicyBase
    {
        string _tfsServer = @"http://TFSServer:8080";
        string _tfsProject = "TfsProject";
        string _buildType = "BuildType";

        [NonSerialized]
        protected bool _disposed = false;
        [NonSerialized]
        protected IPendingCheckin _pendingCheckin;

        //public virtual event PolicyStateChangedHandler PolicyStateChanged;

        #region IPolicyDefinition

        public override string Description
        {
            get { return Strings.PolicyDescription; }
        }

        // This string is a description of the type of our policy.  It will be displayed to the
        // user when they select our policy type in the list of policies installed on the system
        // as mentioned above.
        public override string TypeDescription
        {
            get { return Strings.PolicyTypeDescriptions; }
        }

        // This string is the type of our policy.  It will be displayed to the user in a list
        // of all installed policy types when they are creating a new policy.
        public override string Type
        {
            get { return Strings.PolicyType; }
        }

        // This is a string that is stored with the policy definition on the source
        // control server.  If a user does not have our policy plugin installed, this string
        // will be displayed.  We can use this as an opportunity to explain to the user
        // how they might go about installing our policy plugin.
        public override string InstallationInstructions
        {
            get { return Strings.PolicyInstallInstructions; }
        }

        public override bool CanEdit
        {
            //TODO maybe check the role of the user trying to change?
            get { return true; }
        }

        #endregion //IPolicyDefinition

        #region IPolicyDefinition

        public override void Initialize(IPendingCheckin pendingCheckin)
        {
            if (_pendingCheckin != null)
            {
                throw new InvalidOperationException("Policy already initialized.");
            }

            if (_disposed)
            {
                throw new ObjectDisposedException(null);
            }

            _pendingCheckin = pendingCheckin;

            pendingCheckin.PendingChanges.CheckedPendingChangesChanged += new EventHandler(pendingCheckin_CheckedPendingChangesChanged);
        }

        // This method is invoked by the policy framework when the user creates a new checkin
        // policy or edits an existing checkin policy.  We can use this as an opportunity to
        // display UI specific to this policy type allowing the user to change the parameters
        // of the policy.
        public override bool Edit(IPolicyEditArgs policyEditArgs)
        {
            if (_pendingCheckin != null)
            {
                throw new ApplicationException("The policy can't be edited after it has been initialized for evaluation.");
            }

            using (BuildStatusPolicyConfiguration buildStatusPolicyConfiguration = new BuildStatusPolicyConfiguration(_tfsServer, _tfsProject, _buildType))
            {
                DialogResult formResult = buildStatusPolicyConfiguration.ShowDialog(policyEditArgs.Parent);

                if (formResult == DialogResult.OK)
                {
                    _buildType = buildStatusPolicyConfiguration.TfsBuildType;
                    _tfsProject = buildStatusPolicyConfiguration.TfsProject;
                    _tfsServer = buildStatusPolicyConfiguration.TfsServer;

                    return true;
                }
                return false;
            }
        }

        public override PolicyFailure[] Evaluate()
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(null);
            }
            
            TeamFoundationServer teamFoundationServer = TeamFoundationServerFactory.GetServer(_tfsServer);
            BuildStore buildStore = teamFoundationServer.GetService(typeof(BuildStore)) as BuildStore;

            BuildData[] buildData = buildStore.GetListOfBuilds(_tfsProject, _buildType);

            BuildData lastBuild = this.getLastBuild(buildData);

            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("Builds Returned: {0} \n", buildData.Length.ToString());
            sb.AppendFormat("Build Status: {0} \n", lastBuild.BuildStatus);
            sb.AppendFormat("Build Status ID: {0} \n", lastBuild.BuildStatusId);
            sb.AppendFormat("Finish Time: {0} \n", lastBuild.FinishTime);
            sb.AppendFormat("Build Number: {0} \n", lastBuild.BuildNumber);

            EventLog.WriteEntry("Microsoft.Services.TfsPolicies", sb.ToString());

            if (lastBuild.BuildStatus.Equals("Failed", StringComparison.OrdinalIgnoreCase))
            {
                return new PolicyFailure[] { new PolicyFailure(string.Format(Strings.PolicyPrompt, _buildType), this) };
            }
            else
            {
                return new PolicyFailure[0];
            }
        }

        // This method is called if the user double-clicks on a policy failure in the UI.
        // We can handle this as we please, potentially prompting the user to perform
        // some activity that would eliminate the policy failure.
        public override void Activate(PolicyFailure failure)
        {
            MessageBox.Show(string.Format(Strings.PolicyPrompt, _buildType), "How to fix your policy failure");
        }

        // This method is called if the user presses F1 when a policy failure is active in the UI.
        // We can handle this as we please, displaying help in whatever format is appropriate.
        // For this example, we'll just pop up a dialog.
        public override void DisplayHelp(PolicyFailure failure)
        {
            MessageBox.Show(string.Format(Strings.PolicyDisplayHelp, _buildType), "Prompt Policy Help");
        }

        #endregion IPolicyDefinition

        private void pendingCheckin_CheckedPendingChangesChanged(Object sender, EventArgs e)
        {
            if (!_disposed)
            {
                OnPolicyStateChanged(Evaluate());
            }
        }

        private BuildData getLastBuild(BuildData[] builds)
        {
            DateTime lastBuildDateTime = builds[0].FinishTime;

            int position = 0;

            for (int i = 0; i < builds.Length; i++)
            {
                if (builds[i].FinishTime > lastBuildDateTime)
                {
                    lastBuildDateTime = builds[i].FinishTime;
                    position = i;
                }
            }

            return builds[position];
        }
    }
}

 

Now lets configure it.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Microsoft.Services.TfsPolicies
{
    public partial class BuildStatusPolicyConfiguration : Form
    {
        public BuildStatusPolicyConfiguration(string TfsServer, string TfsProject, string TfsBuildType  )
        {
            InitializeComponent();

            txtTfsUrl.Text = TfsServer;
            txtTfsProject.Text = TfsProject;
            txtTfsBuildType.Text = TfsBuildType;

            this.txtTfsBuildType_TextChanged(null, null);
            this.txtTfsProject_TextChanged(null, null);
            this.txtTfsUrl_TextChanged(null, null);
        }

        public string TfsServer
        {
            get { return this.txtTfsUrl.Text; }
        }

        public string TfsProject
        {
            get { return this.txtTfsProject.Text; }
        }

        public string TfsBuildType
        {
            get { return this.txtTfsBuildType.Text; }
        }

        private void btnOk_Click(object sender, EventArgs e)
        {
            DialogResult = DialogResult.OK;
            Close();
        }

        private void txtTfsUrl_TextChanged(object sender, EventArgs e)
        {
            btnOK.Enabled = txtTfsUrl.Text != string.Empty;
        }

        private void txtTfsProject_TextChanged(object sender, EventArgs e)
        {
            btnOK.Enabled = txtTfsProject.Text != string.Empty;
        }

        private void txtTfsBuildType_TextChanged(object sender, EventArgs e)
        {
            btnOK.Enabled = txtTfsBuildType.Text != string.Empty;
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

Lastly I dropped the strings into their own struct.

using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.Services.TfsPolicies
{
    internal struct Strings
    {
        internal const string PolicyDescription = "Stop users from checking in when the configured BuildType is in a 'Failed' status.";
        internal const string PolicyTypeDescriptions = "This policy will prompt the user to decide whether or not they should be allowed to check in based on the build status.";
        internal const string PolicyType = "Check for passing build";
        internal const string PolicyInstallInstructions = "Please install the package Microsoft.Services.TfsPolicies.Setup.msi.";
        internal const string PolicyPrompt = "Please wait to check-in until the {0} build is passing";
        internal const string PolicyDisplayHelp = "This validates the {0} build status is not in failure";
    }
}

One thing to note, be aware of performance.  This just does the mechanics and is not optimized.  During the course of a check-in this will actually call the web service a number of different times.  On top of that the web service endpoint only returns all build results for a build type rather than just the last one.  So depending on your team size etc. you've now been warned.  Sometimes a performance hit is worth the productivity gained.

posted on Thursday, October 26, 2006 8:26:45 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
 Wednesday, October 25, 2006

Total geek stuff!!! controlthink home control has released an a .NET SDK to control all of your house automation needs.  Yes, my house is now going to get .NETized.

posted on Wednesday, October 25, 2006 8:04:25 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback

Join members of the Visual Studio Team System product group to discuss features available in Visual Studio Team Foundation Server, Team Editions for Architects, Developers, Database Pros, and Testers. In addition, discuss what's new in the latest Community Technology Preview (CTP).

Join the chat on Wednesday, November 8th, 2006 from 10:00am - 11:00am Pacific Time.

Add to Calendar

Additional Time Zones

posted on Wednesday, October 25, 2006 5:37:53 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
 Monday, October 23, 2006

This weekend I was hard at work tidying up the TfsAlert source base, fixing Vista breaking changes.  This morning I finally migrated it to CodePlex.  Right now we are actively working on the package for an initial release.  We should have that released later this week.

If you download the source there are a few things to note.

  • The source base with all written TDD but using partial classes.  Our deployment package will strip out the tests but right now if you run you will need those testing assemblies.
  • We currently call the TFS OM directly to subscribe so you have to have those assemblies installed .  This will be changed in a future release to call the Web Service directly. 
  • Currently listens on port 9999.  You have to manually open the Windows Firewall so it can listen on that port.
  • You have to setup the config.  We don't have a UI screen for that yet.  There are just a few settings in the config file.
    • Add your TFS Server.
    • Add your User Id
    • Select the events you want to subscribe to, Selected == true / false
    • Add a filter expression for that event.

There is one critical bug we are currently addressing.  Right now if your network interface drops out and you are assigned a new IP we orphan that event subscription in TFS and don't get a chance to clean it up.

posted on Monday, October 23, 2006 9:04:50 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
 Friday, October 13, 2006

So just what is TfsAlert?

TfsAlert is a .Net 3.0 smart client which monitors for subscribed TFS notifications and will display a balloon window in the Windows TaskBar Notification Area.  It is built on top of WCF where you can subscribe to any TFS event you feel fit.  It's initial focus is around Team Build management but will also include features around Work Item tracking.

How about that for an official party line!

I started TfsAlert back when TFS was in it's beta days.  I've used Cruise Control for many years and I just felt naked without the tray, so TfsAlert was born.  Yes I have copied some of the feature ideas.  So far it has been a great learning experience of not only .

Net 3.0 but TFS integration.  I am excited about the expanded development and communities response.

A few minutes ago the TfsAlert CodePlex how was created.  It's going to take me a few days just to get everything transferred over and get a build out, but it's high on my list. 

http://www.codeplex.com/Wiki/View.aspx?ProjectName=TfsAlert

posted on Friday, October 13, 2006 2:00:07 PM (Central Standard Time, UTC-06:00)  #    Comments [1] Trackback

 Wow, so its really been a long time since I posted any real content. This past year has just been a whirl wind of activities for both my wife and I. We had our first kid, built a new house, yaddaa yaddaa yaaddaa. Before you know it a whole 6 months slipped from your fingers.

So enough of the excuses, lets catch up.

  1. This is my kid Ethan or "E" as we call him, he wanted to say hello! -->
  2. New version of TFS Power Toys released. If you haven't installed them yet what are you waiting for? You don't have to install the SDK anymore.
  3. Martin Woodward became an MVP! A big congratulations out to him.
  4. TFS SP1 Beta was released.
  5. Jeff Beehler started to blog again.

There is some exciting stuff coming so stayed tuned.

posted on Friday, October 13, 2006 1:09:25 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
 Wednesday, August 16, 2006

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sit amet est. Cras ante. Integer ac nisi vel mi aliquet cursus. Ut eu lacus. Praesent et turpis. Pellentesque pede nisi, volutpat at, iaculis vel, vulputate quis, risus. Vestibulum sed turpis. Nunc pulvinar tortor. Sed vulputate lectus sed massa ultrices interdum. Cras pellentesque dignissim sapien. Proin semper, nunc vitae posuere euismod, erat lectus consectetuer metus, eget tristique diam sapien in enim. Nam quam velit, porta eu, congue non, volutpat sagittis, diam. Pellentesque euismod tortor. Phasellus eget nibh. Mauris felis tellus, suscipit in, dignissim et, sollicitudin lacinia, metus. Cras at lorem ac pede venenatis volutpat. Vivamus nisi pede, scelerisque condimentum, mattis id, commodo at, magna. Pellentesque arcu lectus, bibendum eu, sodales pellentesque, pulvinar non, quam. Donec accumsan tempus sapien.

posted on Wednesday, August 16, 2006 8:54:30 PM (Central Standard Time, UTC-06:00)  #    Comments [1] Trackback