Public/Get-PSUADOPipelineLatestRun.ps1
|
function Get-PSUADOPipelineLatestRun { <# .SYNOPSIS Gets the latest Azure DevOps pipeline run information using pipeline ID or URL. .DESCRIPTION This function retrieves the most recent run (and optionally the second most recent) of a specified Azure DevOps pipeline. You can provide either the pipeline ID directly or a pipeline URL. The function uses a personal access token (PAT) for authentication and returns key details about the run including status, result, and who triggered it. - Make sure your PAT has sufficient permissions (at least "Read & execute" for pipelines). - If the latest run is still in progress, the function falls back to the previous run's result if available. - This function uses Azure DevOps REST API version 7.1-preview.1. .PARAMETER PipelineId (Optional - ParameterSet: ByPipelineId) The numeric ID of the Azure DevOps pipeline. .PARAMETER PipelineUrl (Optional - ParameterSet: ByPipelineUrl) The full URL of the Azure DevOps pipeline. The function will extract the pipeline ID automatically. .PARAMETER Project (Mandatory) The Azure DevOps project name containing the pipeline. .PARAMETER Organization (Optional) The Azure DevOps organization name under which the project resides. Default value is $env:ORGANIZATION. Set using: Set-PSUUserEnvironmentVariable -Name "ORGANIZATION" -Value "your_org_name" .PARAMETER PAT (Optional) Personal Access Token for Azure DevOps authentication. Default value is $env:PAT. Set using: Set-PSUUserEnvironmentVariable -Name "PAT" -Value "your_pat_token" .EXAMPLE Get-PSUADOPipelineLatestRun -Organization "omg" -Project "psutilities" -PipelineId 2323 Fetches the latest run for pipeline ID 2323 in the psutilities project. .EXAMPLE Get-PSUADOPipelineLatestRun -Organization "omg" -Project "psutilities" -PipelineUrl "https://dev.azure.com/omg/psutilities/_build?definitionId=23" Extracts the pipeline ID from the URL and fetches the latest run details. .OUTPUTS [PSCustomObject] .NOTES Author: Lakshmanachari Panuganti Date : 2025-06-16 Updated: 2025-07-22 - Refactored for better URL parsing, error handling, and variable consistency .LINK https://github.com/lakshmanachari-panuganti https://www.powershellgallery.com/packages/OMG.PSUtilities.AzureDevOps https://www.linkedin.com/in/lakshmanachari-panuganti/ #> [CmdletBinding(DefaultParameterSetName = 'ById')] param ( [Parameter(ParameterSetName = 'ById', Mandatory)] [ValidateRange(1, [int]::MaxValue)] [int]$PipelineId, [Parameter(ParameterSetName = 'ByUrl', Mandatory)] [ValidateNotNullOrEmpty()] [string]$PipelineUrl, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Project, [Parameter()] [ValidateNotNullOrEmpty()] [string]$Organization = $env:ORGANIZATION, [Parameter()] [ValidateNotNullOrEmpty()] [string]$PAT = $env:PAT ) begin { # Display parameters Write-Verbose "[$($MyInvocation.MyCommand.Name)] Parameters:" foreach ($param in $PSBoundParameters.GetEnumerator()) { if ($param.Key -eq 'PAT') { $maskedPAT = if ($param.Value -and $param.Value.Length -ge 3) { $param.Value.Substring(0, 3) + "********" } else { "***" } Write-Verbose " $($param.Key): $maskedPAT" } else { Write-Verbose " $($param.Key): $($param.Value)" } } # Validate Organization (required because ValidateNotNullOrEmpty doesn't check default values from environment variables) if (-not $Organization) { throw "The default value for the 'ORGANIZATION' environment variable is not set.`nSet it using: Set-PSUUserEnvironmentVariable -Name 'ORGANIZATION' -Value '<org>' or provide via -Organization parameter." } # Validate PAT (required because ValidateNotNullOrEmpty doesn't check default values from environment variables) if (-not $PAT) { throw "The default value for the 'PAT' environment variable is not set.`nSet it using: Set-PSUUserEnvironmentVariable -Name 'PAT' -Value '<pat>' or provide via -PAT parameter." } $headers = Get-PSUAdoAuthHeader -PAT $PAT } process { try { # Handle PipelineId extraction from URL if that is the input set if ($PSCmdlet.ParameterSetName -eq 'ByUrl') { Write-Verbose "Extracting Pipeline ID from URL: $PipelineUrl" # Enhanced URL pattern matching to handle multiple Azure DevOps URL formats $patterns = @( 'pipelines/(\d+)', # New format: /pipelines/123 'definitionId=(\d+)', # Classic format: ?definitionId=123 '_build/results\?buildId=(\d+)', # Build results: ?buildId=123 '_build\?definitionId=(\d+)', # Direct build: ?definitionId=123 'buildId=(\d+)' # Simple buildId parameter ) $extractedId = $null foreach ($pattern in $patterns) { if ($PipelineUrl -match $pattern) { $extractedId = [int]$matches[1] Write-Verbose "Successfully extracted Pipeline ID: $extractedId using pattern: $pattern" break } } if (-not $extractedId) { throw "Unable to extract Pipeline ID from URL: $PipelineUrl. Supported formats include pipelines/ID, definitionId=ID, or buildId=ID" } $PipelineId = $extractedId } Write-Verbose "Processing Pipeline ID: $PipelineId" Write-Verbose "Escaping project name for URL..." $escapedProject = if ($Project -match '%[0-9A-Fa-f]{2}') { $Project } else { [uri]::EscapeDataString($Project) } # Get top 2 latest runs for fallback logic $runUrl = "https://dev.azure.com/$Organization/$escapedProject/_apis/pipelines/$PipelineId/runs" + "?`$top=2&api-version=7.1-preview.1" Write-Verbose "Calling Azure DevOps API at: $runUrl" $response = Invoke-RestMethod -Uri $runUrl -Headers $headers -Method Get -ErrorAction Stop $runs = $response.value if (-not $runs -or $runs.Count -eq 0) { Write-Warning "No runs found for Pipeline ID: $PipelineId" return $null } Write-Verbose "Found $($runs.Count) run(s) for pipeline $PipelineId" $latestRun = $runs[0] $previousRun = if ($runs.Count -ge 2) { $runs[1] } else { $null } # Determine state and result with improved logic if ($latestRun.state -eq "inProgress" -or -not $latestRun.result) { $state = "inProgress" $result = if ($previousRun -and $previousRun.result) { Write-Verbose "Latest run in progress, using previous run result: $($previousRun.result)" $previousRun.result } else { Write-Verbose "No previous run available, result set to N/A" "N/A" } } else { $state = $latestRun.state $result = $latestRun.result } Write-Verbose "Getting detailed build information..." $buildParams = @{ BuildId = $latestRun.id Pat = $PAT Organization = $Organization Project = $Project } try { $Build = Get-PSUADOPipelineBuild @buildParams } catch { Write-Warning "Could not retrieve detailed build information: $($_.Exception.Message)" $Build = [PSCustomObject]@{ TriggeredBy = "Unknown" } } [PSCustomObject]@{ PipelineId = $PipelineId BuildId = $latestRun.id State = $state Result = $result StartDate = if ($latestRun.createdDate) { [datetime]$latestRun.createdDate } else { $null } EndDate = if ($latestRun.finishedDate) { [datetime]$latestRun.finishedDate } else { $null } TriggeredBy = $Build.TriggeredBy RunWebUrl = $latestRun._links.web.href Source = if ($PSCmdlet.ParameterSetName -eq 'ByUrl') { 'ByUrl' } else { 'ById' } HasPreviousRun = $null -ne $previousRun PSTypeName = 'PSU.ADO.PipelineRun' } } catch { $PSCmdlet.ThrowTerminatingError($_) } } } |