How to update a custom TFS field programmatically - tfs

We have a custom build process (not using MS Build) and during that process I am adding a "fake" build to the global builds list. The reason I am doing that is so that you can select the build for a given work item (found in build). We have a custom field, build included, which is intended to show which build that work item was fixed in. I am having trouble figuring out how to update this field programmatically. The idea is I will have a small app that does this that I will call during the build process, finding all work items since the last build, then updating the field for those work items. Any ideas?

Something like this should work for you:
public void UpdateTFSValue(string tfsServerUrl, string fieldToUpdate,
string valueToUpdateTo, int workItemID)
{
// Connect to the TFS Server
TfsTeamProjectCollection tfs = new TfsTeamProjectCollection(new Uri(tfsUri));
// Connect to the store of work items.
_store = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
// Grab the work item we want to update
WorkItem workItem = _store.GetWorkItem(workItemId);
// Open it up for editing. (Sometimes PartialOpen() works too and takes less time.)
workItem.Open();
// Update the field.
workItem.Fields[fieldToUpdate] = valueToUpdateTo;
// Save your changes. If there is a constraint on the field and your value does not
// meet it then this save will fail. (Throw an exception.) I leave that to you to
// deal with as you see fit.
workItem.Save();
}
An example of calling this would be:
UpdateTFSValue("http://tfs2010dev:8080/tfs", "Integration Build", "Build Name", 1234);
The variable fieldToUpdate should be the name of the field, not the refname (ie. Integration Build, not Microsoft.VSTS.Build.IntegrationBuild)
You could probably get away with using PartialOpen(), but I am not sure.
You will probably need to add Microsoft.TeamFoundation.Client to your project. (And maybe Microsoft.TeamFoundation.Common)

This has changed for TFS 2012, basicly you have to add workItem.Fields[fieldToUpdate].Value
Updated Version of what #Vaccano wrote.
public void UpdateTFSValue(string tfsServerUrl, string fieldToUpdate,
string valueToUpdateTo, int workItemID)
{
// Connect to the TFS Server
TfsTeamProjectCollection tfs = new TfsTeamProjectCollection(new Uri(tfsUri));
// Connect to the store of work items.
_store = (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
// Grab the work item we want to update
WorkItem workItem = _store.GetWorkItem(workItemId);
// Open it up for editing. (Sometimes PartialOpen() works too and takes less time.)
workItem.Open();
// Update the field.
workItem.Fields[fieldToUpdate].Value = valueToUpdateTo;
// Save your changes. If there is a constraint on the field and your value does not
// meet it then this save will fail. (Throw an exception.) I leave that to you to
// deal with as you see fit.
workItem.Save();
}

Related

C# to add test case or user story under a team in a project

I wanted to access TFS project using C# and create a test case. below is the project structure
URL : http://localhost:8080/tfs/test
Project : project1
Team Name : team1
Add test case or work item here
i am able to connect to project and add a work item over there, but how can i connect to a particular team in project ( in this case it is team1 ) and add a test case or work item
here is my sample code
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Uri collectionUri = (args.Length < 1) ?
new Uri("http://localhost:8080/tfs/test") : new Uri(args[0]);
System.Console.WriteLine(collectionUri);
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(collectionUri);
WorkItemStore workItemStore = tpc.GetService<WorkItemStore>();
Project teamProject = workItemStore.Projects["project1"];
WorkItemType workItemType = teamProject.WorkItemTypes["User Story"];
// Create the work item.
WorkItem userStory = new WorkItem(workItemType)
{
// The title is generally the only required field that doesn’t have a default value.
// You must set it, or you can’t save the work item. If you’re working with another
// type of work item, there may be other fields that you’ll have to set.
Title = "test through code",
Description =
"this is an automation user story genrated"
};
// Save the new user story.
userStory.Save();
}
}
}
We use the Classification fields (Area and Iteration) to define "teams" in TFS. You didn't specify how you define your "teams" but assuming you use the Classification fields you can set the Iteration field as follows:
WorkItem userStory = new WorkItem(workItemType)
{
// The title is generally the only required field that doesn’t have a default value.
// You must set it, or you can’t save the work item. If you’re working with another
// type of work item, there may be other fields that you’ll have to set.
Title = "test through code",
Description =
"this is an automation user story genrated",
IterationPath = FormatPath(commonservice.GetNode(newIterationPath).Path, "Iteration", "MyTeamProject");
};

