Skip to content

Instantly share code, notes, and snippets.

@rfennell
Last active May 16, 2025 13:53
Show Gist options
  • Save rfennell/b4453345f371f5f42dbd15611a893797 to your computer and use it in GitHub Desktop.
Save rfennell/b4453345f371f5f42dbd15611a893797 to your computer and use it in GitHub Desktop.
This script connects to an Azure DevOps organization using a Personal Access Token (PAT). It enumerates all projects within the organization, retrieves all pipelines for each project, and gathers details about each pipeline, including type (YAML or Classic), source repository, default branch, repository URL, and the status and time of the latest…
<#
.SYNOPSIS
Retrieves pipeline information for all projects in an Azure DevOps organization and exports it to a CSV file.
.DESCRIPTION
This script connects to an Azure DevOps organization using a Personal Access Token (PAT).
It enumerates all projects within the organization, retrieves all pipelines for each project,
and gathers details about each pipeline, including type (YAML or Classic), source repository,
default branch, repository URL, and the status and time of the latest run.
The collected information is exported to a specified CSV file.
.PARAMETER organization
The name of the Azure DevOps organization.
.PARAMETER pat
The Personal Access Token (PAT) used for authentication with the Azure DevOps REST API.
.PARAMETER outputCsv
The file path for the output CSV file.
.NOTES
- Requires PowerShell and network access to Azure DevOps REST API.
- The PAT must have sufficient permissions to read projects, pipelines, and repositories.
- Handles both YAML and Classic pipelines.
- For YAML pipelines using Azure Repos Git, attempts to resolve the repository URL via the Azure DevOps API.
- The script processes all projects in the organization, not just a single project.
.EXAMPLE
.\get-azdo-builds.ps1 -organization "my-org" -pat "my-pat" -outputCsv "./pipelines.csv"
Retrieves pipeline information from all projects in the specified Azure DevOps organization and exports it to "pipelines.csv".
#>
# Azure DevOps organization and project details
param(
[string]$organization,
[string]$pat,
[string]$outputCsv
)
# Base64-encode the PAT for authentication
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))
# Get all team projects in the organization
$projectsUri = "https://dev.azure.com/$organization/_apis/projects?api-version=7.0"
Write-Host "Fetching list of projects from Azure DevOps..."
$projectsResult = Invoke-RestMethod -Uri $projectsUri -Headers @{Authorization = "Basic $base64AuthInfo"}
$projects = $projectsResult.value
Write-Host ("Found {0} projects." -f $projects.Count)
# Prepare an array to hold pipeline info
$pipelineInfoList = @()
foreach ($projectObj in $projects) {
$project = $projectObj.name
Write-Host ("\nProcessing project: {0}" -f $project)
# Get all pipelines for this project
$uri = "https://dev.azure.com/$organization/$project/_apis/pipelines?api-version=7.0"
Write-Host " Fetching pipelines..."
$pipelines = Invoke-RestMethod -Uri $uri -Headers @{Authorization = "Basic $base64AuthInfo"}
Write-Host (" Found {0} pipelines." -f $pipelines.count)
foreach ($pipeline in $pipelines.value) {
$pipelineId = $pipeline.id
$pipelineName = $pipeline.name
Write-Host (" Processing pipeline: {0} (ID: {1})..." -f $pipelineName, $pipelineId)
# Get pipeline details (for repository info)
$pipelineDetailsUri = "https://dev.azure.com/$organization/$project/_apis/pipelines/$($pipelineId)?api-version=7.0"
$pipelineDetails = Invoke-RestMethod -Uri $pipelineDetailsUri -Headers @{Authorization = "Basic $base64AuthInfo"}
# Handle both YAML and Classic pipelines
$designerJsonRepo = $null
$repo = $null
$pipelineType = $null
if ($pipelineDetails.configuration.PSObject.Properties.Name -contains 'designerJson') {
$designerJson = $pipelineDetails.configuration.designerJson
if ($designerJson.PSObject.Properties.Name -contains 'repository') {
$designerJsonRepo = $designerJson.repository
$pipelineType = $designerJson.type
}
}
if ($pipelineDetails.configuration.PSObject.Properties.Name -contains 'repository') {
$repo = $pipelineDetails.configuration.repository
$pipelineType = 'yaml'
}
if ($designerJsonRepo) {
$sourceType = $designerJsonRepo.type
$sourceBranch = $designerJsonRepo.defaultBranch
$sourceUrl = $designerJsonRepo.url
} elseif ($repo) {
$sourceType = $repo.type
$sourceBranch = $repo.defaultBranch
# For YAML pipelines with Azure Repos Git, get the repo URL from the repo id
if ($sourceType -eq 'azurereposgit' -and $pipelineType -eq 'yaml') {
$repoId = $repo.id
# Call the Azure DevOps Repositories API to get the repo details
$repoApiUri = "https://dev.azure.com/$organization/$project/_apis/git/repositories/$($repoId)?api-version=7.0"
try {
$repoDetails = Invoke-RestMethod -Uri $repoApiUri -Headers @{Authorization = "Basic $base64AuthInfo"}
$sourceUrl = $repoDetails.webUrl
} catch {
$sourceUrl = 'Cannot resolve repo with ID ' + $repoId
}
} else {
$sourceUrl = $repo.url
}
} else {
$sourceType = ''
$sourceBranch = ''
$sourceUrl = ''
}
# Get latest run
$runsUri = "https://dev.azure.com/$organization/$project/_apis/pipelines/$($pipelineId)/runs?api-version=7.0&$top=1"
$runs = Invoke-RestMethod -Uri $runsUri -Headers @{Authorization = "Basic $base64AuthInfo"}
if ($runs.count -gt 0) {
$lastRun = $runs.value[0]
$lastRunTime = $lastRun.createdDate
$status = $lastRun.result
Write-Host (" Last run: {0} (Status: {1})" -f $lastRunTime, $status)
} else {
$lastRunTime = "Never"
$status = "N/A"
Write-Host " No runs found for this pipeline."
}
$pipelineInfo = [PSCustomObject]@{
"Team Project" = $project
"Pipeline Name" = $pipelineName
"Definition Id" = $pipelineId
"Pipeline Type" = $pipelineType
"Source Type" = $sourceType
"Source Branch" = $sourceBranch
"Source URL" = $sourceUrl
"Last Run Time" = $lastRunTime
"Status" = $status
}
$pipelineInfoList += $pipelineInfo
}
}
# Export to CSV
Write-Host "\nExporting pipeline information to $outputCsv..."
$pipelineInfoList | Export-Csv -Path $outputCsv -NoTypeInformation -Encoding UTF8
Write-Host "Pipeline information exported to $outputCsv"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment