Wednesday, August 12, 2009

TFS Admin, Part II - POC Automated Order process

Update: TFS2010 Automated team project creation

In this post we will look at a possible solution covering the team project order process. The solution covers the whole process from order entry to delivery notification and is based upon the requirements listed in the previous post.

A good start, but a long way to go
This is just a proof of concept, a quick hack to prove it’s possible to accomplish an automated team project order process. It's a long easy to go to a releasable product or project with error handling, tracing, packaging and deployment and so on. The best would be to get something like this integrated into the TFS product or into the TFS Admin tool


Architecture and design
One distinctive requirement is the configuration, collection and storage of metadata. As we already have a working instance of WSS, it falls naturally to use its capability of custom lists to handle metadata.

In order to make team project creation fully automated after approval, we would have to programmatically create new team projects. This capability is provided in the latest power tool command line util.
Managing user rights for SSRS, WSS and TFS is also a hard requirement to manage. Fortunately we have the TFS admin tool on Codeplex providing sourcecode for handling this requirement.
Of course some coding and usage of the TFS API is needed to stitch it together.

Creating the TeamProject list
In order to create the teamproject list and the order form I created a custom list at the root sharepoint site of my tfsserver, naming it to TeamProjects.

To this list I added core field as follow. I renamed the Title field to Project Name, added a people column named Project Administrator. I also added a choice column named Process Template with the exact process templates strings.

Under List versioning settings I activated Content Approval enabling a approval workflow.
I also added a view PendingCreation with the filter ApprovalStatus=Approved and TeamProjectCreated=No.

To make it easily accessible I added Quick Launch links to the list as well as to the TeamProjects: New Item form.

The result is a new form for requsting a team project as illustrated to the right.

Automating project creation puting it together
Now we need some code reading the list and creating team projects, giving the project administrator access rights, reassigning the workitems to the project administrator, applying enterprise standard access rights and policies. For the purpose of demonstrating how this can be done, I've put together a a simple solution. The complete solution is attatched as a ZIP file.


http://cid-5d46cae8c0008cf0.skydrive.live.com/self.aspx/.Public/POC%20AutomatedTeamProjectCreation.zip


Code walkthrough
I will also go through the code briefly starting with the main function holding it all together.

