September 2006 - Posts

Custom Build Numbers in Team Build

The Team Build service in Team Foundation Server includes the current date in the build number by default. To me, the string looks like the random gibberish of a disk head crash.

FooBarBuild_20060928.1
FooBarBuild_20060928.2

I know some people are fond of including the date in a build label, but it's a turn off for me. Build labels have a tendency to show up in many places, and a friendly number is easier on the eyes.

FooBar_2.5.1
FooBar_2.5.2

FooBar_2.5.176

Fortunately, it's easier to change Team Build with a custom MSBuild task. There are some examples of how to do this out there, but none that generate a truly friendly name. Ideally, the task will start with a base name like "FooBar_2.5" and just generate an extra identity digit. One of the properties in play during a team build is LastBuildNumber, which we can inspect during the task and use to generate the build number we want. The code would look something like this:

using System;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

namespace TfsBuildUtilities
{
  
public class GetBuildNumber : Task
   {
      
public override bool Execute()
      {
        
bool result = true;

        
try
         {
            ValidateProperties();

            
string buildNumber = GetLastBuildNumber();
            _buildNumber = BaseBuildName +
"." + buildNumber;
         }
        
catch (Exception e)
         {
            result =
false;

            BuildErrorEventArgs eventArgs;
            eventArgs =
new BuildErrorEventArgs
                (
                    
"",
                    
"",
                    BuildEngine.ProjectFileOfTaskNode,
                    BuildEngine.LineNumberOfTaskNode,
                    BuildEngine.ColumnNumberOfTaskNode,
                    0, 0,
                    
"GetBuildNumber failed: " + e.Message,
                    
"", ""
                );

            BuildEngine.LogErrorEvent(eventArgs);

            
throw;
         }

        
return result;
      }

      
private string GetLastBuildNumber()
      {
        
string buildNumber = null;

        
// if there is no last build, or it looks as if the
         // last build was not using our name,
         // we will reset to ".1";
         if (String.IsNullOrEmpty(LastBuildNumber) ||
            !LastBuildNumber.StartsWith(BaseBuildName))
         {
            buildNumber =
"1";
         }
        
// otherwise we need to parse out the last number and increment it
         else
         {
            
string[] parts = LastBuildNumber.Split('.');
            
int number = 1;
            
bool parseResult = Int32.TryParse(
                  parts[parts.Length - 1],
                  
out number
               );
            
            
if (parseResult)
            {
               number++;
               buildNumber = number.ToString();
            }
         }

        
if (String.IsNullOrEmpty(buildNumber))
         {
            
throw new InvalidOperationException(
                  
"Could not generate a valid build number"
               );
         }

        
return buildNumber;
      }

      
private void ValidateProperties()
      {
        
if (String.IsNullOrEmpty(BaseBuildName))
         {
            
throw new ArgumentException("BaseBuildName is null");
         }
      }

      [Output]
      
public string BuildNumber
      {
        
get { return _buildNumber; }
        
set { _buildNumber = value; }
      }
      
private string _buildNumber = String.Empty;

      [Required]
      
public string BaseBuildName
      {
        
get { return _baseBuildName; }
        
set { _baseBuildName = value; }
      }
      
private string _baseBuildName;

      
public string LastBuildNumber
      {
        
get { return _lastBuildNumber; }
        
set { _lastBuildNumber = value; }
      }
      
private string _lastBuildNumber;
   }
}

Then, register the task in TFSBuild.proj file. I like to deploy the assembly into the MSBuild extensions path to use from multiple projects.

<UsingTask
 TaskName="TfsBuildUtilities.GetBuildNumber"  
 AssemblyFile=
"$(MSBuildExtensionsPath)\MyDir\TfsBuildUtilities.dll"/>

Finally, in the same .proj file, override the build number target to spit a new build label into the system.

<Target Name = "BuildNumberOverrideTarget" >    
  <GetBuildNumber BaseBuildName=
"FooBar_2.5"
                  LastBuildNumber=
"$(LastBuildNumber)">
    <Output TaskParameter=
"BuildNumber"
             PropertyName=
"BuildNumber"/>      
  </GetBuildNumber>
</Target>

TFS is making the CM job easy…

posted by scott with 2 Comments

Removing Features Considered Harmful

I get the occasional earful from people who are upset with Microsoft. These rants might come electronically, or they might come in person. If I know anything about the particular problem the person is having, I might try to give them some perspective or background on why Microsoft made the software do whatever it is that is causing the person great pain and suffering. I might know a workaround, or I might not. I might make them happier, or I might not. I do try. C'est la vie.

