Friday, February 20, 2009

Fixing the 2008 Power Tools Team Members IM feature

If, like me, your development team uses VSTS along with Office Communicator, you may find like I did that the Team Members feature of the TFS 2008 Power Tools does not work. In this article I will explain why (atleast 1 reason why) and give you some code to help start you down the path to getting it to work for you.

The problem
I spent a while trying out all the permissions and compatibility mode setting suggestions I could find on the internet, before finally asking for help. Team System MVP Ed Blankenship came to the rescue and had me check for matching SIP and SMTP addresses for team members - and of course they didn't match. I looked at my contact info in Outlook and saw something like this screen shot, with JB.Brown@exchange.ControlStatements.com being my primary SMTP address.



What I then found out from Ed, and two other Team System MVPs; Eugene Zakhareyev and Sondre Bjellås, was that Team System doesn't specifically know or store the SIP address of Team Members, it only knows SMTP. This works great for shops where they match, and is the reason the defect ended up being shipped. When they don't match the 2008 Power Tools can't find Team Members on IM. For example Team System tries to look for the IM address JB.Brown@exchange.ControlStatments.com which is not my IM address - my IM address doesn't have the "exchange" sub-domain.

Options to make it work
The obvious, and expensive, answer is to contact your exchange admin and force them into changing their naming standards.

Since that's unlikely to happen, the second best option is to fix the Power Tools code. This is a problem though, because even if we could get access to the Power Tools source code and could alter it to use a different value for looking up IM addresses, where would we get those values from? Team System doesn't store SIP address for memberships. So, this option is also pretty expensive and unlikely to happen quickly. I'm hoping however that the 2010 version of TFS will have expanded membership properties to include SIP in some way. Until then we'll need an answer.

The good news is that there is a published API that can be leveraged to write a custom ICollaborationProvider to extend VSTS. In fact there is already a CodePlex project for sharing CollaborationProviders. It would be a waste of time and effort though to re-write all the stuff to get data out of TFS, Establish IM sessions, get contact status, keep track of running conversations, etc. All that needs to be done is change the addresses being used based on a text pattern - remove the "exchange." from STMP and use it as SIP.

The Answer
The API still presents a way to achieve what we want without a lot of expensive work.

The solution is fairly simple, and seems to work, at least for me and a few of my team members. The code is linked here (use at your own peril and discretion) and I hope to get it added to the Collaboration CodePlex project shortly. It's certain that not everyone will want a simple find-and-replace, so I've encapsulated the make-IM-work algorithm into an abstract class and deferred the step to get the SIP address from the SMTP address to implementers to allow for easy extension.

What's most important is that there is a simple way to plug in your own method for fetching SIP addresses from the SMTP values provided by Team System.

Here's the new abstract class to take care of most of the Collaboration providing. I inherited from the built-in CommunicatorProvider so that all I had to worry about was overriding the little pieces I wanted to be different. Also this prevented me from having to deal with the entire Communicator API, as well as the entire Collaboration API and kept me from doing a full regression testing of the Team Members functionality - it's still Communicator after all.


using MessengerAPI;
using Microsoft.TeamFoundation.Collaboration;
using Microsoft.TeamFoundation.Collaboration.Microsoft;

namespace TfsCommunity.Collaboration.Communicator
{
/// <summary>
/// Create an algorithim that will allow us to inject our own SIP
/// addresses for Team Members since Team System only knows about
/// their SMTP addresses. These aren't always the same, so we need
/// a way to get from SMTP to SIP for Communicator functionality
/// in the Team Members piece of the TFS 2008 Power Tools October release.
/// </summary>
public abstract class CustomCommunicatorProvider : CommunicatorProvider
{
/// <summary>
/// Defer how the contact ID is acutally augmented, so that we can
/// have different implementations base on need
/// </summary>
/// </pre><param name="contactId">
/// <returns></returns>
public abstract string AugmentContactId(string contactId);

/// <summary>
/// When adding a new contact make sure we use the desired SIP, not SMTP
/// </summary>
/// <param name="contactId">
public override void AddContact(string contactId)
{
base.AddContact(AugmentContactId(contactId));
}

/// <summary>
/// When getting a contact, get it by SIP - if nothing comes back then add it by SIP
/// </summary>
/// <param name="contactId">
/// <returns></returns>
public override Contact GetContact(string contactId)
{
string augmentedContactId = AugmentContactId(contactId);
Contact c = base.GetContact(augmentedContactId);
if (c != null)
return c;
else
{
IMessengerContact contact = (IMessengerContact)base.Messenger.GetContact(augmentedContactId, base.Messenger.MyServiceId);
MessengerContact mc = base.NewContact(contact);
return base.GetContact(augmentedContactId);
}
}

/// <summary>
/// Make sure my contact address is SIP and not SMTP
/// </summary>
public override string MyContactId
{
get{ return AugmentContactId(base.MyContactId);}
}

/// <summary>
/// When adding a new contact, use our custom <see cref="MessengerContactDecorator">
/// as the implementation of <see cref="IMessengerContact"> so that we can ensure
/// that the SIP is the <see cref="IMessengerContact.SignInName"> value.
/// </see>
/// <param name="mc">
/// <returns></returns>
protected override MessengerContact NewContact(IMessengerContact mc)
{
var contactDecorator = new MessengerContactDecorator(mc, AugmentContactId(mc.SigninName));
return base.NewContact(contactDecorator);
}
}
}


