We implemented a custom VSTS task to do this. It works swimmingly. Here is the code.
If creating a custom VSTS task isn't your thing - you can strip out the PowerShell. Just have to pass the values as parameters instead.
file structure
/ Project-Folder
- extension-icon.png
- extension-manifest.json
/ Tasks
/ KeepBuildSynchronous
- icon.png
- task.json
- task.ps1
extension-manifest.json
{
"manifestVersion": 1,
"id": "your-task-name",
"name": "your-package-name",
"version": "0.0.1",
"publisher": "your-publisher-account",
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"description": "your-description",
"categories": [
"Build and release"
],
"icons": {
"default": "extension-icon.png"
},
"files": [
{
"path": "Tasks/KeepBuildSynchronous"
}
],
"contributions": [
{
"id": "12345678-0000-0000-0000-000000000000", <-- must match task.json id
"type": "ms.vss-distributed-task.task",
"targets": [
"ms.vss-distributed-task.tasks"
],
"properties": {
"name": "Tasks/KeepBuildSynchronous"
}
}
]
}
task.json
{
"id": "12345678-0000-0000-0000-000000000000",
"name": "keepBuildSynchronous",
"friendlyName": "Keep Build Synchronous",
"description": "Only allow one build with the same build definition to run at a time",
"helpMarkDown": "",
"category": "Utility",
"visibility": [
"Build"
],
"runsOn": [
"Agent",
"DeploymentGroup"
],
"author": "-your team-",
"version": {
"Major": 1,
"Minor": 1,
"Patch": 1
},
"releaseNotes": "Initial release",
"minimumAgentVersion": "1.91.0",
"inputs": [
{
"name": "waitTimeinMinutes",
"type": "int",
"label": "How many minutes do you want to wait before cancelling the build",
"defaultValue": "15",
"required": true,
"helpMarkDown": ""
}
],
"instanceNameFormat": "Keep Build Synchronous",
"execution": {
"PowerShell3": {
"target": "task.ps1"
}
}
}
task.ps1
[CmdletBinding()]
Param ()
Trace-VstsEnteringInvocation $MyInvocation
try {
Import-VstsLocStrings "$PSScriptRootTask.json"
# Get agent/build variables
[string]$teamFoundationCollectionUri = Get-VstsTaskVariable -Name "system.teamFoundationCollectionUri"
[string]$teamProjectId = Get-VstsTaskVariable -Name "system.teamProjectId"
[string]$buildId = Get-VstsTaskVariable -Name "build.buildId"
[int]$definitionId = Get-VstsTaskVariable -Name "system.definitionId" -AsInt
[string]$accessToken = Get-VstsTaskVariable -Name "system.accessToken"
# Get task inputs
[int]$waitTimeinMinutes = Get-VstsInput -Name waitTimeinMinutes -AsInt
[Object[]]$global:buildsByDefinition = $null
[Object[]]$global:runningBuilds = $null
function Get-Builds-By-Definition([int]$definitionId) {
$url = "$teamFoundationCollectionUri/$teamProjectId/_apis/build/builds?api-version=2.0&definitions=$definitionId"
$type = "application/json"
$headers = @{
Authorization = "Bearer $accessToken"
}
$global:buildsByDefinition = (Invoke-RestMethod -Uri $url -ContentType $type -Method Get -Headers $headers).value
$global:runningBuilds = $buildsByDefinition | Where-Object -Property "status" -Value "inProgress" -EQ
}
[datetime]$startedAt = Get-Date
[datetime]$waitUntil = $startedAt.AddMinutes($waitTimeinMinutes)
Get-Builds-By-Definition -definitionId $definitionId
[string]$buildDefinitionName = $buildsByDefinition[0].definition.name
Write-Host ""
Write-Host "Build definition ..... $buildDefinitionName"
Write-Host "Current build ........ $buildId"
Write-Host ""
Write-Host "Started at ........... $startedAt"
Write-Host "Willing to wait until $waitUntil"
Write-Host ""
while (1 -eq 1) {
if ((Get-Date) -gt $waitUntil) {
Write-Host "Waited too long (cancelling)"
throw "Waited longer than $waitTime minutes to start. Cancelling."
}
if ($global:runningBuilds -eq $null) {
Write-Host "No build running (weird, but ok...)"
break
}
[int]$firstToGo = ($global:runningBuilds | Sort-Object -Property "Id")[0].Id
if ($global:runningBuilds.Count -le 1 -or $buildId -le $firstToGo) {
Write-Host "Your turn to shine"
break
} else {
Write-Host "$($global:runningBuilds.Count) builds running. $firstToGo is next. (checking again)"
Start-Sleep -Seconds 15
}
Get-Builds-By-Definition -definitionId $definitionId
}
}
finally {
Trace-VstsLeavingInvocation $MyInvocation
}
create the .vsix file
tfx extension create --manifest-globs extension-manifest.json --rev-version
I'll leave a little mystery and let you find out how to publish this to the marketplace and get it installed on your build agents