About the author

J Sawyer is a developer based in Houston, TX and loves to write code, especially ASP.NET and other web-related stuff.

He also loves to ride his Kawasaki Ninja.

But he doesn't code and ride at the same time.

Calendar

<<  March 2010  >>
MoTuWeThFrSaSu
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar

Exporting Work Items for a Project Template

February 3, 2010 4:33 PM

There comes a time in every TFS implementation where you need to create your own set of default project work items in your template. You can do this straight in the XML by editing workitems.xml in your process template’s Work Item Tracking folder. I said you could, not that you’d actually want to! You can also use the Process Editor to add them one at a time like you do with the Team Foundation client. Uggh. Wouldn’t it be better just to use Excel to put them into a scratch, temporary TF Project? I certainly think so; I can enter work items in Excel a lot faster than I can in the TF Client (or in the Process Editor).

I did some looking about thinking that someone must have solved this already and posted some code, open source, whatever. There was one result that looked promising but … the URL didn’t work (so I’m not posting it here). <Heavy Sigh>

Oh well, time to pop open Visual Studio and start coding …

It turned out that it was pretty easy, in fact. In the sample below, you take a raw work item query (in WIQL) and get the list of work items that you want to export with the columns that you also want to export. The order doesn’t matter. From there, you execute the query against the Work Item Store to get the list of work items. Loop over them and write to XML in the format that the project template uses.

It is, for the most part, really that simple. There are only 2 things that you really have to watch out for. First, the project name should be replaced with a marker for the target project name  ($$PROJECTNAME$$). Second, you need to make sure that the field is applicable to the work item type. This is something that you need to be especially aware of if you have multiple work item types in your default work item set. With all of that … I am proud to present to you … the actual code (exception handling removed for clarity … put yours in!)

public void Export(
    string serverName, string projectName, string filePath, string WIQL)
{
    TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(
                            serverName, 
                            new UICredentialsProvider() );
    tfs.Authenticate();
    WorkItemStore store = 
        (WorkItemStore)tfs.GetService(typeof(WorkItemStore));
    WorkItemCollection workItems = store.Query(WIQL);
    using (System.IO.Stream output = 
        System.IO.File.Open(filePath, 
            System.IO.FileMode.Create, 
            System.IO.FileAccess.ReadWrite))
    {
        using (XmlWriter writer = XmlWriter.Create(output))
        {
            writer.WriteStartDocument(true);
            writer.WriteStartElement("WORKITEMS");
            WriteWorkItems(projectName, workItems, writer);
            writer.Flush();
            writer.Close();
        }
        
    }
}

private static void WriteWorkItems(string projectName, 
    WorkItemCollection workItems, XmlWriter writer)
{
    for (int i = 0; i < workItems.Count; i++)
    {
        var workItem = workItems[0];
        writer.WriteStartElement("WI");
        writer.WriteAttributeString("type", workItem.Type.Name);
        foreach (FieldDefinition field in workItems.DisplayFields)
        {
            if (workItem.Fields.Contains(field.Name))
            {
                writer.WriteStartElement("FIELD");
                writer.WriteAttributeString("refname", field.ReferenceName);
                writer.WriteAttributeString("value", 
                    workItem.Fields[field.Name].Value.ToString().Replace(
                            projectName, "$$PROJECTNAME$$"));
                writer.WriteEndElement();
            }
        }
        writer.WriteEndElement();
    }
}
In the immortal words of Porky Pig … That’s all folks!

Tags:

TFS

Desktop.ini, Process Templates and TF30169

December 10, 2009 3:48 PM

I’ve been working on customizing a process template here. Nothing special there. I’m sure you know the process – get the template, tweak, adjust, tweak – upload to the (test) server – create a project and make sure that all looks OK. Well, while I was doing this today, I came across an mysterious error when creating the new project. Keep in mind that the template uploaded just fine … this was when selecting the template for a new project. Here’s the error:

Event Description: TF30169: The New Team Project Wizard was unable to download the process template My Process Template.

Exception Type: Microsoft.VisualStudio.Zip.ZipException

Exception Message: A file name specified within the zip file is invalid. It contains a desktop.ini file, which could be used to run authorized code.