static void CreatePendingTeamProject()
{
string serverName = "http://teamsystem.sogeti.se";
string tfsServerPort = ":8080";
string tfsServerUrl = serverName + tfsServerPort;

string listName="TeamProjects";
string viewName="PendingCreation";

string projectNameCol = "LinkTitle";
string projectAdminCol="Project Admin";
string procTemplateCol = "Project template";
string projectCreatedCol = "TeamProjectCreated";

//Fetch an entry for a requested team project form sharepoint list
SharePointListWrapper spReqListWrapper = new SharePointListWrapper(serverName, "", listName, viewName);
ReqForTeamProject reqTP = spReqListWrapper.GetRequestForTeamProject(projectNameCol,procTemplateCol, projectAdminCol);

I have wrapped all access to the Sharepoint list in a special SharePointListWrapper class. The interesting things is in this method


public ReqForTeamProject GetRequestForTeamProject(string projectNameCol, string templateCol, string projectAdminCol)
{
SPSite site = new SPSite(ServerUrl);
SPWeb web = site.AllWebs[RelativeSiteUrl];
SPList myList = web.Lists[ListName];
SPView view = myList.Views[ViewName];
Console.WriteLine("Connected to sharepoint list");

if (myList.GetItems(view).Count > 0)
{
Console.WriteLine("Found a item");

SPListItem itm = myList.GetItems(view)[0];
ReqForTeamProject tp = new ReqForTeamProject();

tp.Id = itm.ID;
tp.Name = itm.GetFormattedValue(projectNameCol); //Project Name, Forced to use this cause Title is renamed.
tp.ProcTemplate = itm.GetFormattedValue(templateCol);
tp.ProjectAdmin = ((SPFieldUserValue)itm.Fields[projectAdminCol]
.GetFieldValue(itm[projectAdminCol].ToString()))
.User.LoginName; ;

Console.WriteLine(tp.ProcTemplate);
Console.WriteLine("Returning a Request for teamproject user " + tp.ProjectAdmin);

return tp;
}
else
{
return null;
}
}
The code opens a SharePoint list, and the view PendingCreation, select the first item if any. As you can see, I’ve used the Microsoft.SharePoint library for access to the SharePoint list. This will require the solution to be deployed at the server running WSS. If you want to deploy it elsewhere you have to use the WSS web services instead.

Back to the main function we check if we got a request to create teamproject.

if (reqTP != null)
{
//Create the Team Project
TFPTWrapper tftp = new TFPTWrapper(tfsServerUrl);
tftp.CreateTeamProject(reqTP.Name, reqTP.ProcTemplate);

The next thing in the code is the actual teamproject creation. This is done by calling the TFPT command line utility. Calling the command line utility is wrapped in the TFPTWrapper class, and the method CreateTeamProject simply builds a command line and execute it by starting a new process.

After the team project is successfully created, we need to give the project administrator access rights.

// Add rights to the ProjectAdmin
TFSAdminTooolWrapper admin = new TFSAdminTooolWrapper(tfsServerUrl);
admin.AddProjectAdmin(reqTP.ProjectAdmin, reqTP.Name);

To set the actual roles to TFS, WSS and RS we rely on the TFS Admintool. I have chosen to wrap a developing branch of the TFS AdminTool. The reason for this is that this branch has separated the code handling the actual tfs, WSS and RS servers into a separate project. making it possible to reference the dll file and not include the whole source for the TFS AdminTool.

This reduces the handing of setting user rights to this function

public void AddUser(string user, string teamProject, string tfsRole, string spRole, string rsRole)
{
TFSAdministrationTool.Proxy.Common.TfsAdminToolTracer.Initialize(null);
Console.WriteLine("Adding user with admin tool " + user);

TFSAdministrationTool.Proxy.ITeamFoundationServerProxy iTFSProxy = TFSAdministrationTool.Proxy.TeamFoundationServerProxyFactory.CreateProxy();
iTFSProxy.Connect(TFSServerUrl);
Console.WriteLine("Admin tool connected ");

iTFSProxy.SelectTeamProject(teamProject);
Console.WriteLine("Team project selected");

iTFSProxy.AddUserToRole(user, tfsRole);
Console.WriteLine("Added TFS role " +tfsRole);

iTFSProxy.SharePointAddUserToRole(user, spRole);
Console.WriteLine("Added WSS role " + spRole);

iTFSProxy.ReportingServiceAddUserToRole(user, rsRole);
Console.WriteLine("Added RS role " + rsRole);
}


public void AddProjectAdmin(string user, string teamProject)
{
AddUser(user, teamProject, "Project Administrators", "Full Control","Content Manager");

}


}

After adding the project administrator we need to setup standard access. In my case I made a xml config file containing the user, TFS, WSS & RS role to add. the users and roles are applied using the same TFSAdminToolWrapper.


//Setup standard team project security
StandardRightsXml stdRightXml = new StandardRightsXml("stdTeamProjectSecurity.xml");
foreach (UserRights stdUsr in stdRightXml.GetTeamProjectRights())
{
admin.AddUser(stdUsr.User, reqTP.Name, stdUsr.TfsRole, stdUsr.SpRole, stdUsr.RsRole);
}

All work items assigned to the creator of the teamproject must be reassigned to the new project administrator.

// Reassign all workitems to the new owner
TFSWorkItemStoreWrapper tfsWIStore = new TFSWorkItemStoreWrapper(tfsServerUrl);
tfsWIStore.ReAssignWI(reqTP.Name, reqTP.ProjectAdmin);

This is done through the TFS WorkitemStore API. Executing a query, opening the work items, and changing the AssignedTo field. This is wrapped into the TFSWorkItemStoreWrapper class.


public void ReAssignWI(string teamProject, string newOwner)
{
WorkItemStore wiStore = new WorkItemStore(TFSServerUrl);

WorkItemCollection wiLst = wiStore.Query(
" SELECT [System.Id], [System.AssignedTo], [System.Title] " +
" FROM WorkItems " +
" WHERE [System.TeamProject] = '" + teamProject + "' ORDER BY [System.WorkItemType], [System.Id]");

foreach (WorkItem itm in wiLst)
{
itm.Open();
itm.Fields["System.AssignedTo"].Value = newOwner;
itm.Save();
}
}
We also need to apply standard check-in policies to our new teamproject. I've made a xml config file containing the list of standard policies to apply to all project.

//Setup standard team project Policies
StandardPoliciesXml stdPoliciesXml = new StandardPoliciesXml("stdTeamProjectPolicies.xml");
TFSPolicyWrapper tfsPolicyStore = new TFSPolicyWrapper(tfsServerUrl);
tfsPolicyStore.AddCheckinPolicies(reqTP.Name, stdPoliciesXml.GetTeamProjectPolicies());

The code for applying policies is based on a postby Jakob Ehn and wrapped into the TFSPolicyWrapper.


public void AddCheckinPolicies(string teamProject, List StdPolicyLst)
{
List policiesToApply = new List();

TeamFoundationServer tfs = new TeamFoundationServer(TFSServerUrl);
VersionControlServer srvVC = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
TeamProject tp = srvVC.GetTeamProject(teamProject);

policiesToApply.AddRange(tp.GetCheckinPolicies());

foreach (TpPolicy WantedPolicy in StdPolicyLst)
{
Assembly policyAssembly = Assembly.LoadFile(WantedPolicy.PolicyAssembly);
object o = policyAssembly.CreateInstance(WantedPolicy.PolicyType);

if (o is IPolicyDefinition)
{
IPolicyDefinition def = o as IPolicyDefinition;

PolicyEnvelope[] checkinPolicies = new PolicyEnvelope[1];
bool foundPolicy = false;
foreach (PolicyType policyType in Workstation.Current.InstalledPolicyTypes)
{
if (policyType.Name == def.Type)
{
policiesToApply.Add(new PolicyEnvelope(def, policyType));
foundPolicy = true;
}
}
if (!foundPolicy)
{
throw new ApplicationException(String.Format("The policy {0} is not registered on this machine", def.Type));
}
}
else
{
throw new ApplicationException(String.Format("Type {0} in assembly {1} does not implement the IPolicyDefinition interface", WantedPolicy.PolicyType, WantedPolicy.PolicyAssembly));
}
}

if (policiesToApply.Count > 0)
{
tp.SetCheckinPolicies(policiesToApply.ToArray());
}
tp = null;
srvVC = null;
tfs.Dispose();
tfs = null;
}

In order to apply a policy to a project we must be able to load the policy. This will require all policies to be installed on the machine executing the program.

The last thing to do is to mark the request for a new teamproject as finished. This is done by updating the list item.


//Update the item
spReqListWrapper.UpdateRequestForTeamProject(reqTP,projectCreatedCol);
}

