Wednesday 19 January 2011

Centralising global TFS alerts/notifications

TFS email alerts are critical to keeping your team informed about changes in the TFS projects they care about. At the very least, most users probably want to know when they've been assigned a work item.

Although Visual Studio 2010 includes the Alerts Explorer (Team –> Alerts Explorer) for signing yourself up for alerts, there's no obvious way to do the same thing for a group of users. Adding custom business logic into the mix really means you'll need to find a better alternative.

Behold! My latest Codeplex project is live, I call it TFS Global Alerts:

http://tfsalerts.codeplex.com/

When we last faced this problem with Visual Studio Team System 2008, we landed on a solution from vertigo.com--I'd link to the original blog post but it's been removed from the site. Moving forward a few years, it was recently time to upgrade our TFS 2008 install to TFS 2010 and the question naturally arose about what we do with alerts: continue using the Vertigo web service or not?

The custom logic originally added to the Vertigo solution was flailing a bit due to the kludge of time so we decided to refactor the current solution to better meet our needs and keep everything working with TFS 2010. I've uploaded a generalised version of the end result for your alerting enjoyment as an example of handling TFS events in a web service (you may want to follow the links below for alternatives based on Windows services and event listeners).

The Solution

TFS Global Alerts is, at its core, a web service containing a single public method:

[WebMethod]
public void Notify (string eventXml)

Notify() accepts a single parameter, a string containing XML detailing the change that occurred and some information about the work item itself. The Notify() method signature changed slightly from TFS 2008: it previously included a second string parameter, tfsIdentityXml. I removed this parameter to get things wired up successfully with TFS 2010.

Most elements in the eventXml look a bit like this—note the OldValue and NewValue child elements:

<Field>
<Name>State</Name>
<ReferenceName>System.State</ReferenceName>
<OldValue>Active</OldValue>
<NewValue>Resolved</NewValue>
</Field>

With the Notify() method called and most of the data you need at hand, sending an email to the relevant parties is then quite simple. TFS Global Alerts does some additional work to exclude the person who made the change from receiving a notification about that change (presumably they know what they've just done!) and avoid sending notifications about code check-ins linked to a work item.

The only real complexity in this code is retrieving an email address for each user from Active Directory. In our production environment I found TFS listed users by display name however in development, with TFS running as a service account in the dev domain, users were returned as domain\username. In production, if a user had multiple accounts (some of our admins), their name was returned as "First Last (domain\username)".

In terms of deployment, I simply deploy the web service to the web server hosting TFS itself and TFS web access.

The notification system hangs off TFS' own event subscription tool, bissubscribe.exe (you'll find it at C:\Program Files\Microsoft Team Foundation Server 2010\Tools\bissubscribe.exe). TFS raises events for all sorts of different happenings and bissubscribe.exe allows you to subscribe an email address or SOAP web service to handle those events.

Since I had some problems creating a server-level subscription, here's the command I use to create a project collection subscription (which captures all events in our case):

bissubscribe /eventType WorkItemChangedEvent /address http://localhost:8080/{vdir_path}/Alerts/WorkItemChanged.asmx /collection http://localhost:8080/tfs/{project_collection_name}

Result:

TF50001:  Created or found an existing subscription. The subscription ID is 36

Debugging

TFS 2010 only sends alerts every two minutes by default and events subscribed to via bissubscribe.exe adhere to the same policy. This can be a bit annoying when you're developing so you may want to dial down the batch wait time. I've included a Powershell script in the solution to configure this but otherwise, check out chrisid's post on the subject.

For additional help debugging bissubscribe, Grant Holliday has some useful tips.

Finally, don't forget to actually configure your TFS server to send email!

Resources

If you found this post helpful, please support my advertisers.

8 comments:

  1. Hi Michael,

    I have tried installing your tfsalert webservice and I think I am running into problem which you mentioned about retrieve an email address.
    I get error in Log saying,

    Failed to retrieve an email address for user
    System.ArgumentNullException: Value cannot be null.
    Parameter name: item
    at System.Net.Mail.MailAddressCollection.InsertItem(Int32 index, MailAddress item)
    at TWA.TFS.WorkItemChanged.HandleEvent(String eventXml) in c:\twa\tfs\alerts\App_Code\WorkItemChanged.cs:line 56
    at TWA.TFS.WorkItemChanged.Notify(String eventXml) in c:\twa\tfs\alerts\App_Code\WorkItemChanged.cs:line 25


    How did you address this issue when you faced it?

    Thanks,

    Ujjaval

    ReplyDelete
    Replies
    1. I just set this up, and I'm seeing the exact same error. Any luck solving this?

      Delete
    2. Hi Joshua,

      Unfortunately I'm not actively developing this web service; have you seen my comments to Ujjaval below? Bear in mind the development was against TFS 2010, if you're using a different version.

      There really isn't much code so would suggest downloading the source and stepping through with a debugger to determine why the user isn't coming back with an email address (the code assumes the AD user has an email address configured... apologies for the lack of error handling around this case but I suppose the code should genuinely fail if the email address is unavailable!).

      Obvious thing to check would be that the user has a valid email address in AD. If you've done that, I'd next debug or trace out the actual email address being returned and verify the format (see my comments in the blog post). If you're not a developer, any mid-level dev should be able to sort this out for you given remote debug access.

      All the best, hope you get it working!

      Delete
    3. Hi, I have used this and configured this for my local TFS Server. What you people are missing is to convert the Active Directory user (display name) into an email address. contact your development department and ask them to convert the code to an email address like given as under:
      (If you are using internet email instead of exchange server)

      private void HandleEvent (string eventXml)
      {
      var eventData = new EventData (eventXml);

      // Hydrate users
      var createdBy = new User (eventData.CreatedBy);
      var assignedTo = new User (eventData.AssignedTo);
      var oldAssignedTo = new User (eventData.OldAssignedTo);
      var changedBy = new User (eventData.ChangedBy);

      #region Populate toAddresses

      var toAddresses = new MailAddressCollection ();
      Logger.Log("Email Address " + eventData.AssignedTo);
      string newEmail = eventData.AssignedTo.ToString().ToLower();
      newEmail = newEmail.Replace(" ", ".") + "@domainname.com";
      MailAddress mailAdd = new MailAddress(newEmail);
      toAddresses.Add(mailAdd);
      Logger.Log("to email is " + mailAdd.ToString());

      Delete
  2. Hi Ujjaval,

    The issue I faced when developing was related to the fact our admins had a dedicated admin account in the same domain but no email address specified for those accounts. The admin account was being returned rather than the normal user account, for whatever reason, and so inserting the mail address would fail.

    I’d suggest attaching a debugger to determine if you are actually getting an email address back from AD and investigating further if not.

    Michael

    ReplyDelete
  3. Michael,
    I followed the post and succeeded to have an event handler web service. However, the processing of each event might be long (30-60 sec) and I noticed that TFS fires the events one by one. I.e., it waits for the first event to be done by the web service, and then invokes the web service for the next event.
    Is there a way to tell TFS to invoke the web service concurently if many events occur concurrently?

    Thanks
    Sruli

    ReplyDelete
    Replies
    1. hi Michael,
      I am still new in this alerts, would you please tell me more about the second step (2. Install to a web server (suggest your TFS web frontend))? I do have the IIS installed in the build server. Thanks much!

      LHu

      Delete
  4. @Fishman: You basically need to get the web service running in IIS. I've only ever installed to the web server with the TFS web components installed but you may get away with the build server--not sure.

    ReplyDelete

Spam comments will be deleted

Note: only a member of this blog may post a comment.