TFS Workflow - Show Test Suites in Custom Editor

[Edit]
You can get the current build-def via the IServiceProvider. See this answer.
[/Edit]
I'm trying to add a custom editor for one of our TFS Xaml build workflows.
I started by following this excelent tutorial by Ewald Hofman.
Everything worked as expected, when I click on the [...] button for the workflow parameter the credential dialog is displayed.
What I want to do:
I want to display a selectable list/tree of test suites that are available in the TFS team project. Just like in the Default Labs Workflow:
Obviously, for this to work, I have to be able to communicate the the TFS over its .Net-API (Is it possible to do via SOAP/REST?).
But whenI started to alter the example to fit to my scenario, I naivly tried using the IServiceProvider to get ahold of an IBuildService instance.
public override object EditValue(ITypeDescriptorContext context, ServiceProvider provider, object value)
{
if (provider != null)
{
IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
IBuildServer bs = (IBuildServer)provider.GetService(typeof(IBuildServer));
...
This approach did not work, bs is null after this statement.
Then I tried to connect to the TestManagement like this:
var pr = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://tfs:8080/tfs/DefaultCollection/ProjectName"));
var TcmService = pr.GetService<ITestManagementService>();
var t = TcmService.GetTeamProject("ProjectName");
But this failed with a HTTP 404 error that is displayed when I click the edit [...] buttton of the workflow parameter. The url is correct, if i open the page in a browser, the Team Webaccess page is displayed.
TL;DR:
I would like to be able to display a list of test suites of the current tfs in a custom worfklow parameter editor, but I am failing to connect to the TFS.
Other approach:
This site (and some others) recommend using builtin editors. Is it possible to use the builtin editor window of the labs workflow paramaters (the one in the first screenshot)?
Thanks in advance.
Appearantly I was just stupid.
var pr = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://tfs:8080/tfs/DefaultCollection/ProjectName"));
The project name must not be in this URL. With the line below it works just fine.
var pr = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri("http://tfs:8080/tfs/DefaultCollection"));
See my followup question

How to force only one transaction within multiple DbContext classes?

Background:
From another question here at SO I have a Winforms solution (Finance) with many projects (fixed projects for the solution).
Now one of my customers asked me to "upgrade" the solution and add projects/modules that will come from another Winforms solution (HR).
I really don't want to keep these projects as fixed projects on the existing finance solution. For that I'm trying to create plugins that will load GUI, business logic and the data layer all using MEF.
Question:
I have a context (DbContext built to implment the Generic Repository Pattern) with a list of external contexts (loaded using MEF - these contexts represent the contexts from each plugin, also with the Generic Repository Pattern).
Let's say I have this:
public class MainContext : DbContext
{
public List<IPluginContext> ExternalContexts { get; set; }
// other stuff here
}
and
public class PluginContext_A : DbContext, IPluginContext
{ /* Items from this context */ }
public class PluginContext_B : DbContext, IPluginContext
{ /* Items from this context */ }
and within the MainContext class, already loaded, I have both external contexts (from plugins).
With that in mind, let's say I have a transaction that will impact both the MainContext and the PluginContext_B.
How to perform update/insert/delete on both contexts within one transaction (unity of work)?
Using the IUnityOfWork I can set the SaveChanges() for the last item but as far as I know I must have a single context for it to work as a single transaction.
There's a way using the MSDTC (TransactionScope) but this approach is terrible and I'd reather not use this at all (also because I need to enable MSDTC on clients and server and I've had crashes and leaks all the time).
Update:
Systems are using SQL 2008 R2. Never bellow.
If it's possible to use TransactionScope in a way that won't scale to MSDTC it's fine, but I've never achieved that. All the time I've used TransactionScope it goes into MSDTC. According to another post on SO, there are some cases where TS will not go into MSDTC: check here. But I'd really prefer to go into some other way instead of TransactionScope...
If you are using multiple contexts each using separate connection and you want to save data to those context in single transaction you must use TransactionScope with distributed transaction (MSDTC).
Your linked question is not that case because in that scenario first connection do not modify data so it can be closed prior to starting the connection where data are modified. In your case data are concurrently modified on multiple connection which requires two-phase commit and MSDTC.
You can try to solve it with sharing single connection among multiple contexts but that can be quite tricky. I'm not sure how reliable the following sample is but you can give it a try:
using (var connection = new SqlConnection(connnectionString))
{
var c1 = new Context(connection);
var c2 = new Context(connection);
c1.MyEntities.Add(new MyEntity() { Name = "A" });
c2.MyEntities.Add(new MyEntity() { Name = "B" });
connection.Open();
using (var scope = new TransactionScope())
{
// This is necessary because DbContext doesnt't contain necessary methods
ObjectContext obj1 = ((IObjectContextAdapter)c1).ObjectContext;
obj1.SaveChanges(SaveOptions.DetectChangesBeforeSave);
ObjectContext obj2 = ((IObjectContextAdapter)c2).ObjectContext;
obj2.SaveChanges(SaveOptions.DetectChangesBeforeSave);
scope.Complete();
// Only after successful commit of both save operations we can accept changes
// otherwise in rollback caused by second context the changes from the first
// context will be already accepted = lost
obj1.AcceptAllChanges();
obj2.AcceptAllChanges();
}
}
Context constructor is defined as:
public Context(DbConnection connection) : base(connection,false) { }
The sample itself worked for me but it has multiple problems:
First usage of contexts must be done with closed connection. That is the reason why I'm adding entities prior to opening the connection.
I rather open connection manually outside of the transaction but perhaps it is not needed.
Both save changes successfully run and Transaction.Current has empty distributed transaction Id so it should be still local.
The saving is much more complicated and you must use ObjectContext because DbContext doesn't have all necessary methods.
It doesn't have to work in every scenario. Even MSDN claims this:
Promotion of a transaction to a DTC may occur when a connection is
closed and reopened within a single transaction. Because the Entity
Framework opens and closes the connection automatically, you should
consider manually opening and closing the connection to avoid
transaction promotion.
The problem with DbContext API is that it closes and reopens connection even if you open it manually so it is a opened question if API always correctly identifies if it runs in the context of transaction and do not close connection.
#Ladislav Mrnka
You were right from the start: I have to use MSDTC.
I've tried multiple things here including the sample code I've provided.
I've tested it many times with changed hare and there but it won't work. The error goes deep into how EF and DbContext works and for that to change I'd finally find myself with my very own ORM tool. It's not the case.
I've also talked to a friend (MVP) that know a lot about EF too.
We have tested some other things here but it won't work the way I want it to. I'll end up with multiple isolated transactions (I was trying to get them together with my sample code) and with this approach I don't have any way to enforce a full rollback automatically and I'll have to create a lot of generic/custom code to manually rollback changes and here comes another question: what if this sort of rollback fails (it's not a rollback, just an update)?
So, the only way we found here is to use the MSDTC and build some tools to help debug/test if DTC is enabled, if client/server firewalls are ok and all that stuff.
Thanks anyway.
=)
So, any chance this has changed by October 19th? All over the intertubes, people suggest the following code, and it doesn't work:
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Open();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Open();
using (var transaction = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = IsolationLevel.ReadUncommitted, Timeout = TimeSpan.MaxValue}))
{
_contextA.SaveChanges();
_contextB.SaveChanges();
// Commit the transaction
transaction.Complete();
}
// Close open connections
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Close();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Close();
This is a serious drag for implementing a single Unit of Work class across repositories. Any new way around this?
To avoid using MSDTC (distributed transaction):
This should force you to use one connection within the transaction as well as just one transaction. It should throw an exception otherwise.
Note: At least EF6 is required
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
}
}
Source:
http://msdn.microsoft.com/en-us/data/dn456843.aspx#existing