Today, I am on the other side of the fence.

I updated ActiveSync from 3.something to 4.2 today, only to find they removed the ability to synch a device over the network.

Let me express my feelings about this decision by saying - it sucks. It sucks really large, rotten, ostrich eggs.

posted by scott with 3 Comments

What's Wrong With This Code (#6)

Joe Developer is working on a new application for a book publisher. Authors can publish zero or more books. Books can have zero or more authors.

Book and Author schema

Joe wrote a query to get a total count of all authors, and a total count of all books. Joe read on the Internet that the DISTINCT keyword is good to use in these scenarios.

SELECT DISTINCT
  COUNT(Authors.Id) AS TotalAuthors,
  COUNT(Books.Id) As TotalBooks
FROM
  Authors  
  FULL JOIN AuthorsBooks AB ON Authors.Id = AB.AuthorID
  FULL JOIN Books ON AB.BookID = Books.Id

The problem is - the numbers seem too high. What's wrong? Is it easy to fix?

posted by scott with 10 Comments

State Machines In Windows Workflow

A new article on OdeToCode: State Machines In Windows Workflow. State machines have been a powerful abstraction in software for many years. Using a state machine in Windows Workflow means we get all the tracking, persistence, and meta-data support the workflow runtume offers, which is quite a bonus.

The state machine designer

posted by scott with 10 Comments

When to set AspCompat

If we interop with a COM component from ASP.NET, we might need to use AspCompat="true" in the @ Page directive. The question is: when do we need AspCompat?

The answer is that we need AspCompat if the COM component has to run on a thread initialized into a single threaded apartment (STA). This begs the question: how do we know if a component needs to run in an STA?

One answer is to dig into the HKEY_CLASSES_ROOT section of the registry and find the component by CLSID (or by ProgID, which can give us the CLSID). Inside will be an InprocServer32 key, which holds a ThreadingModel value.

We could also find this value using the OLE/COM Object Viewer. Locate the component on the left, and look for the ThreadingModel setting on the right.

If the ThreadingModel value is "Apartment", we need to use AspCompat="true" (and look for ways to get rid of the component). If the value is "Free", "Neutral", or "Both", then we don't need AspCompat.

This post was brought to you by the Ministry of Boring Trivia, headquartered in a bar in the airport of Savannah, Georgia.

posted by scott with 3 Comments

Forum Quote Of The Day

From an ASP.NET discussion:   

"Real men don't write code, they regenerate it."

Use the comments to discuss the technological, grammatical, and sociological aspects of this statement.

posted by scott with 7 Comments

The Art of Escalation in Software Requirement Meetings

Maybe you've dealt with The Escalator before. The conversations with The Escalator go something like this:

"Now, what about this scenario? This is a very high-priority scenario for our users. We have to have this feature in the next release".

"We costed that scenario, and we don't have the time in this release".

"But this is a high priority scenario."

"Every feature in this iteration is high priority".

"You don't understand, our user's can't do their jobs without this feature. If we don't deliver this, the project will fail".

"But, it was never that important before…"

"Look – if we don't deliver this – I … will … lose … my … job".

<blank stares>

"Please! For the love of God! The mafia will kill my family!" <tears>

"Um … ok. We might be able to bump something and squeeze it in…"

"Great! Now, about this other scenario. This is a very high-priority scenario for our users. We have to have this feature in the next release…."

posted by scott with 8 Comments

Let's Get Threaded

Threading. It's the new frontier. At least, that's what all the cool articles say these days.

The premise is that we have these new fangled dual core chips, but our applications aren't taking advantage of all the horsepower available because we don't use enough threads. We need to use more threads, they say. We have to grab those chips by their cores and bend every transistor to the will of our application.

Imagine if your local government doubled the width of all the roads in your area, and car manufacturers followed up by doubling the size of all new cars. Would that be an improvement?

