TFS 2012 – IBuildDetail.RequestedFor is not what it used to be!

If you have custom build tasks or activities that rely on “build.RequestedFor” (or “build.RequestedBy”) being the account name, you may have some problems when converting those customizations over to TFS 2012 Build.

In TFS 2012, we have completely switched over to Display names in the UI. To make this work consistent everywhere, we repurposed the IBuildDetail fields to show Display names (“John Smith”) instead of account names (“domain\jsmith”). Unfortunately, some customizations need the account name which is unique (the display name is not necessarily unique).

Luckily, it is possible to get this information…

Custom Activities or XAML

In a custom activity (or in the XAML file), you simply need to access the IBuildDetail.Requests[0].RequestedFor field. A build request is really an IQueuedBuild. IQueuedBuild now has RequestedFor, RequestedBy, RequestedForDisplayName, and RequestedByDisplayName. It is also possible to get to the build request from the IBuildDetail object by using the new IBuildDetail.Requests property.

NOTE: It is possible for the Requests property to be empty or to contain more than one item. It may be empty if no build request was associated with the build or if the request was somehow deleted. This situation is rare, but should be accounted for. If you use the Gated check in feature and choose to batch your builds, you can end up with multiple requests per build. This is the only reason you should have more than one request in the Requests property.

Here is a code snippet of how to get this new information:

XAML example:

     <mtbwa:WriteBuildMessage Message="[BuildDetail.Requests(0).RequestedFor]" Importance="High" />

CodeActivity example:

     public sealed class GetRequestedFor: CodeActivity<String>
    {
        public InArgument<IBuildDetail> Build { get; set; }

        protected override String Execute(CodeActivityContext context)
        {
            // Obtain the runtime value of the Text input argument
            string requestedFor = context.GetValue(this.Build).Requests[0].RequestedFor;
            return requestedFor;
        }
    }

Custom Tasks or Targets

Unfortunately, the $(RequestedFor) property has changed in the same way as described above for builds that use the UpgradeTemplate. The fix is basically the same – you need to get to the Request object and get the information from it. However, this requires a custom MSBuild Task, since you can’t get to the properties of the IBuildDetail object directly in MSBuild.

Using an inline Task is perfect for this. Here is an inline Task that you can include in your targets or proj files that will update $(RequestedFor) to the correct value.

In this example, I put this in my TfsBuild.proj file (at the bottom just before the “</Project>” end element.

 <UsingTask
    TaskName="GetRequestedFor"
    TaskFactory="CodeTaskFactory"
    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
    <ParameterGroup>
      <TeamFoundationServerUrl Required="True"/>
      <BuildUri Required="True"/>
      <RequestedFor Output="True"/>
    </ParameterGroup>
    <Task>
      <Reference Include="mscorlib" />
      <Reference Include="Microsoft.TeamFoundation.Build.Client" />
      <Reference Include="Microsoft.TeamFoundation.Build.Common" />
      <Reference Include="Microsoft.TeamFoundation.Client" />
 
      <Using Namespace="System" />
      <Using Namespace="Microsoft.TeamFoundation.Build.Client" />
      <Using Namespace="Microsoft.TeamFoundation.Build.Common" />
      <Using Namespace="Microsoft.TeamFoundation.Client" />
 
      <Code Type="Fragment" Language="cs">
 
        <![CDATA[
           RequestedFor = String.Empty;
           if (!String.IsNullOrEmpty(TeamFoundationServerUrl))
           {
               Uri serverUri = TfsTeamProjectCollection.GetFullyQualifiedUriForName(TeamFoundationServerUrl);
               // Attempt authentication early. On failure, this is where a good error message will be logged
               TfsTeamProjectCollection collection = new TfsTeamProjectCollection(serverUri);
               collection.Authenticate();             
 
               IBuildServer buildServer = (IBuildServer)collection.GetService(typeof(IBuildServer));
               IBuildDetail buildDetail = buildServer.GetBuild(new Uri(BuildUri));
               
               String requestedFor = buildDetail.RequestedFor;
               if (buildDetail.Requests != null && buildDetail.Requests.Count > 0)
               {
                   requestedFor = buildDetail.Requests[0].RequestedFor;
               }


               // Assign the value to the output parameter               
               RequestedFor = requestedFor;
           }
        ]]>
 
      </Code>
    </Task>
  </UsingTask>


  <Target Name="BeforeGet">
    <GetRequestedFor TeamFoundationServerUrl="$(TeamFoundationServerUrl)" BuildUri="$(BuildUri)">
      <Output PropertyName="RequestedFor" TaskParameter="RequestedFor" />
    </GetRequestedFor>
    <Message Text="RequestedFor = $(RequestedFor)" />
  </Target>

I am calling the task in the BeforeGet target as an example. It could be used where ever you need it as long as you have the TFS Url and BuildUri.

Happy Building!