7
votes

In TFS 2015 new build system, did the functionality to automatically add build number to Global List (Build - Project Name) upon build complete removed?

Do I need to write a custom PowerShell task to accomplish this?

Note: XAML builds still add build number to Global List as it did before.

2

2 Answers

4
votes

Since many features are still missing in the vNext build system, I've made a PowerShell script that do the Job. In a near futur, I plan to update this script to support IntegratedIn field filling and to convert the script as a custom build task.

[CmdletBinding(SupportsShouldProcess=$false)]
param()

function Update-GlobalListXml
{
    [CmdletBinding(SupportsShouldProcess=$false)]
    param(
         [xml]$globalListsDoc,
         [parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$glName,
         [parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$buildNumber
    )

    Write-Verbose "Checking whether '$glName' exists"
    $buildList = $globalListsDoc.GLOBALLISTS.GLOBALLIST | Where-Object { $_.name -eq $glName }
    if ($buildList -eq $null)
    {
        Write-Host "GlobalList '$glName' does not exist and will be created"
        $globalLists = $globalListsDoc.GLOBALLISTS
        if($globalLists.OuterXml -eq $null)
        {
            $newDoc = [xml]"<gl:GLOBALLISTS xmlns:gl="""http://schemas.microsoft.com/VisualStudio/2005/workitemtracking/globallists"""></gl:GLOBALLISTS>"
            $globalLists = $newDoc.GLOBALLISTS
        }
        $globalList = $globalLists.OwnerDocument.CreateElement("GLOBALLIST")
        $globalList.SetAttribute("name", $glName)
        $buildList = $globalLists.AppendChild($globalList)
    }
    if(($buildList.LISTITEM | where-object { $_.value -eq $buildNumber }) -ne $null)
    {
        throw "The LISTITEM value: '$buildNumber' already exists in the GLOBALLIST: '$glName'"
    }

    Write-Host "Adding '$buildNumber' as a new LISTITEM in '$glName'"
    $build = $buildList.OwnerDocument.CreateElement("LISTITEM")
    $build.SetAttribute("value", $buildNumber)
    $buildList.AppendChild($build) | out-null

    return $buildList.OwnerDocument
}

function Invoke-GlobalListAPI()
{
    [CmdletBinding(SupportsShouldProcess=$false)]
    param(
        [parameter(Mandatory=$true)][Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]$wiStore,
        [parameter(Mandatory=$true,ParameterSetName="Import")][switch]$import,
        [parameter(Mandatory=$true,ParameterSetName="Import")][xml]$globalLists,
        [parameter(ParameterSetName="Export")][switch]$export
    )

    try {
        if($import)
        {
            $wiStore.ImportGlobalLists($globalLists.OuterXml) # Account must be explicitly in the Project Administrator Group
        }
        if($export)
        {
            return [xml]$wiStore.ExportGlobalLists()
        }
    }
    catch [Microsoft.TeamFoundation.TeamFoundationServerException] {
        Write-Error "An error has occured while exporting or importing GlobalList"
        throw $_
    }
}

function Get-WorkItemStore()
{
    [CmdletBinding(SupportsShouldProcess=$false)]
    param(
        [parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$tpcUri,
        [parameter(Mandatory=$true)][string][ValidateNotNullOrEmpty()]$agentWorker
    )

    # Loads client API binaries from agent folder
    $clientDll = Join-Path $agentWorker "Microsoft.TeamFoundation.Client.dll"
    $wiTDll = Join-Path $agentWorker "Microsoft.TeamFoundation.WorkItemTracking.Client.dll"
    [System.Reflection.Assembly]::LoadFrom($clientDll) | Write-Verbose
    [System.Reflection.Assembly]::LoadFrom($wiTDll) | Write-Verbose

    try {
        Write-Host "Connecting to $tpcUri"
        $tfsTpc = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($tpcUri)
        return $tfsTpc.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore])
    }
    catch [Microsoft.TeamFoundation.TeamFoundationServerException] {
        Write-Error "An error has occured while retrieving WorkItemStore"
        throw $_
    }
}

function Get-WITDataStore64
{
    [CmdletBinding(SupportsShouldProcess=$false)]
    param()

    if($env:VS140COMNTOOLS -eq $null)
    {
        throw New-Object System.InvalidOperationException "Visual Studio 2015 must be installed on the build agent" # TODO: Change it by checking agent capabilities
    }

    $idePath = Join-Path (Split-Path -Parent $env:VS140COMNTOOLS) "IDE"
    return Get-ChildItem -Recurse -Path $idePath -Filter "Microsoft.WITDataStore64.dll" | Select-Object -First 1 -ExpandProperty FullName
}

function Update-GlobalList
{
    [CmdletBinding(SupportsShouldProcess=$false)]
    param()

    # Get environment variables
    $tpcUri = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI
    Write-Verbose "Team Project Collection Url: '$tpcUri'"
    $teamProjectName = $env:SYSTEM_TEAMPROJECT
    Write-Verbose "Team Project: '$teamProjectName'"
    $buildNumber = $env:BUILD_BUILDNUMBER
    Write-Verbose "Build Number: '$buildNumber'"
    $agentHome = $env:AGENT_HOMEDIRECTORY
    Write-Verbose "Agent home direrctory: '$agentHome'"
    $globalListName = "Builds - $teamProjectName"
    Write-Verbose "GlobalList name: '$teamProjectName'"

    # Copy 'Microsoft.WITDataStore64.dll' from Visual Studio directory to AgentBin directory if it does not exist
    $agentWorker = Join-Path $agentHome "agent\Worker"
    $targetPath = Join-Path $agentWorker "Microsoft.WITDataStore64.dll" # Only compatible with x64 process #TODO use constant instead
    if(-not (Test-Path $targetPath))
    {
        $wITDataStore64FilePath = Get-WITDataStore64
        Write-Host "Copying $wITDataStore64FilePath to $targetPath"
        Copy-Item $wITDataStore64FilePath $targetPath | Write-Verbose
    }

    $wiStore = Get-WorkItemStore -tpcUri $tpcUri -agentWorker $agentWorker

    # Retrive GLOBALLISTS
    $xmlDoc = Invoke-GlobalListAPI -export -wiStore $wiStore
    $gls2 = Update-GlobalListXml -globalListsDoc $xmlDoc -glName $globalListName -buildNumber $buildNumber

    Invoke-GlobalListAPI -import -globalLists $gls2 -wiStore $wiStore
}

Update-GlobalList

Here is the link of the Github repo, feedbacks are welcome => https://github.com/GregoryOtt/UpdateWiBuildNum/blob/master/Update-GlobalList.ps1

3
votes

[disclaimer - I work on the new build system]

That global list on the workitem is a mechanism that dated back to the original release of TFS. It's one that sort of worked in that day and age (days of nightly builds, pre-CI and CD agility). It's starts to fall apart and doesn't show as proper relationships in TFS. I worked on WIT at that time and we needed a queryable mechanism and that's what we had (blame me :)

So, when we started a new build system, we didn't want to rebuild things and repeat the same mistakes. We're trying to take an agile, incremental approach to a better build system.

In the next sprint (88), we are starting work on proper links between builds and workitems and the WIT team is also doing work to make them more first class. The first thing you'll see is a link on the WIT form and that should hopefully make QU1 as well (at least parts of it).

We realize this does leave a few gaps but we are working to close them (gated and label sources being two others) and hopefully in a better way for a better long term product.

As far as a workaround goes, it should be possible to automate via powershell and our clients but we don't have anything canned for others to use.