A desktop.ini file? What on earth would that be doing in there? I suspect that this is a typo; I think they meant which could be used to run unauthorized code. Don’t get me wrong – this is a Very Good Thing. It shows that someone was thinking about the types of files in the templates and making sure that nothing harmful accidently got in there.

What happened, as you may suspect, is that there was a desktop.ini file in the folder for the process template. How it got there or why is a mystery but there it was. And, apparently, when the process template is uploaded, everything in that folder on down is also uploaded. You can see the contents of the process template as it is returned from the server in your local temp folder. If the project creation succeeds, it appears that this file is deleted so you will have to be (relatively) quick about it if you want to see it. The template will have a name like 345.tmp; a number + a .tmp extension. It’s a zip file (as the error message so nicely points out) so you can change the extension to .zip and open it up. When I did this – lo and behold – there certainly was a desktop.ini file in there. Deleting it from the template directory and re-uploading the file did, of course, solve the problem.

One lesson that I want to pass on from this – when you upload a new process template using the Process Template Manager, it uploads the entire contents of the process template folder. So make sure that you don’t have any extraneous stuff sitting in there (like desktop.ini, for example!).



Tags:

TFS

TF209001: NullReferenceException in TFBuild Logger with Custom Task

December 1, 2009 12:37 PM

This has been driving me absolutely nuts for … oh … a month or so (off and on). Here’s the scenario: I’ve got a custom task that access the Team Foundation Server to do some things before the build really gets kicking; the task is called in the BeforeEndToEndIteration target, which is one of the ones that are specified as extensible. So … nothing wrong there.

The custom task itself changed around the workspace settings for the build so that we could have 1 build for each branch of the code. Switching between the branches is done based on a property that is passed when kicking off the build (/p: switch). This allows one build definition for all of the branches, rather than having a separate build for each branch or manually changing the workspace mappings before kicking off the build. The task itself worked perfectly; the builds pulled and labeled the correct branch. Running the TFBuild standalone, debugging with MSBuild Sidekick as I previously detailed showed no problems and everything was fine. But, when running the build under full Team Build caused errors and the build itself was marked “Partially Succeeded”. Here’s what the build results looked like:

image

Searching for TF209001 gave me nada. Trying to figure out what was going on, I tracked the error to the Team Build logging component (in Microsoft.TeamFoundation.Build.Server.Logger.dll) and the error message was a generic one from a try{}catch{} block in the LogProjectStartedEvent. However, I couldn’t tell much else and talking with some of my friends at MSFT didn’t yield any information either.

So it bugged me. I knew that there was nothing wrong with my task … it ran just fine on its own. Even when the error happened, it did what it was supposed to do successfully and the build was actually fine. When the task was removed, everything with the logger was fine and there were no errors. But even when my task didn’t actually remap any of the workspace mappings, the logger ran into this error. It could only be some sort of mysterious interaction between the task and the TF Build logger.

Frustrated, I put this on the side burner for a while and just came back to it yesterday, determined to get to the bottom of this mystery and get everything working. Looking through everything in Reflector – something every .NET developer should have in their toolbox – didn’t show anything that really jumped out at me. The only thing that I could possibly guess was that, somehow, in the call to EnsureWorkspace(), the workspace wasn’t getting created. Checking the MSBuild properties WorkspaceName and WorkspaceOwner showed, however, that these were fine and being passed along correctly.

Baffled, I finally decided to fire up cordbg and get to the bottom of this. I knew that it was a (caught) NullReferenceException so I set the debugger to break on all NullReferenceExceptions.

(cordbg) ca e System.NullReferenceException

Next … fire up Task Manager so that I can grab the PID of the MSBuild process that Team Build used to run the build. Then kick off the build and attach to MSBuild. Finally, the debugger stopped on the exception:

First chance exception generated: (0x15e37fc) <System.NullReferenceException>
  _className=<null>
  _exceptionMethod=<null>
  _exceptionMethodString=<null>
  _message=(0x15e38e4) "Object reference not set to an instance of an object."
  _data=<null>
  _innerException=<null>
  _helpURL=<null>
  _stackTrace=(0x15e3960) <System.SByte[]>
  _stackTraceString=<null>
  _remoteStackTraceString=<null>
  _remoteStackIndex=0
  _dynamicMethods=<null>
  _HResult=-2147467261
  _source=<null>
  _xptrs=4572092
  _xcode=-1073741819