I'd argue that dual core CPUs allow hardware to finally catch up with today's common usage. I have twice as many processes running today as I did in the old days (when Moore's law allowed processes to keep up). I have streaming audio playing while I'm synching a smart phone and an MP3 player. I have a torrent client running at the same time my desktop search engine is indexing and my newsreader is aggregating. I have at least one virtual machine and Outlook running all the time, and in Vista, everyone will have desktop gadgetry doing whiz-bang animations and back flips in a sidebar.

The last thing we need in this scenario is some application to come along and crush all the background work with threads of its own. Most applications just need enough to keep the UI responsive and do a little work in between button clicks.

Share the processor, and your users will thank you.

posted by scott with 5 Comments

What's Wrong With This Code (#5)

Joe Developer is working on a bowling program (again). Joe wrote the following code.

using System;
using System.Collections.Generic;

[
Serializable]
class Bowlers
{
    
List<string> _bowlerList = new List<string>();

    
public void AddBowler(string name)
    {
        _bowlerList.Add(name);

        
EventHandler<BowlerAddedEventArgs> handler = BowlerAdded;
        
if (handler != null)
        {
            handler(
this, new BowlerAddedEventArgs(name));
        }
    }

    
public event EventHandler<BowlerAddedEventArgs> BowlerAdded;

    
// ...
}

[
Serializable]
class BowlerAddedEventArgs : EventArgs
{
    
public BowlerAddedEventArgs(string name)
    {
        Name = name;
    }

    
public string Name;
}

Joe unit tested the code to within an inch of its life, so he was surprised when another developer wrote the following program, which throws an exception.

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

class Test
{
    
public static void Main()
    {
        
Bowlers bowlers = new Bowlers();

        
string addedMessage = "Added bowler: {0}";
        bowlers.BowlerAdded +=
            
delegate(object sender, BowlerAddedEventArgs e)
            {
                
Console.WriteLine(addedMessage, e.Name);
            };

        bowlers.AddBowler(
"Bob");
        bowlers.AddBowler(
"Jan");
        bowlers.AddBowler(
"Ann");

        
using (MemoryStream stream = new MemoryStream())
        {
            
BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, bowlers);
        }
    }
}  

What's wrong?

Hint: the exception is a strange looking serialization exception.

posted by scott with 14 Comments

This Could Be a Team Foundation Server Kind of Week

A few weeks ago, the company I'm working with made an acquisition, thus becoming a slightly bigger, more distributed company. The acquisition started a chain of events that ultimately resulted in me pitching Team Foundation Server (TFS) to a group of executives and developers. The pitch was successful - at least I've been told the licenses can be purchased this week. It was an interesting (and sometimes frustrating) experience getting to this point….

Problems
The acquisition forced a reevaluation of existing tools and processes. With the exception of Visual Studio and SQL Server, all the tools used for product development are a hodgepodge of free software and custom-built applications. The build engine, for example, is a Windows service I wrote years ago, and before I heard of a tool called NAnt. Only two people on the planet are willing to write XML scripts for my build engine - which is one problem.

A solution would be to replace my build engine with NAnt, but that only solves one of the problems. Technologies like Sharepoint only get a team so far in tracking issues, projects, bugs, and customer requirements. I experimented with a trail version of TFS and decided its features, and the tight integration with Visual Studio, would be a great boon to the team's productivity.

I was also encouraged by TFS customizations I've seen. For example, take a look at Mitch Denny's "Yesterday's Weather" post.

The next step was to get a ballpark figure for licensing. I could float this figure around and see if the approach was tenable…

Pricing
If you ask Microsoft how to buy Team Foundation Server, they will tell you to contact an authorized reseller, or a Microsoft partner. Well, this company is a Microsoft partner. I logged into the partner website to find information, but came up empty, so I contacted the partner program directly. I would have had more success calling the psychic hotline and asking for my tarot reading, because the people I talked to in the partner program were clueless. The experience only furthered my belief that the partner program is the dimwitted step-cousin of the marketing department.

Still trying to get an approximate cost for the retooling, I started searching reseller websites.

I thought my head was going to explode.

It turned out there were too many permutations of SKUs and licenses to make this a job I wanted to do. I read the "Software Assurance" FAQ. I looked to see what "Microsoft Open License" was. All the while, I was also trying to judge if the price I was seeing was an upgrade, or a renewal, or included an MSDN subscription, or was actually the U.S. dollar figure that resembled the price we would pay. The pain of searching through item descriptions was excruciating, so I gave up.

In the end, I contacted a reseller directly (CDW, to be exact), and talked to a sales rep. The guy was fantastic, although it still took some time to figure out the exact part numbers.

The Demo
The quotes I received for licensing met a favorable response, but no one else had seen TFS, so they were still a little edgy. I took the TFS Virtual PC image available to MSDN subscribers and worked up a 30-minute presentation on Team System. The execs liked the reports, work item visibility, and integration with Excel and Project. Developers liked the features, too - like how a completed work item contains a link to its changeset.

There is still some concern over how much administration TFS will require. Another risk is the future of TFS, which is unclear to me. Will future versions hike up the price? Will there be backward compatibility? Despite these unknowns, I think TFS is a great choice for a relatively small company. Nothing else offers this range of features in this price range.

posted by scott with 4 Comments