build/scripts/Wait-RunningBuild.ps1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. <# .DESCRIPTION This script will enforce the concept of a queue for a build pipeline to ensure that builds do not actively run concurrently. .PARAMETER PersonalAccessToken A token that has Build READ permission. .PARAMETER OrganizationName The name of the organization that this project is a part of. .PARAMETER ProjectName The name of the project that this build pipeline can be found in. .PARAMETER BuildDefinitionId The ID for the build definition that we are enforcing a queue on. .PARAMETER BuildId The ID for this build. .PARAMETER NumSecondsSleepBetweenPolling The number of seconds to sleep before polling attempt to check build pipeline status again. .PARAMETER MaxRetriesBeforeStarting The number of successive retries that will be attempted to query for build pipeline status before just allowing the build to start. .EXAMPLE $params = @{ PersonalAccessToken = $env:buildReadAccessToken OrganizationName = 'ms' ProjectName = 'PowerShellForGitHub' BuildDefinitionId = $(System.DefinitionId) BuildId = $(Build.BuildId) } ./Wait-RunningBuilds.ps1 @params #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "This is the preferred way of writing output for Azure DevOps.")] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $PersonalAccessToken, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $OrganizationName, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ProjectName, [Parameter(Mandatory)] [int64] $BuildDefinitionId, [Parameter(Mandatory)] [int64] $BuildId, [int] $NumSecondsSleepBetweenPolling = 30, [int] $MaxRetriesBeforeStarting = 3 ) Write-Host '[Wait-RunningBuilds] - Starting' $elapsedTimeFormat = '{0:hh\:mm\:ss}' $stopwatch = New-Object -TypeName System.Diagnostics.Stopwatch $stopwatch.Start() $url = "https://dev.azure.com/$OrganizationName/$ProjectName/_apis/build/builds?api-version=5.1&definitions=$BuildDefinitionId" $headers = @{ 'Authorization' = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$PersonalAccessToken")) } $remainingRetries = $MaxRetriesBeforeStarting do { try { $params = @{ Uri = $url Method = 'Get' ContentType = 'application/json' Headers = $headers UseBasicParsing = $true } $builds = Invoke-RestMethod @params $remainingRetries = $MaxRetriesBeforeStarting # successfully got a result. Reset remaining retries $thisBuild = $builds.value | Where-Object { $_.id -eq $BuildId } $runningBuilds = @($builds.value | Where-Object { $_.status -eq 'inProgress' }) $currentRunningBuild = $runningBuilds | Sort-Object -Property 'Id' | Select-Object -First 1 if ($null -eq $currentRunningBuild) { Write-Host 'Failed to identify the currently running build. To prevent an indefinite wait, allowing this build to start.' break } elseif ($BuildId -ne $currentRunningBuild.id) { $buildsAheadInQueue = @($runningBuilds | Where-Object { $_.id -lt $BuildId }) # We want to display how long the current build has been running for _actively_, # so we need to take into account if it had been queued while the previous build was # running and thus subtract that extra time. $currentRunningBuildStartTime = Get-Date -Date $currentRunningBuild.startTime $lastCompletedBuild = $builds.value | Where-Object { $_.status -ne 'inProgress' } | Select-Object -First 1 if ($null -ne $lastCompletedBuild) { $lastCompletedBuildTime = Get-Date -Date $lastCompletedBuild.finishTime if ($lastCompletedBuildTime -gt $currentRunningBuildStartTime) { $waitedDuration = $lastCompletedBuildTime - $currentRunningBuildStartTime $currentRunningBuildStartTime.AddMilliseconds($waitedDuration.TotalMilliseconds) } } $currentRunningBuildElapsedTime = New-TimeSpan -Start $currentRunningBuildStartTime -End (Get-Date) $currentRunningBuildElapsedTimeFormatted = $elapsedTimeFormat -f $currentRunningBuildElapsedTime $timeWaited = New-TimeSpan -Start $thisBuild.startTime -End (Get-Date) $timeWaitedFormatted = $elapsedTimeFormat -f $timeWaited $message = @( "* Time: $(Get-Date -Format 'o')", " This build: $($thisBuild.id) ($($thisBuild.buildNumber)) [Waiting for $timeWaitedFormatted]", " Builds ahead in queue: $($buildsAheadInQueue.buildNumber -join ', ')", " Total queued builds: $($runningBuilds.Count - 1)", " Currently running build: $($currentRunningBuild.id) ($($currentRunningBuild.buildNumber)) [Running for $currentRunningBuildElapsedTimeFormatted]", " Waiting $NumSecondsSleepBetweenPolling seconds before polling build status for this pipeline again...", '--------------------------') Write-Host ($message -join [Environment]::NewLine) } else { break } } catch { $remainingRetries-- if ($remainingRetries -lt 0) { Write-Host 'Still unable to retrieve build status for this pipeline. Exhausted retries. To prevent an indefinite wait, allowing this build to start.' break } else { Write-Host "Failed to get build status for this pipeline. Will try again in $NumSecondsSleepBetweenPolling seconds. $remainingRetries retries remaining." } } Start-Sleep -Seconds $NumSecondsSleepBetweenPolling } while ($true) $stopwatch.Stop() $timeWaitedFormatted = $elapsedTimeFormat -f $stopwatch.Elapsed Write-Host "Waiting completed after $timeWaitedFormatted. Starting this build." Write-Host '[Wait-RunningBuilds] - Exiting' |