Using TFS Impersonation with the Version Control Client APIs

Recently I have a received a few emails asking questions about how to use the version control client APIs to create workspaces and check in files with the most recent one also asking if it could be used with TFS Impersonation to record the check ins as if they had been done by a different user.  Since I am in the process of transferring over to the Version Control Server team I thought this would be a great place for me to dig in and use the APIs to put together a code example to answer these questions.

First of all, if you are new to the TFS 2010 OM you may want to start with this post which introduces the TfsConnection, TfsConfigurationServer and TfsTeamProjectCollection objects.  Secondly, if you have not used TFS Impersonation before you will surely find it useful to read up on its introductory post here.

In order to use the code from the examples in this post you will need the following include statements:

 using System;
using System.IO;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.VersionControl.Client;

Let’s start by defining a scenario that we want to solve with this example.  Since we want to use TFS Impersonation along with the version control client OM a scenario that seems to work well would be one in which a service or a job checks in edits to a file on behalf of a user that triggers some event.  In order to have some data for our example to process, let’s define some variables up front.  This, of course, would need to come from some type of configuration file or user input if this example were to be turned into production code.

 // Holds the uri that points to the team project collection we will communicate with. 
Uri uriToTeamProjectCollection;

// The account name of the user we will be performing this check in on behalf of. 
String userAccountName;

// The server path of the file in TFS that we will be modifying. 
String serverPathForFile;

// The place on the local file system where we want to download the file from TFS 
String localWorkspacePathForFile;

// Let's assume for this example that the file actually already exists on disk in the form that 
// we want to send it to the server as. That is, we want to copy the contents of this file
 // to send to the server. This is just our way of making changes to the file. 
String localCopyOfFile;

Since I wrote the majority of the TFS Impersonation code and haven’t touched the version control client OM before, let’s start with the easy stuff first :).  As you know from reading the TFS Impersonation post above we will actually have to make two connections to our collection in order to perform this scenario.  We will use the first connection to determine the IdentityDescriptor for the user we will be impersonating since that is the piece of information needed to perform impersonation:

 TfsTeamProjectCollection baseUserTpcConnection = 
  new TfsTeamProjectCollection(uriToTeamProjectCollection);
IIdentityManagementService ims =
  baseUserTpcConnection.GetService<IIdentityManagementService>();

// Read out the identity of the user we want to impersonate 
TeamFoundationIdentity identity = ims.ReadIdentity(IdentitySearchFactor.AccountName, 
    userAccountName, MembershipQuery.None, ReadIdentityOptions.None);

Now that we have the identity of the user that we want to impersonate the next step is to actually create the impersonated connection to the collection.  But wait! This is a great time to remember that in order for impersonation to work, the user that the process is running as must have the “Make requests on behalf of others” permission for this collection.  If the user does not have this permission then you are going to see an Access Denied error message.  So, now that that is taken care of, let’s actually create the impersonated connection to the collection.

 TfsTeamProjectCollection impersonatedTpcConnection = 
    new TfsTeamProjectCollection(uriToTeamProjectCollection, identity.Descriptor);

Great! So now we have our impersonated connection.  The next step is to check in the changes to a file using this impersonated connection.  In order to this, we must start by creating a workspace and setting up working folder mappings so that we can download the existing file on the server.  Note, that for any of you out there wondering, the following code would work in a non-impersonation scenario as well.

 VersionControlServer sourceControl =
   impersonatedTpcConnection.GetService<VersionControlServer>();

// Create the workspace 
Workspace workspace =
   sourceControl.CreateWorkspace(String.Format("MyTempWorkspace-{0}", 
   DateTime.Now));

// Map the file that we want to change 
workspace.CreateMapping(new WorkingFolder(serverPathForFile, 
    localWorkspacePathForFile));

In the above code, we get the VersionControlServer object because it is the source for performing all version control operations.  Next, I create a workspace with a name that has a the current time included in it.  The reason that I use a time stamp within the name is because the name of a workspace for a given computer must be unique and although we will try to clean up this workspace later, we do not want a crash of the program to cause us to fail when trying to create the next workspace due to a naming conflict.  Finally, we map the server path for the file to a local path.  If you are going to change multiple files you do not need to create multiple mappings but can instead pick a root folder that both of the files live in.

The next thing we need to do is actually download the file that exists on the TFS server.  In our case, this is a simple as just calling the following method:

 // Download the file 
workspace.Get();

Now that we have the file downloaded locally we could add an optimization that terminates execution if the file that we want to upload is the same as the existing file.  For brevity, I will avoid that optimization and leave that to you if you feel that you need it.  Thus, the next step is to pend the edit that we want to make and then copy in the contents of the file to the file that is mapped in the workspace:

 // Pend the edit on the file to remove the read-only bit 
workspace.PendEdit(serverPathForFile);

// Copy our file to the mapped location 
File.Copy(localCopyOfFile, localWorkspacePathForFile, true);

Note that in the PendEdit call, I could have passed either the server path or the local path.

Finally, all we have left to do is check in the changes that we have pended and clean up our workspace.  This can be done via the following two calls:

 // CheckIn the file 
workspace.CheckIn(new WorkspaceCheckInParameters(
    workspace.GetPendingChangesEnumerable(), 
    "Comment detailing what happened."));

// Clean up the workspace 
workspace.Delete();

 

And voila!  The changes to the file are now on the server and they will be recorded as if the impersonated user had performed the operation.  Also, it is good to know that if you didn’t implement the optimization of checking if the files and they happen to be the same then no check in will actually happen.

Hopefully that gives you a better understanding of both how TFS Impersonation works and how the version control client OM works.  Please send me any questions you have either via the contact link above or the comment section below.

Happy Coding!