Performing the same operation against nearly every collection

Let's say you have an action you want to perform against nearly every collection on your TFS instance. For example, maybe you just fixed an issue with your mail server and want to force the "Team Foundation Server Event Processing" job for each collection to run soon. (By default it runs every 30 minutes if there are no client actions like checkins triggering it.)

Below are Powershell examples you can customize. When you are operating on several collections you should be prepared for some to be offline, unless you know otherwise. I use trap below to handle this expected condition.

Which example do you want to use? If you're an instance-level TFS administrator and you're performing an administrative action, use the ITeamProjectCollectionService. Otherwise, use the catalog. Taylor Lafrinere covers this in more detail in one of his blog posts. I'm mainly converting his C# examples into Powershell examples here.

Why "nearly" every collection? See my caveats below.

Using the ITeamProjectCollectionService.

 

Three caveats:

  • You have to be highly privileged to get a list of team project collections using the ITeamProjectCollectionService. Otherwise you will see "Access Denied: MYDOMAIN\jdoe needs the following permission(s) to perform this action: Edit instance-level information".
  • This only operates against started collections. If an administrator has stopped a collection, you can't operate against it using the client OM.
  • This only operates against existing collections. If you're adding something custom to each collection like a new job definition, you may want to rerun this code every time a new collection is created or attached.

 

Notes:

  • Rather than trapping TeamFoundationServiceUnavailableException we could use if ($_.State -eq "Started") { … }. I use trap here to mirror the code below.
  • I've highlighted the code you'll want to customize.

 

Code:

[Reflection.Assembly]::Load("Microsoft.TeamFoundation.Client, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")

 

#Update configuration URL as necessary

$configServer = new-object Microsoft.TeamFoundation.Client.TfsConfigurationServer "http://localhost:8080/tfs/"

 

$collectionService = $configServer.GetService([Microsoft.TeamFoundation.Framework.Client.ITeamProjectCollectionService])

 

$collectionService.GetCollections() | %{

        $collectionName = $_.Name

        $collectionId = $_.Id

 

        trap [Microsoft.TeamFoundation.TeamFoundationServiceUnavailableException]

        {

           write-warning ("Skipping project collection "+ $collectionName +" ("+ $collectionId +"): "+ $_.Exception.Message)

           continue

        }

        &{

           $projectCollection = $configServer.GetTeamProjectCollection($collectionId)

 

           # Check that we can access the team project collection.

           $projectCollection.Connect("None")

 

           write-host

           write-host ("* Operating on project collection "+ $collectionName +" ("+ $collectionId +").")

 

           # Now do your per-collection action.

           # For example, queue the "Job History Cleanup Job" for this collection

           # $jobService = $projectCollection.GetService([Microsoft.TeamFoundation.Framework.Client.ITeamFoundationJobService])

           # $jobService.QueueDelayedJob('8fc76967-da5d-4d53-937b-e3efc6af0fcb')      

 

           # Your code here.

        }

}

 

Using the TFS Catalog:

 

Three caveats:

 

  • This only operates on collections you're allowed to see. ITeamProjectCollectionService.GetCollections() requires high-privileges and returns every collection. The catalog requires normal privileges and may only return some of the collections in the system.
  • Like the example above, this only operates against started collections. If an administrator has stopped a collection, you can't operate against it using the client OM.
  • Like the example above, this only operates against existing collections. If you're adding something custom to each collection like a new job definition, you may want to rerun this code every time a new collection is created or attached.

 

Notes:

  • I've highlighted the code you'll want to customize.

 

Code:

[Reflection.Assembly]::Load("Microsoft.TeamFoundation.Client, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")

 

#Update configuration URL as necessary

$configServer = new-object Microsoft.TeamFoundation.Client.TfsConfigurationServer "http://localhost:8080/tfs/"

 

$configServerNode = $configServer.CatalogNode;

 

# Query the children of the configuration server node for all of the team project collection nodes

$tpcNodes = $configServerNode.QueryChildren(

        [Guid[]] @( [Microsoft.TeamFoundation.Framework.Common.CatalogResourceTypes]::ProjectCollection ),

        $false,

        "None");

 

$tpcNodes | %{ $_.Resource } | %{

        $collectionName = $_.DisplayName

        $collectionId = $_.Properties["InstanceId"]

 

        trap [Microsoft.TeamFoundation.TeamFoundationServiceUnavailableException]

        {

           write-warning ("Skipping project collection "+ $collectionName +" ("+ $collectionId +"): "+ $_.Exception.Message)

           continue

        }

        &{

           $projectCollection = $configServer.GetTeamProjectCollection($collectionId)

 

           # Check that we can access the team project collection.

           $projectCollection.Connect("None")

 

           write-host

           write-host ("* Operating on project collection "+ $collectionName +" ("+ $collectionId +").")

 

           # Now do your per-collection action.

           # For example, queue the "Job History Cleanup Job" for this collection

           # $jobService = $projectCollection.GetService([Microsoft.TeamFoundation.Framework.Client.ITeamFoundationJobService])

           # $jobService.QueueDelayedJob('8fc76967-da5d-4d53-937b-e3efc6af0fcb')

       

           # Your code here.

        }

}