Extending Team Foundation Server Version Control for Team Build Types Visual Studio integration


By Eugene Zakhareyev
August 2006 (last updated January 7, 2008)

The article discusses issues related to Team Foundation Server functionality, namely

  • Extending Team Explorer functionality related to Team Build
  • Fundamentals of TFS Version Control object model 

The knowledge of Team Foundation Server version control basics is presumed.

Introduction

With the release of Visual Studio 2005 Team System, Visual Studio become much more than development environment. One of the reasons is tight integration with Team Foundation Server in VS IDE, so that many TFS specific tasks are integrated into VSTS. One of such tasks is definition and execution of builds as part of Team Build functionality. The article will discuss creation of Visual Studio 2005 add-in that will extend the functionality of Team Explorer in order to provide integrated Team Build Types editing experience.

Build types management in Visual Studio 2005

The Team Build Types are defined for specific Team Projects and are in fact Microsoft Build Engine scripts created for purpose of building one or more solutions or projects stored in TFS source code repository. The build is executed by running that script using Team Build engine on specified computer; the build results are stored in TFS database. Team Foundation Build functionality review (except for Team Build Types editing) is out of scope of the article; refer to "Managing Builds with Team Build" and "Build System Customization" on MSDN.

User is able to define new build type by using "New Team Build Creation Wizard", accessible through "Build->New Team Build Type" menu (after selecting Team Project in Team Explorer) or by right-clicking Build Types project's node and selecting "New Team Build Type" context menu in Team Explorer. Using the wizard, initial options for the build type are set throughout several stages and upon clicking Finish, new Team Build Type is created.

The build type definition consists of three files, TFSBuild.proj, TFSBuild.rsp and WorkspaceMapping.xml. Upon creation of the build type, those files are created and placed in TFS source code control repository, under server path "$/[Team Project Name]/TeamBuildTypes/[Build type name]".

While creating new Team Build Type through the wizard effectively hides from the users all inner workings related to files format and storage of the files in source code control repository, those files (especially TFSBuild.proj MSBuild script) will probably need to be edited for all but most trivial projects manually.

Currently, Team Explorer does not provide single-click "Edit Build Type" functionality. The user is obliged to find the build files in Source Control Explorer and perform manual operations (as described in comments in created build type file):

<!-- TO EDIT BUILD TYPE DEFINITION

  To edit the build type, you will need to edit this file which was generated by the Create New Build Type wizard.
  This file is under source control and needs to be checked out before making any changes.

 

  The file is available at -$/{TeamProjectName}/TeamBuildTypes/{BuildTypeName} 

  where you will need to replace TeamProjectName and BuildTypeName with your Team Project and Build Type
  name that you created

 

  Checkout the file

  1. Open Source Control Explorer by selecting View -> Other Windows -> Source Control Explorer

  2. Ensure that your current workspace has a mapping for the $/{TeamProjectName}/TeamBuildTypes folder and that you have done a "Get Latest Version" on that folder

  3. Browse through the folders to {TeamProjectName}->TeamBuildTypes->{BuildTypeName} folder

  4. From the list of files available in this folder, right click on TfsBuild.Proj. Select 'Check Out For Edit...'

 

  Make the required changes to the file and save

 

  Checkin the file

  1. Right click on the TfsBuild.Proj file selected in Step 3 above and select 'Checkin Pending Changes'

  2. Use the pending checkin dialog to save your changes to the source control

 

  Once the file is checked in with the modifications, all future builds using this build type will use the
  modified settings

-->


This article will describe steps and code required for creation of Visual Studio 2005 add-in that will allow editing of Team Build Types with single click from Team Explorer, as well as committing the changed files to source control, so that steps described above will become unnecessary.

Note: In Visual Studio Team Foundation Server 2008, the build type files may be located not only under "$/[Team Project Name]/TeamBuildTypes" but elsewhere under Team Project folder. Thus while the logic above applies to all TFS 2005 build types, it will be incorrect for TFS 2008 build types located in custom folders.

How to create add-in

The additional commands for selected build type in Team Explorer will be provided through Visual Studio add-in. To create add-in the following steps should be followed:

  1. From the Visual Studio "File->New->Project?" menu expand the "Other Project Types" from the Project types tree
  2. Select Extensiblity under the "Other Project Types" node and then select Visual Studio Add-in. Press OK
  3. Press the Next button on the first page of appeared wizard
  4. Choose Create an Add-in using Visual C# and press the Next button
  5. Check the "Microsoft Visual Studio 2005" check box. Uncheck all others (you may have more items than what is shown below) and press the Next button
  6. Name your add-in and give it an appropriate description and then press Next
  7. Uncheck "Would you like to create a command bar UI for the Add-in" and press Next.
  8. Press Finish

