Triggering a Build In Visual Studio Online and Check Status
A few months back, Andrew Tarr showed us how to trigger a build from another build using PowerShell. That worked pretty well for us and remains the recommended way to have a chain of builds in Visual Studio Online. However, recently I was working on a project that needed to trigger a build but also get the status and report it back to the main build. The main build would then fail if the triggered build failed. It turns out that adding that functionality on top of the original build trigger Powershell script wasn’t too hard.
One important note about this setup is that you must have two different build agents, one to execute the main build and a second build agent for the triggered build. Otherwise, you will end up with a “Build Deadlock”, the triggered build queued up but not started and the main build waiting for it to complete.
The start of the process to trigger the build is the same as before:
$triggerUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds?api-version=2.0" $body = "{ 'definition' : { 'id' : $env:IOSBUILD_DEFINITIONID }, 'sourceBranch' : '$env:BUILD_SOURCEBRANCH' }" $type = "application/json" $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } Write-Host "URL: $triggerUrl" Write-Host "BODY: $body" #Trigger the build $triggeredBuildResponse = Invoke-RestMethod -Uri $triggerUrl -Body $body -ContentType $type -Method Post -Headers $headers Write-Host "QUEUE RESPONSE: $triggeredBuildResponse"
The next thing that we are going to do is grab the build id from the response of our triggered build and construct the url for our build status query:
#Grab the build id $buildId = [string]$triggeredBuildResponse.id Write-Host "BUILD ID: $buildId" $statusUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds/$($buildId)?api-version=2.0" Write-Host "STATUS URL: $statusUrl"
Next we will query for the status and loop until it reports as completed:
#Query for the build status $statusGet = Invoke-RestMethod -Uri $statusUrl -ContentType $type -Method Get -Headers $headers Write-Host "STATUS RESPONSE: $statusGet" #Loop until the build is completed while($statusGet.status -ne "completed"){ Write-Host "Status: " + $statusGet.status Start-Sleep -Seconds 5 $statusGet = Invoke-RestMethod -Uri $statusUrl -ContentType $type -Method Get -Headers $headers Write-Host "STATUS RESPONSE: $statusGet" }
The final step of our script checks the result and if it was not successful, fail our build step:
$buildResult = $statusGet.result Write-Host "Build Completed: $buildResult" #Return an error if the build failed if($buildResult -ne "succeeded"){ Write-Error ("Failed Build") exit 1 } else{ exit 0 }
Here is the script in full:
$triggerUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds?api-version=2.0" $body = "{ 'definition' : { 'id' : $env:IOSBUILD_DEFINITIONID }, 'sourceBranch' : '$env:BUILD_SOURCEBRANCH' }" $type = "application/json" $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } Write-Host "URL: $triggerUrl" Write-Host "BODY: $body" #Trigger the build $triggeredBuildResponse = Invoke-RestMethod -Uri $triggerUrl -Body $body -ContentType $type -Method Post -Headers $headers Write-Host "QUEUE RESPONSE: $triggeredBuildResponse" #Grab the build id $buildId = [string]$triggeredBuildResponse.id Write-Host "BUILD ID: $buildId" $statusUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds/$($buildId)?api-version=2.0" Write-Host "STATUS URL: $statusUrl" #Query for the build status $statusGet = Invoke-RestMethod -Uri $statusUrl -ContentType $type -Method Get -Headers $headers Write-Host "STATUS RESPONSE: $statusGet" #Loop until the build is completed while($statusGet.status -ne "completed"){ Write-Host "Status: " + $statusGet.status Start-Sleep -Seconds 5 $statusGet = Invoke-RestMethod -Uri $statusUrl -ContentType $type -Method Get -Headers $headers Write-Host "STATUS RESPONSE: $statusGet" } $buildResult = $statusGet.result Write-Host "Build Completed: $buildResult" #Return an error if the build failed if($buildResult -ne "succeeded"){ Write-Error ("Failed Build") exit 1 } else{ exit 0 }
Lastly, we set a timeout on the build step in case something goes wrong, so that our build doesn’t run forever. The exact timeout should be longer than you ever expect your build to be queued and run.
Now the success or failure of your main build will depend on the successful completion of the triggered build.
Asynchronous Build
We also implemented one other change. In our case, we could trigger the dependent build, do some more build steps on the main build, and then check the status of the triggered build later on. This saved us quite a few minutes of build time because we were processing the builds in parallel.
To do this, after triggering the build, set a variable with the triggered build id:
Write-Host ("##vso[task.setvariable variable=triggeredbuildid;]$buildId")
Then when you are ready to check the status in a later build step, you can get the triggeredbuildid value with:
$buildId = $env:triggeredbuildid
Thus, you can now create two different build steps, one to trigger the build and one later to check the status.
Trigger Build Script: $triggerUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds?api-version=2.0" $body = "{ 'definition' : { 'id' : $env:IOSBUILD_DEFINITIONID }, 'sourceBranch' : '$env:BUILD_SOURCEBRANCH' }" $type = "application/json" $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } Write-Host "URL: $triggerUrl" Write-Host "BODY: $body" #Trigger the build $triggeredBuildResponse = Invoke-RestMethod -Uri $triggerUrl -Body $body -ContentType $type -Method Post -Headers $headers Write-Host "QUEUE RESPONSE: $triggeredBuildResponse" #Grab the build id $buildId = [string]$triggeredBuildResponse.id Write-Host "BUILD ID: $buildId" Write-Host ("##vso[task.setvariable variable=triggeredbuildid;]$buildId")
Check Build Status Script:
$type = "application/json" $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } $buildId = $env:triggeredbuildid $statusUrl = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/build/builds/$($buildId)?api-version=2.0" Write-Host "STATUS URL: $statusUrl" #Query for the build status $statusGet = Invoke-RestMethod -Uri $statusUrl -ContentType $type -Method Get -Headers $headers Write-Host "STATUS RESPONSE: $statusGet" #Loop until the build is completed while($statusGet.status -ne "completed"){ Write-Host "Status: " + $statusGet.status Start-Sleep -Seconds 5 $statusGet = Invoke-RestMethod -Uri $statusUrl -ContentType $type -Method Get -Headers $headers Write-Host "STATUS RESPONSE: $statusGet" } $buildResult = $statusGet.result Write-Host "Build Completed: $buildResult" #Return an error if the build failed if($buildResult -ne "succeeded"){ Write-Error ("Failed Build") exit 1 } else{ exit 0 }