How to get the project name when using TFS custom plugins

When we write C# code for a custom TFS plugin, we are capturing the check-in event. How can we retrieve the project name the user is checking in to?
The Team Project name is always the first portion of the path. For example, if a user checks in a file to:
$/ExampleProject/Folder/File.txt
Then the Team Project name is ExampleProject. The TFS SDK has a helper method that will allow you to get the Team Project name given a source control path:
string teamProjectName =
VersionControlPath.GetTeamProjectName("$/ExampleProject/Folder/File.txt");
Note that you can check into multiple team projects at the same time. For example, your pending changes can contain changes to:
$/Project1/Folder/File.txt
$/Project2/Folder/File.txt
In which case, the user is checking into both Project1 and Project2.
When capturing the Checkin-event using a plugin for TFS 2010 and you loop thru all the CheckinNotification properties (notificationEventArgs as CheckinNotification) I get.....
Changeset,
Comment,
ComputerName,
NotificationInfo,
Options,
CheckinNote,
PolicyOverrideInfo,
ChangesetOwnerName,
WorkspaceOwnerName,
WorkspaceName,
CheckinType,
SubmittedItems,
HasAllItems
The submitted items is a collection, just loop thru the collection...
string myitem="";
CheckinNotification data = notificationEventArgs as CheckinNotification;
if (data != null)
{
Type type = data.GetType();
PropertyInfo[] myproperties = type.GetProperties();
if (property.Name == "SubmittedItems")
{
foreach (var checkin in data.SubmittedItems)
myitem = checkin.ToString();
}
}
}
This will give you all the files that have been checked in. This is only partial code, you need to find the example from nielshebling.de titled TFS 2010: Using plugins to register an event

