Set your TFS profile or team image from the commandline

Update

One of the cool new features of Team Web Access is that you can now set a Team and Personal mugshot. This is one more step towards making the software development experience more personal and people oriented.

This new feature in 2012 requires each user to set their own image once per TFS instance they use and the image is automatically shares across all projects and project collections. There is, however, no simple way to bulk set the profile images for all users from the command line or through PowerShell.

Well there wasn't, until today :).

Before:

D:\Sources\TfsTeams\Main\TfsTeams CmdLine\bin\Debug>TfsTeams.exe SetProfileImage /user:avanade-corp\jesse.houwing /imagepath:identityimage.png /collection:http://localhost:8080/tfs/defaultcollection
Profile image set.
D:\Sources\TfsTeams\Main\TfsTeams CmdLine\bin\Debug>

After:

NOTE: To set the profile picture for another user, you need to have Manage Server-level Permissions.

After some tinkering with Reflector and digging in the TFS_Configuration database I figured out where the images are stored and how to set them through the TFS Client Object Model.

It turns out that the image data is stored as an extended property on the users (or groups) Identity object.

Step one is to connect to TFS and grab the Identity service:

var server = TfsConfigurationServerFactory.GetConfigurationServer(_args.Collection);
server.EnsureAuthenticated();
var identityService = server.GetService<IIdentityManagementService2>();

Step two is to retrieve the user or group through it's name:

TeamFoundationIdentity i = identityService.ReadIdentity(IdentitySearchFactor.AccountName, identity, MembershipQuery.Direct, ReadIdentityOptions.None);

We can now set the profile image (which needs to be a 144x144 png formatted image stored in a byte[]):

i.SetProperty("Microsoft.TeamFoundation.Identity.Image.Data", image /* byte[] */);
i.SetProperty("Microsoft.TeamFoundation.Identity.Image.Type", "image/png");
i.SetProperty("Microsoft.TeamFoundation.Identity.Image.Id", Guid.NewGuid().ToByteArray());

We also need to clear any temporary images that might have been uploaded through Team Web Access, but which were never committed:

i.SetProperty("Microsoft.TeamFoundation.Identity.CandidateImage.Data", null);
i.SetProperty("Microsoft.TeamFoundation.Identity.CandidateImage.UploadDate", null);

Finally we need to save the identity:

try
{
   indentityService.UpdateExtendedProperties(i);
}
catch (PropertyServiceException)
{
   //swallow; this exception happens each and every time, but the changes are applied.
}

Since Teams are internally stored as a group, and since groups are actually also Identities, the Profile Image for teams is stored the same way as they are stored for users. To fetch the identity name for a group, you can use the Team API:

var cssService = teamProjectCollection.GetService<ICommonStructureService4>();
var projectInfo = cssService.GetProjectFromName(teamProjectName);
var teamProjectCollection = new TfsTeamProjectCollection(collectionUri);
var teamService = this.teamProjectCollection.GetService<TfsTeamService>();
TeamFoundationTeam t = this.teamService.ReadTeam(this.projectInfo.Uri, team, null);
identity = t.Identity.DisplayName;

After overwriting existing images, you'll need to recycle the Team Web Access application pool. It uses a cache for all resized images and that cache needs to be reset. There doesn't seem to be a way to force Team Web Access to reload the images by anything on the identity object.

Download the latest version from the codeplex website to access these features.