Exception is called:FIRST_CHANCE

Now we’re getting somewhere. Let’s see where we are. The where command will give us a stack trace.

(cordbg) w
Thread 0x2168 Current State:GCUnsafe spot
0)* Microsoft.TeamFoundation.Client.TeamFoundationServer::GetService +0055[native] +0014[IL] in <Unknown File Name>:<Unknown Line Number>
1)  Microsoft.TeamFoundation.Build.Server.Logger.BuildLoggerBase::get_VersionControlServer +0055[native] +0000[IL] in <Unknown File Name>:<Unknown Lin
e Number>
2)  Microsoft.TeamFoundation.Build.Server.Logger.BuildLogger::EnsureWorkspace +0317[native] +0000[IL] in <Unknown File Name>:<Unknown Line Number>
3)  Microsoft.TeamFoundation.Build.Server.Logger.BuildLogger::LogProjectStartedEvent +0093[native] +0047[IL] in <Unknown File Name>:<Unknown Line Numb
er>
4)  Microsoft.Build.BuildEngine.EventSource::RaiseProjectStartedEvent +0061[native] +0008[IL] in <Unknown File Name>:<Unknown Line Number>
5)  Microsoft.Build.BuildEngine.EventSource::RaiseStronglyTypedEvent +0390[native] +0131[IL] in <Unknown File Name>:<Unknown Line Number>
6)  Microsoft.Build.BuildEngine.EngineLoggingServicesInProc::ProcessPostedLoggingEvents +0213[native] +0180[IL] in <Unknown File Name>:<Unknown Line N
umber>
7)  Microsoft.Build.BuildEngine.Project::Load +1608[native] +0845[IL] in <Unknown File Name>:<Unknown Line Number>
8)  Microsoft.Build.BuildEngine.SolutionWrapperProject::ScanProjectDependencies +0319[native] +0114[IL] in <Unknown File Name>:<Unknown Line Number>
9)  Microsoft.Build.BuildEngine.SolutionWrapperProject::CreateSolutionProject +0208[native] +0098[IL] in <Unknown File Name>:<Unknown Line Number>

We see the entire stack from MSBuild all the way into the logger. It turns out that the exception is happening in the GetService() method of Microsoft.TeamFoundation.Client.TeamFoundationServer. The stack trace doesn’t show use the actual source lines because we don’t have a pdb for the assembly, but it does show the IL line number, which will get us to what’s going on in ILDASM. Here’s what we find there:

IL_000f:  ldfld      class [System]System.ComponentModel.Design.ServiceContainer Microsoft.TeamFoundation.Client.TeamFoundationServer::m_serviceContainer
  IL_0014:  ldarg.1

In Reflector, this maps to:

object service = this.m_serviceContainer.GetService(serviceType);

If GetService() returns null, it’s no problem; that’s handled. But if m_serviceContainer is null … that isn’t handled and will throw an exception as soon as it tries to call GetService(). Now we’ve identified where the exception is coming from. But we still don’t know why. Investigating further, m_serviceContainer is created in the constructor of the TeamFoundationServer class, which is working just fine or we wouldn’t have even gotten this far. So how does m_serviceContainer get set to null? It turns out that it’s set to null in TeamFoundationServer::Dispose(). And only there. Hmmm … how is the TeamFoundationServer object getting disposed?

It turns out that I was disposing the TeamFoundationServer class. How did this affect the logger? Simple … both components were calling TeamFoundationServerFactory.GetServer() to return an instance of the TeamFoundationServer class. When I called this method, I wrapped the TeamFoundationServer class in a using{} block (it implements IDisposable) like a good little .NET doobie. A look into GetServer(), however, shows that it creates a cache (using a Dictionary) with a single instance of the TeamFoundationServer for each unique TFS URI. It’s  static and handed to all clients that request the same URI; this single instance lives the entire lifetime of the process and is shared around. (Considering how long it can take to initialize the session context, this isn’t a bad thing.) However, because of this, when I called Dispose(), it was on the same instance that the distributed logger was using! (BTW … this is not documented anywhere that I could find!)