That's pretty simple, not a whole lot of code. In fact there's probably more comments shown above than code. There's 1 structure piece left and the all-important translation from SMTP to SIP to show. Let's look at the structure piece first.

When adding a new contact to the Team Members section the CommunicatorProvider.NewContact() method shown above is invoked, and the passed in argument contains a get-only property for "SignInName" which happens to be the SMTP address. To fix that NewContact() is overriden with a Decorator pattern to wrap the passed in argument to achieve the same IMessengerContact interface but have a new value for the SignInName property. Here's the decorator - MessengerContactDecorator.


using MessengerAPI;

namespace TfsCommunity.Collaboration.Communicator
{
/// <summary>
/// Decorate an object of the same interface, so that we can change
/// the value of the <see cref="SignInName"> property to be SIP
/// and not SMTP
/// </see>
public class MessengerContactDecorator : IMessengerContact
{
//our decorated instance - it's SignInName value would return SMTP
private IMessengerContact decorated;

//the new value we want to return for SignInName - the SIP address
private string newSignInName;

/// <summary>
/// Basic constructor - the thing we are decorating and how we want to
/// augment the behavior
/// </summary>
/// </summary></pre><param name="contact">
/// <param name="signInName">
public MessengerContactDecorator(IMessengerContact contact, string signInName)
{
decorated = contact;
newSignInName = signInName;
}

/// <summary>
/// The only place in this class that really matters - This is the new
/// implementation that returns a SIP address - an augmentation of our
/// decorated instance's value of SignInName
/// </summary>
public string SigninName
{
get
{
//this is it...
return newSignInName;
}
}



#region IMessengerContact implementation - nothing changed.. just calls decorated instance

public bool Blocked
{
get{return decorated.Blocked;}
set{decorated.Blocked = value;}
}

public bool CanPage
{
get { return decorated.CanPage; }
}

public string FriendlyName
{
get { return decorated.FriendlyName; }
}

public bool IsSelf
{
get { return decorated.IsSelf; }
}

public string ServiceId
{
get { return decorated.ServiceId; }
}

public string ServiceName
{
get { return decorated.ServiceName; }
}

public MISTATUS Status
{
get { return decorated.Status; }
}

public string get_PhoneNumber(MPHONE_TYPE PhoneType)
{
return decorated.get_PhoneNumber(PhoneType);
}

public object get_Property(MCONTACTPROPERTY ePropType)
{
return decorated.get_Property(ePropType);
}

public void set_Property(MCONTACTPROPERTY ePropType, object pvPropVal)
{
decorated.set_Property(ePropType, pvPropVal);
}

#endregion
}
}



Now finally the good stuff, everything else was structure to extend CommunicatorProvider, now for the content - our new Provider to fix the SIP address problem.


using Microsoft.TeamFoundation.Collaboration;

namespace TfsCommunity.Collaboration.Communicator
{
/// <summary>
/// <para>
///
/// This is an example implementation.. you plug your changes in here
///
/// </para>
/// </summary>
[CollaborationProvider("Communicator Provider for SIP/IM addresses that can pattern match SMTP addresses")]
public class FindAndReplaceCommunicator : CustomCommunicatorProvider
{
const string replace = "@";
const string find = "@exchange.";

public override string AugmentContactId(string contactId)
{
return contactId.Replace(find, replace);
}

}
}



To Deploy
  1. Download the code (use at your own peril and discretion)
  2. Change the FindAndReplaceCommunicator to meet your needs for fetching SIP addresses
  3. Compile
  4. Drop it in C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies\CollaborationProviders
  5. Restart Visual Studio
  6. Right Click on "Team Members"
  7. Choose "Personal Settings.."
  8. Click "Change"
  9. Choose "Communication Provider for SIP/IM addresses that can pattern match SMTP addresses"
  10. Click OK


And now your Team Members should light up red, green and yellow. Now if only double clicking one would start a conversation...

If you find any optimizations, issues, fixes, or extend this in any way please post it back as a comment so that we can all benefit ( and I can fix my code ).

Sunday, February 8, 2009

Team System Code Review Presentation

Tomorrow (Feb 9, 2009) I'll be demonstrating out of the box and open source solutions for performing Team System Code Reviews. Each participant will have the opportunity to record their own evaluation to take home as a I show off the various tools. We'll also try to do the same as a group in order to share opinions as thoughts as long as it doesn't get time consuming.

For a preview here's JB Brown's Team System Code Review Tools Presentation, and here's the evaluation form.

The only thing I ask is that you notify me of any corrections I should make or of suggestions about other criteria for the evaluation form.