How to retrieve a list of Changesets (or work items) that were checked-in between builds?

I need a list of changesets (or Work Items) that were made betweend builds (I can label builds if its necessary).
I need that list for our test team (and to publish 'changelist').
Is MSBuild task able to retrieve that list and save as file (then I can process that list further.
Or maybe I need to connect to TFS from C# code and retrieve that list myself (I'm familiar with retrieving WorkItems in C#).
I know this thread is a couple of years old, but I found it when trying to accomplish the same thing.
I've been working on this for a couple of days now, and came up with a solution that accomplishes this specific task. (TFS 2010)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.Build.Client;
namespace BranchMergeHistoryTest
{
class Program
{
private static Uri tfsUri = new Uri("http://sctf:8080/tfs");
private static TfsTeamProjectCollection tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(tfsUri);
static void Main(string[] args)
{
IBuildServer buildServer = tfs.GetService<IBuildServer>();
IBuildDefinition buildDef = buildServer.GetBuildDefinition("Project", "Specific Build");
IOrderedEnumerable<IBuildDetail> builds = buildServer.QueryBuilds(buildDef).OrderByDescending(build => build.LastChangedOn);
/* I had to use some logic to find the last two builds that had actual changesets attached - we have some builds that don't have attached changesets. You may want to do the same. */
IBuildDetail newestBuild = builds.ElementAt(0);
IBuildDetail priorBuild = builds.ElementAt(1);
string newestBuildChangesetId = newestBuild.Information.GetNodesByType("AssociatedChangeset")[0].Fields["ChangesetId"];
string priorBuildChangesetId = priorBuild.Information.GetNodesByType("AssociatedChangeset")[0].Fields["ChangesetId"];
VersionControlServer vcs = tfs.GetService<VersionControlServer>();
const string sourceBranch = #"$SourceBranch-ProbablyHEAD";
const string targetBranch = #"$TargetBranch-ProbablyRelease";
VersionSpec versionFrom = VersionSpec.ParseSingleSpec(newestBuildChangesetId, null);
VersionSpec versionTo = VersionSpec.ParseSingleSpec(priorBuildChangesetId, null);
ChangesetMergeDetails results = vcs.QueryMergesWithDetails(sourceBranch, VersionSpec.Latest, 0, targetBranch,VersionSpec.Latest, 0, versionFrom, versionTo, RecursionType.Full);
foreach(Changeset change in results.Changesets)
{
Changeset details = vcs.GetChangeset(change.ChangesetId);
// extract info about the changeset
}
}
}
}
Hope this helps the next person trying to accomplish the task!
I know this is old post but I have been digging around for how to accomplish this for many hours and I thought someone else might benefit from what I have put together. I am working with TFS 2013 and this was compiled together from several different sources. I know I don't remember them all at this point but the main ones where:
Get Associated Changesets from Build
Queue a Team Build from another and pass parameters
What I was missing from most articles I found on this subject was how to take the build detail and load the associated changesets or work items. The InformationNodeConverters class was the missing key for this and allows you to get other items as well. Once I had this I was able to come up with the following code that is pretty simple.
Note that if you are running this from a post build powershell script you can use the TF_BUILD_BUILDURI variable. I have also included the code that I came up with to take the summary data retrieved and load the actual item.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
namespace Sample
{
class BuildSample
{
public void LoadBuildAssociatedDetails(Uri tpcUri, Uri buildUri)
{
TfsTeamProjectCollection collection = new TfsTeamProjectCollection(tpcUri);
IBuildServer buildServer = collection.GetService<IBuildServer>();
IBuildDetail buildDetail = buildServer.GetAllBuildDetails(buildUri);
List<IChangesetSummary> changeSets = InformationNodeConverters.GetAssociatedChangesets(buildDetail);
VersionControlServer vcs = collection.GetService<VersionControlServer>();
IEnumerable<Changeset> actualChangeSets = changeSets.Select(x => vcs.GetChangeset(x.ChangesetId));
List<IWorkItemSummary> workItems = InformationNodeConverters.GetAssociatedWorkItems(buildDetail);
WorkItemStore wis = collection.GetService<WorkItemStore>();
IEnumerable<WorkItem> actualWorkItems = workItems.Select(x => wis.GetWorkItem(x.WorkItemId));
}
}
}
TFS will automatically produce a list of all change sets and associated work items checked-in between two successful builds. You will find the lists at the end of the build report.
You could set up a build that is used to communicate with the testers. When that build is build successfully the testers could just look at the build report to see what work items and change sets has been committed since the last build.
If you set up an event listener for the build quality property of a build you could send an email alert to the testers when that builds quality filed changes to a specific version.
We have build labels for each build, they are the same as build number, which is the same as the product version number that our QA and Support operate on.
So, this works for us:
tf.exe history <BRANCH> /version:L<BUILD_NUMBER_FROM>~L<BUILD_NUMBER_TO> /recursive /collection:http://<our TFS server>
results look like this:
Changeset User Date Comment
--------- ----------------- ---------- ------------------------------------- ----------------
3722 Sergei Vorobiev 2013-11-16 Merge changeset 3721 from Main
3720 <redacted>
3719 <redacted>
This blog post may be what you are looking for. You basically go through all the links finding ones with a Uri containing 'changeset'. There doesn't seem to be a specific property for this.
Link
(copied from blog in case of rot)
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.VersionControl.Client;
class ChangesetsFromWorkItems
{
static void Main(string[] args)
{
if (args.Length < 2)
{
Console.Error.Write("Usage: ChangesetsFromWorkItems <server> <workitemid> [workitemid...]");
Environment.Exit(1);
}
TeamFoundationServer server = TeamFoundationServerFactory.GetServer(args[0]);
WorkItemStore wiStore = (WorkItemStore)server.GetService(typeof(WorkItemStore));
VersionControlServer vcs = (VersionControlServer) server.GetService(typeof(VersionControlServer));
int workItemId;
for (int i = 1; i < args.Length; i++)
{
if (!int.TryParse(args[i], out workItemId))
{
Console.Error.WriteLine("ignoring unparseable argument {0}", args[i]);
continue;
}
WorkItem workItem = wiStore.GetWorkItem(workItemId);
List<Changeset> associatedChangesets = new List<Changeset>();
foreach (Link link in workItem.Links)
{
ExternalLink extLink = link as ExternalLink;
if (extLink != null)
{
ArtifactId artifact = LinkingUtilities.DecodeUri(extLink.LinkedArtifactUri);
if (String.Equals(artifact.ArtifactType, "Changeset", StringComparison.Ordinal))
{
// Convert the artifact URI to Changeset object.
associatedChangesets.Add(vcs.ArtifactProvider.GetChangeset(new Uri(extLink.LinkedArtifactUri);
}
}
}
// Do something with the changesets. Changes property is an array, each Change
// has an Item object, each Item object has a path, download method, etc.
}
}
}
We do something similar in our TFS Build process. To do this we created a MSBuild custom task in C# that makes the call to TFS for the items. It is pretty straight forward to create the custom tasks.
Here is an article to get you started with writing MSBuild tasks. http://msdn.microsoft.com/en-us/library/t9883dzc.aspx
I assume you already know how to do the calls to TFS based on your question.
I posted a blog article on how to do this here: Getting a List of Changes After a Specified Build/Label from TFS 2013. It provides a quick and concise function for retrieving the list of files that have been changed since a given Build/Label.
Hope that helps!

Resources