The solution – somewhat counter intuitively – is to not dispose the disposable TeamFoundationServer object when you are done with it. (Don’t make a habit of it though, OK?) Once I took out the using{} block, all was well and the build ran without any hitch at all. This is only applicable to instances that are retrieved from TeamFoundationServerProxy.GetServer(); if you create the instance using new, then you should make sure that you dispose it when you are done.

This smells all kinds of wrong to me. I don’t know what the official stance would be on this, but it smells suspiciously like a bug to me. The first thing is that the TeamFoundationServer class isn’t checking to see if it is disposed before it goes about its happy, merry way, contrary to what I thought was the recommended design pattern for implementing IDisposable; this includes checking to see if the class is disposed before doing any work in a method … and throwing an ObjectDisposedException when a method is called after being disposed. If you don’t do this, then be smart enough to realize when you’ve been previous disposed (somewhere) and reinitalize when necessary (ewww … no, that’s just wrong. Don’t do that.)

So there it is, the end to the mystery.



Tags:

.NET Stuff | TFS

Debugging TFS Build

November 6, 2009 10:04 AM

If nothing else, this is one of the coolest things in TFS 2010. But I am not, unfortunately, afforded the luxury at this time of working with TFS 2010 in my current gig. Though I won’t complain too loudly as it is 2008 and not 2005.

I’m currently working on the build process. As simple as that may sound (and some builds are simple), I do have some pretty high goals. Let me explain. The Team Project is a collection of solutions and projects that are certainly related and tied together in a single software project, but there are 10-15 of them. We have, in Source Control, branches for each release environment and thus, will need to have builds for each environment as well. My ideal scenario would be to pass the environment into TFS Build as a command line argument and have it pull the project from the correct branch, label the project from the correct branch and then create the drop directory with the correct name from the correct branch. And then zip it as well, but that getting that to have the correct branch/environment name is pretty easy as it’s a task that I’m adding in, not changing the behavior of Team Build.

While there is a build debugger in TFS 2010, such a beastie does not come out-of-the-box with TFS. There is, however, MSBuild Sidekick out there that lets you edit and debug MSBuild projects. Since TFS Build projects are MSBuild projects, I figured that this would be a good place to start. So I downloaded the trial with high hopes. I started with simply opening the MSBuild project that TF Build creates when you kick off a build. You’ll find this in the BuildType folder that’s right under the working directory for the Build Agent. It defaults to the Temp folder, but I like to put it in it’s own spot. On the Build Server that I’m working on, it’s F:\Build so you will find your TFSBuild.proj file in F:\Build\BuildType. Simple enough, right? No. When you fire up the debugger, it starts with CanonicalizePaths, then BeforeCompile and then CallCompile. And there it fails.

Digging in a bit, you’ll see a second file in the folder with TFSBuild.proj – TFSBuild.rsp. Hmmm … interesting. Upon opening that up in Notepad, you’ll find a list of stuff that looks remarkably like a series of command-line switches. Guess what? That’s exactly what they are. These are all of the command-line arguments that are passed into MSBuild when the build is kicked off. There are a slew of properties, some loggers and a couple of other things – like the initial target for the build. That’s a little important. Next step – add these into MSBuild Sidekick. You do this in the Properties screen for the build project. First, under project properties, you need to add in ALL of the properties that TFBuild is passing in. Then you have to specify the initial target for MSBuild. They’ll look like this:

Build Properties Initial Target

When you run it now, it hits CanonicalizePaths and then jumps right to the end and happily informs you that “Build succeeded”. Oh boy. What’s up with this? As it turns out, the first target in the chain for TF Build is “CheckSettingsForEndToEndIteration”. There’s a condition on this - $(IsDesktopBuild)'!='true'. So, let’s now add a property to the command line in our build options: IsDesktopBuild with a value of False. And … here we go again. We get past “CheckSettingsForEndToEndIteration” and go into the “InitializeBuildProperties” target, which calls the GetBuildProperties task. Which then promptly fails with the message:

C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets(267,5): error MSB4018: The "GetBuildProperties" task failed unexpectedly.
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets(267,5): error MSB4018: Microsoft.TeamFoundation.Build.Client.BuildNotFoundForUriException: TF214007: No build was found with the URI vstfs:///Build/Build/34. Either the URI does not exist, or TFSTEST\Administrator does not have permission to access it.
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets(267,5): error MSB4018:    at Microsoft.TeamFoundation.Build.Client.BuildServer.GetBuild(Uri buildUri, String[] informationTypes, QueryOptions queryOptions)
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets(267,5): error MSB4018:    at Microsoft.TeamFoundation.Build.Tasks.Task.get_BuildDetail()
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets(267,5): error MSB4018:    at Microsoft.TeamFoundation.Build.Tasks.GetBuildProperties.get_BuildAgentName()
C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets(271,15): error MSB4028: The "GetBuildProperties" task's outputs could not be retrieved from the "BuildAgentName" parameter. TF214007: No build was found with the URI vstfs:///Build/Build/34. Either the URI does not exist, or TFSTEST\Administrator does not have permission to access it.

And that, my friends, is where I am now. BUT … at least MSBuild Sidekick allowed be to look at all of the properties as they are when it starts up. And it’s also shown me what the entire process is that TF Build actually goes through. I haven’t done much editing with MSBuild Sidekick (yet) but the debugging … even if I cannot get TFBuild to cooperate fully … is priceless.

UPDATE:

I’ve gotten it working from beginning to end, soup to nuts. :-) Here’s what I did: First, I logged in as the user that the Build Server runs under (don’t lecture me about security; it’s a test environment). I did this because I remember seeing an error message when I tried to change the Build Server’s account about access to some URL or another (I don’t remember the details … I just switched back to the previous account). That still didn’t work. Therefore, I knew that it wasn’t security permissions; that account would need those permissions of Team Foundation Build was going to work at all. So now I’m looking at “No Build Found”. Time to fire up Sql Server Management Studio to see what’s in the TfsBuild database. When I opened up the tbl_Build table (what’s with the “tbl” prefix??? That’s sooo pre-Y2K!), I noticed that there was no build number 34 (look at the URI … it’s in there). BUT … there was a 33 and a 35. Changing the BuildUri property in the BuildOptions (above) did the trick and I was happily stepping through the entire TFS Build process.



Tags:

TFS

TFS & Reporting Services Installation Fun

October 21, 2009 4:02 PM

Maybe it’s me … maybe SSRS just doesn’t like me. But that has always seemed to be the most difficult piece of the whole Team Foundation Server integrated solution. But, now that I have that out of the way … I had challenges installing TFS into the production environment. Yes, I read the latest installation guide. I print out a checklist and go over it twice. I am well aware (from many bouts of swearing) that it is very important to make sure that you have everything in a row when installing TFS. Granted, it’s not as bad with 2008 as it was with 2005 (which was an improvement over the betas!!!), but it’s still something that you need to do.

Everything is checked. All the prereqs are installed. Did have one warning with the system scan though – it said that the processor on the app tier server wasn’t up to the recommended clock speed. Which was true. But there wasn’t just one processor … it has 4 Intel Xeon hyperthreaded processors in there! So I did ignore that.

Heading off to the races, we’re getting into the final stages of the TFS install … it’s installing Reporting Services. And it’s failing. Is it a security thing? Go check permissions for the account, make sure all is kosher there (it was). Still no go. What gives?

As it turned out, the backend Sql Server already had a default instance of Reporting Services registered to it (i.e. databases, etc) and I had not used an instance name for the TFS installation of reporting services on the app tier. Of course you have to have different instance names on the same box, but you also have to have different instance names from any Reporting Services instance that is registered/using your Data Tier server! I looked in the docs again and again … didn’t see it there. So, word to the wise: when installing Reporting Services for TFS on your App Tier server, check to make sure that there aren’t any instances of Reporting Services associated with your Data Tier server! If so, specify an instance name!



Tags: , ,

TFS