The add-in created will be without commands and any useful functionality. Now, as the add-in will use Team Foundation Object Model and some Visual Studio extensibility techniques, it is supposed that TFS Team Explorer is installed at the machine as well as Visual Studio 2005 SDK (additional information about SDK may be accessed here).

The add-in will require references to the following TFS assemblies (located at Visual Studio's PrivateAssemblies path similar to C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies)

  • Microsoft.VisualStudio.TeamFoundation.dll
  • Microsoft.TeamFoundation.Common.dll
  • Microsoft.TeamFoundation.Client.dll
  • Microsoft.TeamFoundation.VersionControl.Client.dll

The assemblies should be added using Browse in "Add Reference" window, as private assemblies are not listed by Visual Studio.

Additionally, the add-in will use the following Visual Studio SDK assemblies (located at VS SDK assemblies path similar to C:\Program Files\Visual Studio 2005 SDK\2006.06\VisualStudioIntegration\Common\Assemblies) using .NET in Add References window:

  • Microsoft.VisualStudio.Shell.Interop
  • Microsoft.VisualStudio.OLE.Interop


To provide additional menus on Build Type context menu, the named commands must be created:

  • Checkout - to check out the build types files for edit to local disk drive
  • Edit - to open build types on local disk for editing
  • Checkin - to check in changed build types files into repository
  • Undo - undo changes to build types files in repository
  • Configure - to configure add-in

The created commands must be added to appropriate command bar (to "Build Type" command bar for commands appearing when right-clicking build types and "Tools" command bar for configuration command).

The VS automation methods used for creation of commands are out of scope of current article; the details may be viewed in add-in source code.

Accessing selected build type

Now that the commands are created, one needs to obtain the build type to edit together with additional information required to access TFS version control (viz. TFS server name and selected project name).

To retrieve selected build type node in Team Explorer as well as Team Foundation server context data, the following code will be used:

        private bool GetTfsSelected(out string serverName, out string projectName, out string buildTypeName)

        {

            serverName = null;

            projectName = null;

            buildTypeName = null;

 

            // get Team Explorer service

            OleInterop.IServiceProvider serviceProvider = (OleInterop.IServiceProvider)_applicationObject;

            Guid guid = new Guid(typeof(IVsTeamExplorer).GUID.ToString());

            IntPtr ptr;

            int result = serviceProvider.QueryService(ref guid, ref guid, out ptr);

            if (result != 0)

                throw new ApplicationException("Failed to get Team Explorer service");

            IVsTeamExplorer explorer = (IVsTeamExplorer)Marshal.GetObjectForIUnknown(ptr);           

            // explorer is not open yet

            if (explorer.TeamExplorerUIHierarchy == null)

                return false;

 

            // get TFS server node

            object value;           

            result = explorer.TeamExplorerUIHierarchy.GetProperty((uint)       

                Microsoft.VisualStudio.VSConstants.VSITEMID_ROOT,

                (int)__VSHPROPID.VSHPROPID_Name, out value);

            // if failed to get hierarchy root           

            if (result != 0)

                throw new ApplicationException("Failed to get TFS server node name");

            serverName = value as string;

 

            // get project name

            ProjectContext projectContext = explorer.GetProjectContext();

            // if no selected project

            if (projectContext.ProjectName == null)

                return false;

            projectName = projectContext.ProjectName;

 

            // get selected item id

            uint itemID;

            IVsMultiItemSelect multiSelect;

            result = explorer.TeamExplorerWindow.GetCurrentSelection(out ptr, out itemID, out multiSelect);

            // if no selection

            if (result != 0)

                return false;

 

            // get selected node name (we suppose that menu is visible only on builds)

            IVsHierarchy hierarchy = (IVsHierarchy)Marshal.GetObjectForIUnknown(ptr);

            result = hierarchy.GetProperty(itemID, (int)__VSHPROPID.VSHPROPID_Name, out value);

            if (result != 0)

                return false;

            buildTypeName = value as string;           

            return true;

        }

The code performs the following tasks:

  1. Gets IVsTeamExplorer service using OLE.Interop
  2. Uses IVsTeamExplorer TeamExplorerUIHierarchy property to retrieve root node name (which is team server name)
  3. Uses IVsTeamExplorer GetProjectContext method to retrieve selected project context (to get Team Project name)
  4. Uses IVSTeamExplorer TeamExplorerWindow property to get currently selected hierarchy node and then its name. As relevant add-in commands are always called from selected build type node it may be suggested that selected node name is selected build type name

Thus after performing the method TFS server name, Team Project name and build type name become available in add-in for usage with TFS Version Control.

Working with version control

Now, that the commands are created and initial build type data are available one needs to implement logic for the command execution and correct status display (so that Check In command will appear only if build types files are checked out etc.)

For all build type operations special class called BuildTypeEditor will be used. The follwing class diagram describes class methods and properties.
 
The class constructor will receive TFS server name to initialize all required TFS object model objects and then will use Select method before any operation on build type is performed.

In constructor, new instance of TeamFoundationServer is created using factory. It may be safely supposed that authentication will not be required (as commands are called through Team Explorer). After team server instance is created, new instance of VersionControlServer is created. VersionControlServer is a class that provides all Version Control related functionality in TFS object model, and it will be used for version control operations.

Select method will simply set internal members to define scope for any subsequent operations. In order to edit build type it is required first to retrieve build type files from TFS source code repository to local disk drive. File operations in TFS, such as Get latest version or Check out are all performed in context of user local workspace. Workspace is essentially a set of mappings between item server path in source code control repository and local path at user's workstation. Thus in order to get and check out files for edit, workspace needs to be available. Add-in will create and maintain single workspace for all files operations named BuildTypesWorkspace. Let's have a look at the following code snippet:

                // get workspace for local build files editing

                Workspace[] workspaces = _serverVc.QueryWorkspaces(WorkspaceName, UserName,

                                                              ComputerName);               

                Workspace workspace = null;

                Debug.Assert(workspaces.Length < 2);

                // either create or take found workspace

                if (workspaces.Length == 0)

                    workspace = _serverVc.CreateWorkspace(WorkspaceName, UserName, WorkspaceComment);

                else

                    workspace = workspaces[0];

                // check - if project is not mapped, then map it

                if (!workspace.IsServerPathMapped(ProjectServerPath))

                    workspace.Map(ProjectServerPath, ProjectLocalPath);


The code performs following tasks:

  1. Gets array of workspaces according to specified criteria (workspace name, user name and computer name) using QueryWorkspaces method of VersionControlServer object. As exact criteria are provided (workspace name, user name and computer name), the array will either contain single workspace (if one exists at current computer) or empty array
  2. If workspace does not exist, it is created using CreateWorkspace method with same parameters values as in search call before
  3. Once there is valid workspace, it is possible to check whether currently selected team project is mapped to local path on current computer using Workspace class IsServerPathMapped method. If it is not, Workspace.Map method is used to create new mapping (the mapping will be created of team project server path to local directory; as all build types are located at TeamBuildTypes folder under the project folder, one mapping is sufficient for all build types).


The properties used in the method calls such as UserName and ProjectLocalPath are defined in BuildTypeEditor class. Of special interest are properties UserName, ProjectServerPath and ComputerName.

        string UserName

        {

            get

            {

                return _serverTfs.AuthenticatedUserName;

            }

        }

        string ProjectServerPath

        {

            get

            {

                return String.Format("$/{0}", ProjectName);

            }

        }      

        string ComputerName

        {

            get

            {

                return Workstation.Current.Name;

            }

        }


UserName property uses TeamFoundationServer property AuthenticatedUserName to get currently logged in user name. ProjectServerPath uses selected Team Project name to make up its source code control repository path (based on the fact that all projects are located directly under the root path $/). Computer name uses Workstation class static property Current to retrieve current computer name.

Once there exists valid workspace with required Team Project mapping, it is possible to get build types files to local disk. The following code is used:

                // Check if get may be performed without problems

                GetRequest request = new GetRequest(BuildTypeServerPath, RecursionType.OneLevel,

                   VersionSpec.Latest);

                GetStatus status = workspace.Get(request, GetOptions.Preview | GetOptions.GetAll);

                // if status is not empty - will not proceed and let the user fix it

                if (!status.NoActionNeeded && (status.NumFailures > 0 || status.NumConflicts > 0 ||

                      status.NumWarnings > 0))

                    return false;

                // get the files latest version locally

                status = workspace.Get(request, GetOptions.GetAll);

                // if failed - let the user fix it

                if (status.NumFailures != 0)

                    return false;

                // pend edit with exclusive lock

                int result = workspace.PendEdit(new string[] { BuildTypeServerPath },

                    RecursionType.OneLevel, null, LockLevel.CheckOut);

                return (result >= 3);


The code performs the following tasks:

  1. GetRequest is created with parameters that mean "Get latest versions of all files immediately under build type server path to local disk as specified by workspace mapping". After that get operation is initiated on workspace using one of the method Get overloads. The options for the operations are set to GetAll (meaning that all files, not those that server thinks are outdated are to be retrieved) and Preview (meaning not to actually get files but preview the operation expected results). Preview flag is useful to detect situation where files are present locally and some of them are writable
  2. Get status is examined to see whether there are some errors or warnings; in case there are some operation is cancelled to let user decide what to do. All source code control operations are logged in TFS Source Control Output pane in Visual Studio, so detailed message is available to user
  3. If status is OK, Get request is initiated without Preview option, and files are retrieved from server to local disk drive according to the workspace mapping
  4. Once the files are available locally, it is possible to check out those files (or in TFS terminology to "pend an edit" on the file). The operation is performed by using Workspace class PendEdit method (one of its many overloads) and giving it path, recursion option, empty file type filter and lock level (meaning "check out all files immediately under build type server path and make them writable on local disk as specified by workspace mapping and set exclusive lock on those files"). Exclusive lock is set in order to prevent build type editing by other user as we do not expect this to be a concurrent activity

So after executing code above, editable latest version of the build type files is available at local disk.

Let's forget for the moment that the files after checkout are to be open for editing and concentrate on check in activities that will be required once the files are modified. The following code snippet may be used both for check in or changes undo (depending on passed flag value). In that snippet most of error handling is removed for clarity (it is supposed that check out succeeded before CommitEdit is called):

            // find build type editing workspace

            Workspace[] workspaces =_serverVc.QueryWorkspaces(WorkspaceName, UserName, ComputerName);

            if (workspaces.Length != 1)

                throw new ApplicationException(String.Format("Workspace {0} not found", WorkspaceName));

            // check that server path mapping exists

            Workspace workspace = workspaces[0];

            if (!workspace.IsServerPathMapped(BuildTypeServerPath))

                throw new ApplicationException(String.Format("Path {0} is not mapped in {1}",

                       BuildTypeServerPath,WorkspaceName));

            // get pending changes

            PendingSet[] sets = workspace.QueryPendingSets(new string[] { BuildTypeServerPath },

                                      RecursionType.OneLevel, WorkspaceName, UserName, false);

            if (sets.Length != 1)

                throw new ApplicationException(String.Format("No pending edit changes in path {0},
                    mapped in {1}"
, BuildTypeServerPath, WorkspaceName));

            // undo or check in changes

            if (undo)

                    workspace.Undo(sets[0].PendingChanges);

            else

                    workspace.CheckIn(sets[0].PendingChanges, CheckinComment);


After the "check out" code snippet walkthrough, most of the code is fairly straightforward

  1. Workspace where build type files where checked out is found
  2. All pending files in the workspace under build type server folder are retrieved by using Workspace class QueryPendingSets method (with almost same parameters specified for PendEdit method; the last parameters specifies that we are not going to use the pending sets returned for files downloads). The return value should contain exactly one PendingSet (with build type files checked out at previous stage)
  3. Once the pending set is available, it is possible either to undo changes or to check in them. To undo changes Undo method of Workspace class is used and pending changes array returned in PendingSet is used as a parameter to specify changes to be undone. When changes are undone, local files will be replaced and all edits to them will be forgone. To check in changes, CheckIn method of Workspace class will be invoked, again passing in PendingChanges array and check in comment

Assembling it all together

Now that it is clear how selected build type files are retrieved locally and how version control operations are to be performed, it is time to plug that into add-in. The commands behavior (enabled, visible etc.) is controlled by QueryStatus method and command execution logic is invoked in Exec method (both methods add-in implements as part of IDTCommandTarget interface inheritance).

I will not linger on QueryStatus add-in method implementation (code is available in article sources), suffice it to say that logic implemented displays command corresponding to build files status: "Check Out for Edit" available for build type that was not checked out for edit locally and "Check In Changes/Undo Changes" available for build type that is being edited locally. The implementation uses QueryPendingSets methods discussed above.

Exec method will bring together all pieces of code we have discussed.
First it is required to retrieve TFS server, Team Project and selected build type using code described in "Accessing selected build type data".
Secondly, depending on the command invoked the build types files will be either checked out or checked in (using code described in "Working with version control"). In case that files are being checked out, the .proj file will be opened either in Visual Studio or in user-configured application (see Configure command in source code).

Conclusion

In current article I have tried to illustrate the power and elegancy of TFS Version Control object model and overall ease with which one may extend TFS functionality in Visual Studio. The object model methods and classes used illustrate basic functionality of Version Control and approach to object model more advanced uses. You may download source code for this article as well as add-in MSI installation package (please note that newer version of add-in is available). Code snippets described in the article generally leave out error handling code so for whole picture please view complete source code version. See also Team Build Sidekick product page for updates.

Update from November 5, 2006

Version 1.1 of Team Build Sidekick add-in was renamed to Team Foundation Sidekick add-in and in addition to functionality described in the above article that is related to Team Build provides additional features augumenting Source Control Explorer functionality.
You may wish to obtain source code for the newer version together with version 1.1 MSI installation.

Update from January 7, 2008

Team Foundation Sidekick add-in application is no longer supported (the source code is still available for educational purposes). The functionality previously available in add-in is now part of Team Foundation Sidekicks 2.0.

About the author

Eugene Zakhareyev is a principal consultant at Attrice Corporation in San-Jose, California. His primary focus is SCM and development methodologies using Visual Studio Team System. He may be contacted through his email.


© 2006-2016 Attrice Corporation. Last updated 30-Dec-2015 Contact us