5 comments:

  1. When I execute the File.BatchNewTeamProject command in 2010, it says command not found. How do we enable Team Explorer before running this command as a process??

    ReplyDelete
  2. In order to have the File.BatchNewTeamProject command running in vsts2010 beta1 the TeamExplorer tab must be active (then you closed VS last time) This is a feature/bug in Beta1 and I have reported it to Microsoft, hopefully it will be gone before the RTM.

    ReplyDelete
  3. Hello, I want to auto create team project without create a sharepoint, but i want to add project admin and contributor using an xml file. I'm using TFS 2010, do you have any idea to help. Paul

    ReplyDelete
  4. Hi paul - I think it would be possible- take a look at this thread - http://social.msdn.microsoft.com/Forums/en/tfspowertools/thread/984bd467-6ef5-4d3e-98d3-bf92d25cda2b
    And simply switch the configfile to false";

    ReplyDelete
  5. The code you referenced is no longer available in the current version (2.1).

    iTFSProxy.Connect(TFSServerUrl); is now

    void Connect(Uri collectionUri, ICredentials credentials);

    I believe I'm connecting ok. However, the below code throws a null exception.

    I believe it's because the TeamProjects collection in the iTFSProxy is null. So maybe I have to do something to fill that collection or maybe I'm not connecting properly.


    iTFSProxy.Connect(new Uri(collectionUrl), System.Net.CredentialCache.DefaultNetworkCredentials);
    iTFSProxy.SelectTeamProject(teamProjectName); this causes exception

    ReplyDelete