added documetation

This commit is contained in:
Jurjen Ladenius
2025-11-03 08:12:01 +01:00
parent 8840b0e300
commit a226ca97ac
37 changed files with 8315 additions and 1481 deletions

View File

@@ -1,65 +1,171 @@
<#
.SYNOPSIS
Exports Azure DevOps pipeline information to a CSV file.
.DESCRIPTION
This script retrieves all build pipelines from an Azure DevOps project and exports detailed information
about each pipeline to a CSV file. It collects pipeline metadata including ID, name, path, type,
author, creation date, pipeline type (Classic or YAML), and edit URLs.
.PARAMETER Token
The Azure DevOps Personal Access Token (PAT) used for authentication. This token must have
'Build (Read)' permissions to access pipeline information.
.PARAMETER Organization
The Azure DevOps organization name. Defaults to "effectory" if not specified.
.PARAMETER Project
The Azure DevOps project name. Defaults to "Survey%20Software" if not specified.
Note: URL-encoded project names should be used (spaces as %20).
.EXAMPLE
.\Pipelines.ps1 -Token "your-personal-access-token"
Exports pipeline information using the default organization and project settings.
.EXAMPLE
.\Pipelines.ps1 -Token "your-pat-token" -Organization "myorg" -Project "MyProject"
Exports pipeline information for a specific organization and project.
.EXAMPLE
.\Pipelines.ps1 -Token "your-pat-token" -Project "My%20Custom%20Project"
Exports pipeline information for a custom project (with URL-encoded name) using the default organization.
.OUTPUTS
Creates a timestamped CSV file in the current directory with the format: "yyyy-MM-dd HHmm pipelines.csv"
The CSV contains the following columns:
- Id: Pipeline ID
- Name: Pipeline name
- Path: Pipeline folder path
- Type: Pipeline type (build/release)
- Author: Pipeline author
- CreatedDate: Pipeline creation date
- PipelineType: Classic or YAML
- PipelineEditUrl: Direct URL to edit the pipeline
.NOTES
Author: Cloud Engineering Team
Created: 2025
Requires: PowerShell 5.1 or later
Dependencies: Internet connectivity to Azure DevOps
The script uses Azure DevOps REST API version 7.1-preview.7 to retrieve pipeline information.
Ensure your Personal Access Token has the necessary permissions before running the script.
.LINK
https://docs.microsoft.com/en-us/rest/api/azure/devops/build/definitions/list
#>
param(
[Parameter(Mandatory = $true, HelpMessage = "Azure DevOps Personal Access Token with Build (Read) permissions")]
[string]$Token,
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps organization name")]
[string]$Organization = "effectory",
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps project name (URL-encoded if contains spaces)")]
[string]$Project = "Survey%20Software"
)
# Define a class to structure pipeline information
class PipelineInfo {
[string] $Id = ""
[string] $Name = ""
[string] $Path = ""
[string] $Type = ""
[string] $Author = ""
[string] $CreatedDate = ""
[string] $PipelineType = ""
[string] $PipelineEditUrl = ""
[string] $Id = "" # Pipeline unique identifier
[string] $Name = "" # Pipeline display name
[string] $Path = "" # Pipeline folder path in Azure DevOps
[string] $Type = "" # Pipeline type (build/release)
[string] $Author = "" # Pipeline creator
[string] $CreatedDate = "" # Pipeline creation timestamp
[string] $PipelineType = "" # Classic or YAML pipeline type
[string] $PipelineEditUrl = "" # Direct URL to edit the pipeline
}
$token = "hyrvwxicogy37djvmhkwrcdexokcrpyudkk4j2n3n7gnjb5wsv5a"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$organization = "effectory"
$project = "Survey%20Software"
$head = @{ Authorization =" Basic $token" }
# Encode the Personal Access Token for Basic Authentication
# Azure DevOps requires the token to be base64 encoded with a colon prefix
$encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($Token)"))
# Create authentication header for REST API calls
$head = @{ Authorization =" Basic $encodedToken" }
<#
.SYNOPSIS
Determines if a pipeline is Classic or YAML based.
.DESCRIPTION
Makes an additional API call to determine the pipeline process type.
Returns 1 for Classic pipelines, 2 for YAML pipelines.
.PARAMETER pipeLineId
The unique identifier of the pipeline to check.
.OUTPUTS
Integer value: 1 = Classic pipeline, 2 = YAML pipeline
#>
function GetPipelineType {
param (
[int] $pipeLineId
)
$url = "https://dev.azure.com/$organization/$project/_apps/hub/ms.vss-build-web.ci-designer-hub?pipelineId=$pipeLineId&__rt=fps&__ver=2"
# Call Azure DevOps internal API to get pipeline process type
$url = "https://dev.azure.com/$Organization/$Project/_apps/hub/ms.vss-build-web.ci-designer-hub?pipelineId=$pipeLineId&__rt=fps&__ver=2"
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
# Extract the definition process type from the response
return $response.fps.dataProviders.data."ms.vss-build-web.pipeline-detail-data-provider".definitionProcessType
}
# Generate timestamped filename for the output CSV
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date pipelines.csv"
# Display script execution banner
Write-Host "========================================================================================================================================================================"
Write-Host "Creating service connection overview."
Write-Host "Creating pipeline overview for Organization: $Organization, Project: $Project"
Write-Host "Output file: $fileName"
Write-Host "========================================================================================================================================================================"
$url="https://dev.azure.com/$organization/$project/_apis/build/definitions?api-version=7.1-preview.7"
# Call Azure DevOps REST API to get all build definitions
$url="https://dev.azure.com/$Organization/$Project/_apis/build/definitions?api-version=7.1-preview.7"
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
# Initialize array to store pipeline information
[PipelineInfo[]]$Result = @()
# Process each pipeline returned from the API
$response.value | ForEach-Object {
Write-Host "Processing pipeline: $($_.name)" -ForegroundColor Green
# Determine if this is a Classic or YAML pipeline
$definitionProcessType = GetPipelineType -pipeLineId $_.id
$definitionProcessType = GetPipelineType -pipeLineId $_.id
# Create new pipeline info object and populate properties
[PipelineInfo] $pipelineInfo = [PipelineInfo]::new()
$pipelineInfo.Id = $_.id
$pipelineInfo.Name = $_.name
$pipelineInfo.Path = $_.path
$pipelineInfo.Type = $_.type
$pipelineInfo.Author = $_.authoredby.DisplayName
$pipelineInfo.CreatedDate = $_.createdDate
$pipelineInfo.PipelineType = $definitionProcessType -eq 1 ? "Classic" : "Yaml"
# Generate appropriate edit URL based on pipeline type
$pipelineInfo.PipelineEditUrl = $definitionProcessType -eq 1 ?
"https://dev.azure.com/$Organization/$Project/_apps/hub/ms.vss-ciworkflow.build-ci-hub?_a=edit-build-definition&id=$($_.id)" :
"https://dev.azure.com/$Organization/$Project/_apps/hub/ms.vss-build-web.ci-designer-hub?pipelineId=$($_.id)&branch=master"
# Add to results array
$Result += $pipelineInfo
}
[PipelineInfo] $pipelineInfo = [PipelineInfo]::new()
$pipelineInfo.Id = $_.id
$pipelineInfo.Name = $_.name
$pipelineInfo.Path = $_.path
$pipelineInfo.Type = $_.type
$pipelineInfo.Author = $_.authoredby.DisplayName
$pipelineInfo.CreatedDate = $_.createdDate
$pipelineInfo.PipelineType = $definitionProcessType -eq 1 ? "Classic" : "Yaml"
$pipelineInfo.PipelineEditUrl = $definitionProcessType -eq 1 ?
"https://dev.azure.com/$organization/$project/_apps/hub/ms.vss-ciworkflow.build-ci-hub?_a=edit-build-definition&id=$($_.id)" :
"https://dev.azure.com/$organization/$project/_apps/hub/ms.vss-build-web.ci-designer-hub?pipelineId=$($_.id)&branch=master"
$Result += $pipelineInfo
}
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
# Export results to CSV file
$Result | Export-Csv -Path $fileName -NoTypeInformation
# Display completion summary
Write-Host "========================================================================================================================================================================"
Write-Host "Done."
Write-Host "Export completed successfully!" -ForegroundColor Green
Write-Host "Total pipelines processed: $($Result.Count)" -ForegroundColor Yellow
Write-Host "Output file: $fileName" -ForegroundColor Yellow
Write-Host "========================================================================================================================================================================"

View File

@@ -1,36 +1,121 @@
<#
.SYNOPSIS
Exports Azure DevOps pull request information across all repositories to a CSV file.
.DESCRIPTION
This script retrieves all repositories from an Azure DevOps project and then collects
detailed information about all pull requests (active, completed, and abandoned) from each
repository. The information is exported to a timestamped CSV file for analysis and reporting.
.PARAMETER Organization
The Azure DevOps organization name. Defaults to "effectory" if not specified.
.PARAMETER Project
The Azure DevOps project name. Defaults to "survey software" if not specified.
.EXAMPLE
.\PullRequests.ps1
Exports pull request information using the default organization and project settings.
.EXAMPLE
.\PullRequests.ps1 -Organization "myorg" -Project "myproject"
Exports pull request information for a specific organization and project.
.OUTPUTS
Creates a timestamped CSV file in the current directory with the format: "yyyy-MM-dd HHmm pull requests.csv"
The CSV contains the following columns:
- RepositoryId: Repository unique identifier
- RepositoryName: Repository name
- DefaultBranch: Repository default branch
- RepositoryWebUrl: Repository web URL
- PullRequestId: Pull request unique identifier
- PullRequestDate: Pull request creation date
- PullRequestName: Pull request title
- PullRequestCreatedBy: Pull request author
- PullRequestReviewers: Comma-separated list of reviewers
- PullRequestStatus: Pull request status (Active, Completed, Abandoned)
- PullRequestWebUrl: Direct URL to the pull request
- CompletionBypassReason: Reason for bypassing completion requirements (if any)
.NOTES
Author: Cloud Engineering Team
Created: 2025
Requires: PowerShell 5.1 or later, Azure CLI installed and authenticated
Dependencies: Azure CLI (az) must be installed and user must be authenticated
Prerequisites:
- Install Azure CLI: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
- Authenticate: az login
- Set default subscription if needed: az account set --subscription "subscription-name"
The script processes all active repositories in the specified project and retrieves
all pull requests regardless of their status (active, completed, abandoned).
.LINK
https://docs.microsoft.com/en-us/cli/azure/repos/pr
#>
param(
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps organization name")]
[string]$Organization = "effectory",
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps project name")]
[string]$Project = "survey software"
)
# Define a class to structure pull request information
class PullRequest {
[string] $RepositoryId = ""
[string] $RepositoryName = ""
[string] $DefaultBranch = ""
[string] $RepositoryWebUrl = ""
[string] $PullRequestId = ""
[string] $PullRequestDate = ""
[string] $PullRequestName = ""
[string] $PullRequestCreatedBy = ""
[string] $PullRequestReviewers = ""
[string] $PullRequestStatus = ""
[string] $PullRequestWebUrl = ""
[string] $CompletionBypassReason = ""
[string] $RepositoryId = "" # Repository unique identifier
[string] $RepositoryName = "" # Repository display name
[string] $DefaultBranch = "" # Repository default branch (e.g., main, master)
[string] $RepositoryWebUrl = "" # Repository web URL in Azure DevOps
[string] $PullRequestId = "" # Pull request unique identifier
[string] $PullRequestDate = "" # Pull request creation timestamp
[string] $PullRequestName = "" # Pull request title/name
[string] $PullRequestCreatedBy = "" # Pull request author display name
[string] $PullRequestReviewers = "" # Comma-separated list of reviewer names
[string] $PullRequestStatus = "" # PR status: Active, Completed, Abandoned
[string] $PullRequestWebUrl = "" # Direct URL to view the pull request
[string] $CompletionBypassReason = "" # Reason for bypassing completion policies
}
# Generate timestamped filename for the output CSV
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date pull requests.csv"
# Display script execution banner
Write-Host "========================================================================================================================================================================"
Write-Host "Creating pull request overview."
Write-Host "Creating pull request overview for Organization: $Organization, Project: $Project"
Write-Host "Output file: $fileName"
Write-Host "Note: This script requires Azure CLI to be installed and authenticated (az login)"
Write-Host "========================================================================================================================================================================"
$repos = az repos list --organization "https://dev.azure.com/effectory/" --project "survey software" | ConvertFrom-Json | Select-Object | Where-Object { $true -ne $_.isDisabled }
# Retrieve all active repositories from the Azure DevOps project
Write-Host "Fetching repositories from project '$Project'..." -ForegroundColor Yellow
$repos = az repos list --organization "https://dev.azure.com/$Organization/" --project "$Project" | ConvertFrom-Json | Select-Object | Where-Object { $true -ne $_.isDisabled }
Write-Host "Found $($repos.Count) active repositories" -ForegroundColor Green
# Process each repository to collect pull request information
$totalPullRequests = 0
foreach ($repo in $repos)
{
$prs = az repos pr list --project "survey software" --repository "$($repo.name)" --organization "https://dev.azure.com/effectory/" --status all | ConvertFrom-Json | Select-Object
Write-Host "Processing repository: $($repo.name)" -ForegroundColor Cyan
# Retrieve all pull requests from the current repository (all statuses: active, completed, abandoned)
$prs = az repos pr list --project "$Project" --repository "$($repo.name)" --organization "https://dev.azure.com/$Organization/" --status all | ConvertFrom-Json | Select-Object
Write-Host " Found $($prs.Count) pull requests" -ForegroundColor Gray
# Initialize array to store pull request information for this repository
[PullRequest[]]$Result = @()
# Process each pull request in the current repository
foreach ($pr in $prs)
{
# Create new pull request object and populate all properties
[PullRequest] $pullRequest = [PullRequest]::new()
$pullRequest.RepositoryId = $repo.id
$pullRequest.RepositoryName = $repo.name
@@ -40,17 +125,30 @@ foreach ($repo in $repos)
$pullRequest.PullRequestDate = $pr.creationDate
$pullRequest.PullRequestName = $pr.title
$pullRequest.PullRequestCreatedBy = $pr.createdBy.displayName
# Join all reviewer names into a comma-separated string
$pullRequest.PullRequestReviewers = $pr.reviewers | join-string -property displayName -Separator ','
$pullRequest.PullRequestStatus = $pr.status
# Construct direct URL to the pull request
$pullRequest.PullRequestWebUrl = "$($repo.webUrl)/pullrequest/$($pr.pullRequestId)"
# Capture bypass reason if completion policies were bypassed
$pullRequest.CompletionBypassReason = $pr.completionOptions.bypassReason
# Add to results array
$Result += $pullRequest
}
# Append results for this repository to the CSV file
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
$totalPullRequests += $Result.Count
}
# Display completion summary
Write-Host "========================================================================================================================================================================"
Write-Host "Done."
Write-Host "Export completed successfully!" -ForegroundColor Green
Write-Host "Total repositories processed: $($repos.Count)" -ForegroundColor Yellow
Write-Host "Total pull requests exported: $totalPullRequests" -ForegroundColor Yellow
Write-Host "Output file: $fileName" -ForegroundColor Yellow
Write-Host "========================================================================================================================================================================"

View File

@@ -1,30 +1,110 @@
<#
.SYNOPSIS
Exports Azure DevOps repository information along with last completed pull request details to a CSV file.
.DESCRIPTION
This script retrieves all repositories from an Azure DevOps project and collects detailed information
about each repository including basic metadata and information about the most recent completed pull request.
For active repositories, it identifies the last completed PR and captures details about the author,
reviewers, and other relevant information. The data is exported to a timestamped CSV file for analysis.
.PARAMETER Organization
The Azure DevOps organization name. Defaults to "effectory" if not specified.
.PARAMETER Project
The Azure DevOps project name. Defaults to "survey software" if not specified.
.EXAMPLE
.\Repositories.ps1
Exports repository information using the default organization and project settings.
.EXAMPLE
.\Repositories.ps1 -Organization "myorg" -Project "myproject"
Exports repository information for a specific organization and project.
.OUTPUTS
Creates a timestamped CSV file in the current directory with the format: "yyyy-MM-dd HHmm repositories.csv"
The CSV contains the following columns:
- Id: Repository unique identifier
- Name: Repository name
- DefaultBranch: Repository default branch (e.g., main, master)
- IsDisabled: Boolean indicating if the repository is disabled
- WebUrl: Repository web URL in Azure DevOps
- LastPRDate: Creation date of the most recent completed pull request
- LastPRName: Title of the most recent completed pull request
- LastPRCreatedBy: Author of the most recent completed pull request
- LastPRReviewers: Comma-separated list of reviewers for the most recent completed PR
- LastPRUrl: Direct URL to the most recent completed pull request
.NOTES
Author: Cloud Engineering Team
Created: 2025
Requires: PowerShell 5.1 or later, Azure CLI installed and authenticated
Dependencies: Azure CLI (az) must be installed and user must be authenticated
Prerequisites:
- Install Azure CLI: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
- Authenticate: az login
- Set default subscription if needed: az account set --subscription "subscription-name"
The script processes all repositories in the specified project, including disabled ones.
For disabled repositories, pull request information will be empty as they cannot be accessed.
Only completed pull requests are considered when determining the "last" PR.
.LINK
https://docs.microsoft.com/en-us/cli/azure/repos
#>
param(
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps organization name")]
[string]$Organization = "effectory",
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps project name")]
[string]$Project = "survey software"
)
# Define a class to structure repository information
class Repository {
[string] $Id = ""
[string] $Name = ""
[string] $DefaultBranch = ""
[string] $IsDisabled = ""
[string] $WebUrl = ""
[string] $LastPRDate = ""
[string] $LastPRName = ""
[string] $LastPRCreatedBy = ""
[string] $LastPRReviewers = ""
[string] $LastPRUrl = ""
[string] $Id = "" # Repository unique identifier
[string] $Name = "" # Repository display name
[string] $DefaultBranch = "" # Repository default branch (e.g., main, master)
[string] $IsDisabled = "" # Whether the repository is disabled (True/False)
[string] $WebUrl = "" # Repository web URL in Azure DevOps
[string] $LastPRDate = "" # Creation date of most recent completed PR
[string] $LastPRName = "" # Title of most recent completed PR
[string] $LastPRCreatedBy = "" # Author of most recent completed PR
[string] $LastPRReviewers = "" # Comma-separated list of reviewers
[string] $LastPRUrl = "" # Direct URL to most recent completed PR
}
# Generate timestamped filename for the output CSV
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date repositories.csv"
# Display script execution banner
Write-Host "========================================================================================================================================================================"
Write-Host "Creating repository overview."
Write-Host "Creating repository overview for Organization: $Organization, Project: $Project"
Write-Host "Output file: $fileName"
Write-Host "Note: This script requires Azure CLI to be installed and authenticated (az login)"
Write-Host "========================================================================================================================================================================"
$repos = az repos list --organization "https://dev.azure.com/effectory/" --project "survey software" | ConvertFrom-Json | Select-Object
# Retrieve all repositories from the Azure DevOps project
Write-Host "Fetching repositories from project '$Project'..." -ForegroundColor Yellow
$repos = az repos list --organization "https://dev.azure.com/$Organization/" --project "$Project" | ConvertFrom-Json | Select-Object
Write-Host "Found $($repos.Count) repositories" -ForegroundColor Green
# Initialize array to store repository information
[Repository[]]$Result = @()
# Process each repository to collect information and last PR details
foreach ($repo in $repos)
{
Write-Host "Processing repository: $($repo.name)" -ForegroundColor Cyan
# Create new repository object and populate basic information
[Repository] $repository = [Repository]::new()
$repository.Id = $repo.id
$repository.Name = $repo.name
@@ -32,26 +112,56 @@ foreach ($repo in $repos)
$repository.IsDisabled = $repo.isDisabled
$repository.WebUrl = $repo.webUrl
# Only attempt to get pull request information for active repositories
if ($true -ne $repo.isDisabled)
{
$lastPr = az repos pr list --project "survey software" --repository $repo.name --organization "https://dev.azure.com/effectory/" --status completed --top 1 | ConvertFrom-Json | Select-Object
Write-Host " Fetching last completed pull request..." -ForegroundColor Gray
# Get the most recent completed pull request (top 1, sorted by most recent)
$lastPr = az repos pr list --project "$Project" --repository $repo.name --organization "https://dev.azure.com/$Organization/" --status completed --top 1 | ConvertFrom-Json | Select-Object
# If a completed PR exists, capture its details
if ($lastPr)
{
$repository.LastPRDate = $lastPr.creationDate
$repository.LastPRName = $lastPr.title
$repository.LastPRUrl = $lastPr.url
$repository.LastPRCreatedBy = $lastPr.createdBy.displayName
# Join all reviewer names into a comma-separated string
$repository.LastPRReviewers = $lastPr.reviewers | join-string -property displayName -Separator ','
}
Write-Host " Last PR: $($lastPr.title) ($(Get-Date $lastPr.creationDate -Format 'yyyy-MM-dd'))" -ForegroundColor Gray
}
else
{
Write-Host " No completed pull requests found" -ForegroundColor Gray
}
}
else
{
Write-Host " Repository is disabled - skipping pull request analysis" -ForegroundColor Yellow
}
# Add repository to results array
$Result += $repository
}
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
# Export results to CSV file
$Result | Export-Csv -Path $fileName -NoTypeInformation
# Display completion summary
Write-Host "========================================================================================================================================================================"
Write-Host "Done."
Write-Host "Export completed successfully!" -ForegroundColor Green
Write-Host "Total repositories processed: $($Result.Count)" -ForegroundColor Yellow
# az repos pr list --project "survey software" --repository "ProjectCenter" --organization "https://dev.azure.com/effectory/" --status all --top 1
# Count repositories with and without recent PRs
$reposWithPRs = ($Result | Where-Object { $_.LastPRDate -ne "" }).Count
$activeRepos = ($Result | Where-Object { $_.IsDisabled -ne "True" }).Count
$disabledRepos = ($Result | Where-Object { $_.IsDisabled -eq "True" }).Count
Write-Host "Active repositories: $activeRepos" -ForegroundColor Yellow
Write-Host "Disabled repositories: $disabledRepos" -ForegroundColor Yellow
Write-Host "Repositories with completed PRs: $reposWithPRs" -ForegroundColor Yellow
Write-Host "Output file: $fileName" -ForegroundColor Yellow
Write-Host "========================================================================================================================================================================"

View File

@@ -1,41 +1,144 @@
<#
.SYNOPSIS
Analyzes Azure DevOps repositories to identify which have 'test' and 'accept' branches and their last activity.
.DESCRIPTION
This script retrieves all repositories from an Azure DevOps project and analyzes each one to determine:
- Basic repository information (ID, name, default branch, status, URL)
- Last commit activity on the default branch
- Whether a 'test' branch exists and its last commit activity
- Whether an 'accept' branch exists and its last commit activity
This is commonly used in development workflows where 'test' and 'accept' branches represent
specific deployment environments or approval stages in the development pipeline.
.PARAMETER Token
The Azure DevOps Personal Access Token (PAT) used for authentication. This token must have
'Code (Read)' permissions to access repository and commit information.
.PARAMETER Organization
The Azure DevOps organization name. Defaults to "effectory" if not specified.
.PARAMETER Project
The Azure DevOps project name. Defaults to "Survey Software" if not specified.
.EXAMPLE
.\RepositoriesWithTestAccept.ps1 -Token "your-personal-access-token"
Analyzes repositories using the default organization and project settings.
.EXAMPLE
.\RepositoriesWithTestAccept.ps1 -Token "your-pat-token" -Organization "myorg" -Project "MyProject"
Analyzes repositories for a specific organization and project.
.OUTPUTS
Creates a timestamped CSV file in the current directory with the format: "yyyy-MM-dd HHmm repositories with test and accept.csv"
The CSV contains the following columns:
- Id: Repository unique identifier
- Name: Repository name
- DefaultBranch: Repository default branch (e.g., main, master)
- IsDisabled: Boolean indicating if the repository is disabled
- WebUrl: Repository web URL in Azure DevOps
- LastDefaultChange: Date of last commit on the default branch
- HasTest: Boolean indicating if a 'test' branch exists (True/False)
- LastTestChange: Date of last commit on the 'test' branch (empty if branch doesn't exist)
- HasAccept: Boolean indicating if an 'accept' branch exists (True/False)
- LastAcceptChange: Date of last commit on the 'accept' branch (empty if branch doesn't exist)
.NOTES
Author: Cloud Engineering Team
Created: 2025
Requires: PowerShell 5.1 or later, Azure CLI installed and authenticated
Dependencies: Azure CLI (az) for repository listing, Azure DevOps REST API for commit information
Prerequisites:
- Install Azure CLI: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
- Authenticate: az login
- Azure DevOps Personal Access Token with Code (Read) permissions
The script uses both Azure CLI commands and REST API calls:
- Azure CLI for listing repositories
- REST API for checking branch existence and commit history
Branch Analysis:
- 'test' branch: Often used for testing environment deployments
- 'accept' branch: Often used for acceptance testing or staging environments
- Default branch: Usually 'main' or 'master', represents the primary development branch
Error Handling:
- If a branch doesn't exist, the API call will fail and the branch is marked as non-existent
- Disabled repositories are processed but branch analysis is skipped
- Network or authentication errors are handled gracefully
.LINK
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/commits/get-commits
#>
param(
[Parameter(Mandatory = $true, HelpMessage = "Azure DevOps Personal Access Token with Code (Read) permissions")]
[string]$Token,
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps organization name")]
[string]$Organization = "effectory",
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps project name")]
[string]$Project = "Survey Software"
)
# Define a class to structure repository information with branch analysis
class Repository {
[string] $Id = ""
[string] $Name = ""
[string] $DefaultBranch = ""
[string] $IsDisabled = ""
[string] $WebUrl = ""
[string] $LastDefaultChange = ""
[string] $HasTest = ""
[string] $LastTestChange = ""
[string] $HasAccept = ""
[string] $LastAcceptChange = ""
[string] $Id = "" # Repository unique identifier
[string] $Name = "" # Repository display name
[string] $DefaultBranch = "" # Repository default branch (e.g., main, master)
[string] $IsDisabled = "" # Whether the repository is disabled (True/False)
[string] $WebUrl = "" # Repository web URL in Azure DevOps
[string] $LastDefaultChange = "" # Date of last commit on default branch
[string] $HasTest = "" # Whether 'test' branch exists (True/False)
[string] $LastTestChange = "" # Date of last commit on 'test' branch
[string] $HasAccept = "" # Whether 'accept' branch exists (True/False)
[string] $LastAcceptChange = "" # Date of last commit on 'accept' branch
}
# Initialize variables for API calls
[string] $url = ""
[string] $repositoryId = ""
[string] $branchName = ""
# Generate timestamped filename for the output CSV
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date repositories with test and accept.csv"
[string] $token = "yixqmupncd3b72zij4y5lfsenepak5rtvlba3sj33tvxvc4s7a6q" #"{INSERT_PERSONAL_ACCESS_TOKEN}"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$head = @{ Authorization =" Basic $token" }
[string] $organization = "effectory"
[string] $project = "Survey%20Software"
# Prepare authentication for Azure DevOps REST API calls
# Personal Access Token must be base64 encoded with a colon prefix
$encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($Token)"))
$head = @{ Authorization =" Basic $encodedToken" }
# URL-encode the project name for API calls
$projectEncoded = $Project -replace " ", "%20"
# Display script execution banner
Write-Host "========================================================================================================================================================================"
Write-Host "Creating repository overview."
Write-Host "Analyzing repositories for 'test' and 'accept' branches"
Write-Host "Organization: $Organization, Project: $Project"
Write-Host "Output file: $fileName"
Write-Host "========================================================================================================================================================================"
$repos = az repos list --organization "https://dev.azure.com/$organization/" --project "survey software" | ConvertFrom-Json | Select-Object
# Retrieve all repositories from the Azure DevOps project
Write-Host "Fetching repositories from project '$Project'..." -ForegroundColor Yellow
$repos = az repos list --organization "https://dev.azure.com/$Organization/" --project "$Project" | ConvertFrom-Json | Select-Object
Write-Host "Found $($repos.Count) repositories" -ForegroundColor Green
# Initialize array to store repository analysis results
[Repository[]]$Result = @()
# Process each repository to analyze branch structure and activity
foreach ($repo in $repos)
{
Write-Host $repo.name
Write-Host "Analyzing repository: $($repo.name)" -ForegroundColor Cyan
# Create new repository object and populate basic information
[Repository] $repository = [Repository]::new()
$repository.Id = $repo.id
$repository.Name = $repo.name
@@ -43,50 +146,96 @@ foreach ($repo in $repos)
$repository.IsDisabled = $repo.isDisabled
$repository.WebUrl = $repo.webUrl
# Only analyze branches for active repositories
if ($true -ne $repo.isDisabled)
{
$repositoryId = $repo.id
# Analyze default branch activity
$branchName = $repo.defaultBranch
$branchName = $branchName.Replace("refs/heads/", "")
Write-Host " Checking default branch: $branchName" -ForegroundColor Gray
try {
$url="https://dev.azure.com/$organization/$project/_apis/git/repositories/$repositoryId/commits?searchCriteria.itemVersion.version=$branchName&searchCriteria.`$top=1&api-version=6.0"
$url="https://dev.azure.com/$Organization/$projectEncoded/_apis/git/repositories/$repositoryId/commits?searchCriteria.itemVersion.version=$branchName&searchCriteria.`$top=1&api-version=6.0"
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
$repository.LastDefaultChange = $response.value[0].committer.date
Write-Host " Last commit: $(Get-Date $response.value[0].committer.date -Format 'yyyy-MM-dd HH:mm')" -ForegroundColor Gray
}
catch {
Write-Host " No commits found or branch inaccessible" -ForegroundColor Yellow
$repository.LastDefaultChange = ""
}
# Check for 'test' branch existence and activity
Write-Host " Checking for 'test' branch..." -ForegroundColor Gray
try {
$branchName = "test"
$url="https://dev.azure.com/$organization/$project/_apis/git/repositories/$repositoryId/commits?searchCriteria.itemVersion.version=$branchName&searchCriteria.`$top=1&api-version=6.0"
$url="https://dev.azure.com/$Organization/$projectEncoded/_apis/git/repositories/$repositoryId/commits?searchCriteria.itemVersion.version=$branchName&searchCriteria.`$top=1&api-version=6.0"
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
$repository.HasTest = "True"
$repository.LastTestChange = $response.value[0].committer.date
Write-Host " 'test' branch found - Last commit: $(Get-Date $response.value[0].committer.date -Format 'yyyy-MM-dd HH:mm')" -ForegroundColor Green
}
catch {
$repository.HasTest = "False"
$repository.LastTestChange = ""
Write-Host " 'test' branch not found" -ForegroundColor Yellow
}
# Check for 'accept' branch existence and activity
Write-Host " Checking for 'accept' branch..." -ForegroundColor Gray
try {
$branchName = "accept"
$url="https://dev.azure.com/$organization/$project/_apis/git/repositories/$repositoryId/commits?searchCriteria.itemVersion.version=$branchName&searchCriteria.`$top=1&api-version=6.0"
$url="https://dev.azure.com/$Organization/$projectEncoded/_apis/git/repositories/$repositoryId/commits?searchCriteria.itemVersion.version=$branchName&searchCriteria.`$top=1&api-version=6.0"
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
$repository.HasAccept = "True"
$repository.LastAcceptChange = $response.value[0].committer.date
Write-Host " 'accept' branch found - Last commit: $(Get-Date $response.value[0].committer.date -Format 'yyyy-MM-dd HH:mm')" -ForegroundColor Green
}
catch {
$repository.HasAccept = "False"
$repository.LastAcceptChange = ""
Write-Host " 'accept' branch not found" -ForegroundColor Yellow
}
}
else
{
Write-Host " Repository is disabled - skipping branch analysis" -ForegroundColor Yellow
# Set default values for disabled repositories
$repository.LastDefaultChange = ""
$repository.HasTest = "N/A"
$repository.LastTestChange = ""
$repository.HasAccept = "N/A"
$repository.LastAcceptChange = ""
}
# Add repository to results array
$Result += $repository
}
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
# Export results to CSV file
$Result | Export-Csv -Path $fileName -NoTypeInformation
# Calculate and display summary statistics
$totalRepos = $Result.Count
$activeRepos = ($Result | Where-Object { $_.IsDisabled -ne "True" }).Count
$disabledRepos = ($Result | Where-Object { $_.IsDisabled -eq "True" }).Count
$reposWithTest = ($Result | Where-Object { $_.HasTest -eq "True" }).Count
$reposWithAccept = ($Result | Where-Object { $_.HasAccept -eq "True" }).Count
$reposWithBoth = ($Result | Where-Object { $_.HasTest -eq "True" -and $_.HasAccept -eq "True" }).Count
# Display completion summary
Write-Host "========================================================================================================================================================================"
Write-Host "Done."
Write-Host "Branch analysis completed successfully!" -ForegroundColor Green
Write-Host ""
Write-Host "SUMMARY STATISTICS:" -ForegroundColor Cyan
Write-Host "Total repositories: $totalRepos" -ForegroundColor Yellow
Write-Host "Active repositories: $activeRepos" -ForegroundColor Yellow
Write-Host "Disabled repositories: $disabledRepos" -ForegroundColor Yellow
Write-Host "Repositories with 'test' branch: $reposWithTest" -ForegroundColor Yellow
Write-Host "Repositories with 'accept' branch: $reposWithAccept" -ForegroundColor Yellow
Write-Host "Repositories with both 'test' and 'accept' branches: $reposWithBoth" -ForegroundColor Yellow
Write-Host ""
Write-Host "Output file: $fileName" -ForegroundColor Yellow
Write-Host "========================================================================================================================================================================"

View File

@@ -1,55 +1,256 @@
<#
.SYNOPSIS
Exports Azure DevOps service connections with detailed service principal information to a CSV file.
.DESCRIPTION
This script retrieves all service connections from an Azure DevOps project and analyzes their
associated service principals. It combines information from Azure DevOps REST API and Azure
PowerShell cmdlets to provide comprehensive details about:
- Service connection metadata (ID, name, status, authorization scheme)
- Associated service principal details (Application ID, Object ID, display name)
- Service principal credential expiration dates
This is particularly useful for security auditing and monitoring service principal
credential expiration to prevent service disruptions.
.PARAMETER Token
The Azure DevOps Personal Access Token (PAT) used for authentication. This token must have
'Service Connections (Read)' permissions to access service endpoint information.
.PARAMETER Organization
The Azure DevOps organization name. Defaults to "effectory" if not specified.
.PARAMETER Project
The Azure DevOps project name. Defaults to "Survey Software" if not specified.
.EXAMPLE
.\ServiceConnections.ps1 -Token "your-personal-access-token"
Analyzes service connections using the default organization and project settings.
.EXAMPLE
.\ServiceConnections.ps1 -Token "your-pat-token" -Organization "myorg" -Project "MyProject"
Analyzes service connections for a specific organization and project.
.OUTPUTS
Creates a timestamped CSV file in the current directory with the format: "yyyy-MM-dd HHmm serviceconnections.csv"
The CSV contains the following columns:
- Id: Service connection unique identifier
- Name: Service connection display name
- OperationStatus: Current operational status of the service connection
- AuthorizationScheme: Authentication method used (e.g., ServicePrincipal, ManagedServiceIdentity)
- ServicePrincipalApplicationId: Application (Client) ID of the associated service principal
- ServicePrincipalObjectId: Object ID of the service principal in Azure AD
- ServicePrincipalName: Display name of the service principal
- ServicePrincipalEndDateTime: Expiration date of the service principal credentials
.NOTES
Author: Cloud Engineering Team
Created: 2025
Requires: PowerShell 5.1 or later, Az PowerShell module
Dependencies: Az PowerShell module must be installed and authenticated to Azure
Prerequisites:
- Install Az PowerShell module: Install-Module -Name Az
- Connect to Azure: Connect-AzAccount
- Azure DevOps Personal Access Token with Service Connections (Read) permissions
- Appropriate Azure AD permissions to read service principal information
The script combines two data sources:
1. Azure DevOps REST API - for service connection metadata
2. Azure PowerShell cmdlets - for detailed service principal information
Security Considerations:
- Monitor credential expiration dates to prevent service disruptions
- Regular auditing of service connections helps maintain security posture
- Service principals with expired credentials will cause deployment failures
Error Handling:
- If service principal information cannot be retrieved, those fields will be empty
- The script continues processing even if individual service principals fail
- Network or authentication errors are handled gracefully
.LINK
https://docs.microsoft.com/en-us/rest/api/azure/devops/serviceendpoint/endpoints
https://docs.microsoft.com/en-us/powershell/module/az.resources/get-azadserviceprincipal
#>
param(
[Parameter(Mandatory = $true, HelpMessage = "Azure DevOps Personal Access Token with Service Connections (Read) permissions")]
[string]$Token,
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps organization name")]
[string]$Organization = "effectory",
[Parameter(Mandatory = $false, HelpMessage = "Azure DevOps project name")]
[string]$Project = "Survey Software"
)
# Define a class to structure service connection information with service principal details
class ServiceConnection {
[string] $Id = ""
[string] $Name = ""
[string] $OperationStatus = ""
[string] $AuthorizationScheme = ""
[string] $ServicePrincipalApplicationId = ""
[string] $ServicePrincipalObjectId = ""
[string] $ServicePrincipalName = ""
[string] $ServicePrincipalEndDateTime = ""
[string] $Id = "" # Service connection unique identifier
[string] $Name = "" # Service connection display name
[string] $OperationStatus = "" # Current operational status
[string] $AuthorizationScheme = "" # Authentication method (e.g., ServicePrincipal)
[string] $ServicePrincipalApplicationId = "" # Application (Client) ID of service principal
[string] $ServicePrincipalObjectId = "" # Object ID of service principal in Azure AD
[string] $ServicePrincipalName = "" # Display name of service principal
[string] $ServicePrincipalEndDateTime = "" # Expiration date of service principal credentials
}
# Generate timestamped filename for the output CSV
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date serviceconnections.csv"
# Display script execution banner
Write-Host "========================================================================================================================================================================"
Write-Host "Creating service connection overview."
Write-Host "Analyzing Azure DevOps service connections and associated service principals"
Write-Host "Organization: $Organization, Project: $Project"
Write-Host "Output file: $fileName"
Write-Host "Note: This script requires Azure PowerShell (Az module) to be installed and authenticated"
Write-Host "========================================================================================================================================================================"
$token = "adlgsqh2uoedv6rf44hjd47z3ssuo5zonrqicif4ctjqlqqtlhdq"
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($token)"))
$organization = "effectory"
$project = "Survey%20Software"
# Prepare authentication for Azure DevOps REST API calls
# Personal Access Token must be base64 encoded with a colon prefix
$encodedToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($Token)"))
$url="https://dev.azure.com/$organization/$project/_apis/serviceendpoint/endpoints?api-version=6.0-preview.4"
$head = @{ Authorization =" Basic $token" }
# URL-encode the project name for API calls
$projectEncoded = $Project -replace " ", "%20"
# Check if Azure PowerShell is available and user is authenticated
Write-Host "Verifying Azure PowerShell authentication..." -ForegroundColor Yellow
try {
$azContext = Get-AzContext
if (-not $azContext) {
Write-Host "ERROR: Not authenticated to Azure. Please run 'Connect-AzAccount' first." -ForegroundColor Red
exit 1
}
Write-Host "Azure authentication verified - Tenant: $($azContext.Tenant.Id)" -ForegroundColor Green
}
catch {
Write-Host "ERROR: Azure PowerShell module not found. Please install with 'Install-Module -Name Az'" -ForegroundColor Red
exit 1
}
# Retrieve service connections from Azure DevOps
Write-Host "Fetching service connections from Azure DevOps..." -ForegroundColor Yellow
$url="https://dev.azure.com/$Organization/$projectEncoded/_apis/serviceendpoint/endpoints?api-version=6.0-preview.4"
$head = @{ Authorization =" Basic $encodedToken" }
$response = Invoke-RestMethod -Uri $url -Method GET -Headers $head
Write-Host "Found $($response.count) service connections" -ForegroundColor Green
# Initialize array to store service connection analysis results
[ServiceConnection[]]$Result = @()
# Process each service connection to gather detailed information
$response.value | ForEach-Object {
[ServiceConnection] $serviceConnection = [ServiceConnection]::new()
$serviceConnection.Id = $_.id
$serviceConnection.Name = $_.name
$serviceConnection.OperationStatus = $_.operationStatus
$serviceConnection.AuthorizationScheme = $_.authorization.scheme
Write-Host "Analyzing service connection: $($_.name)" -ForegroundColor Cyan
# Create new service connection object and populate basic information
[ServiceConnection] $serviceConnection = [ServiceConnection]::new()
$serviceConnection.Id = $_.id
$serviceConnection.Name = $_.name
$serviceConnection.OperationStatus = $_.operationStatus
$serviceConnection.AuthorizationScheme = $_.authorization.scheme
Write-Host " Authorization scheme: $($_.authorization.scheme)" -ForegroundColor Gray
Write-Host " Operation status: $($_.operationStatus)" -ForegroundColor Gray
$principalid = $_.authorization.parameters.serviceprincipalid
if ($null -ne $principalid) {
$principal = Get-AzADServicePrincipal -ApplicationId $principalid
$credential = Get-AzADAppCredential -ApplicationId $principalid
# Extract service principal information if available
$principalid = $_.authorization.parameters.serviceprincipalid
if ($null -ne $principalid) {
Write-Host " Retrieving service principal details..." -ForegroundColor Gray
try {
# Get service principal information from Azure AD
$principal = Get-AzADServicePrincipal -ApplicationId $principalid -ErrorAction Stop
$credential = Get-AzADAppCredential -ApplicationId $principalid -ErrorAction Stop
$serviceConnection.ServicePrincipalApplicationId = $principalid
$serviceConnection.ServicePrincipalObjectId = $principal.Id
$serviceConnection.ServicePrincipalName = $principal.DisplayName
$serviceConnection.ServicePrincipalEndDateTime = $credential.EndDateTime
$serviceConnection.ServicePrincipalApplicationId = $principalid
$serviceConnection.ServicePrincipalObjectId = $principal.Id
$serviceConnection.ServicePrincipalName = $principal.DisplayName
# Handle multiple credentials - get the latest expiration date
if ($credential) {
$latestExpiration = ($credential | Sort-Object EndDateTime -Descending | Select-Object -First 1).EndDateTime
$serviceConnection.ServicePrincipalEndDateTime = $latestExpiration
Write-Host " Service Principal: $($principal.DisplayName)" -ForegroundColor Gray
Write-Host " Application ID: $principalid" -ForegroundColor Gray
Write-Host " Credential expires: $latestExpiration" -ForegroundColor Gray
# Warn about expiring credentials (within 30 days)
if ($latestExpiration -and $latestExpiration -lt (Get-Date).AddDays(30)) {
Write-Host " WARNING: Credential expires within 30 days!" -ForegroundColor Red
}
}
else {
Write-Host " No credentials found for this service principal" -ForegroundColor Yellow
}
$Result += $serviceConnection
}
catch {
Write-Host " Error retrieving service principal details: $($_.Exception.Message)" -ForegroundColor Red
# Keep the Application ID even if other details fail
$serviceConnection.ServicePrincipalApplicationId = $principalid
}
}
else {
Write-Host " No service principal associated with this connection" -ForegroundColor Gray
}
# Add service connection to results array
$Result += $serviceConnection
}
$Result | Export-Csv -Path $fileName -Append -NoTypeInformation
# Export results to CSV file
$Result | Export-Csv -Path $fileName -NoTypeInformation
# Calculate and display summary statistics
$totalConnections = $Result.Count
$connectionsWithServicePrincipals = ($Result | Where-Object { $_.ServicePrincipalApplicationId -ne "" }).Count
$connectionsWithoutServicePrincipals = $totalConnections - $connectionsWithServicePrincipals
# Check for expiring credentials (within 30 days)
$expiringCredentials = $Result | Where-Object {
$_.ServicePrincipalEndDateTime -ne "" -and
[DateTime]::Parse($_.ServicePrincipalEndDateTime) -lt (Get-Date).AddDays(30)
}
# Check for expired credentials
$expiredCredentials = $Result | Where-Object {
$_.ServicePrincipalEndDateTime -ne "" -and
[DateTime]::Parse($_.ServicePrincipalEndDateTime) -lt (Get-Date)
}
# Display completion summary
Write-Host "========================================================================================================================================================================"
Write-Host "Service connection analysis completed successfully!" -ForegroundColor Green
Write-Host ""
Write-Host "SUMMARY STATISTICS:" -ForegroundColor Cyan
Write-Host "Total service connections: $totalConnections" -ForegroundColor Yellow
Write-Host "Connections with service principals: $connectionsWithServicePrincipals" -ForegroundColor Yellow
Write-Host "Connections without service principals: $connectionsWithoutServicePrincipals" -ForegroundColor Yellow
if ($expiredCredentials.Count -gt 0) {
Write-Host ""
Write-Host "EXPIRED CREDENTIALS (IMMEDIATE ATTENTION REQUIRED):" -ForegroundColor Red
$expiredCredentials | ForEach-Object {
Write-Host " - $($_.Name): $($_.ServicePrincipalName) (expired $($_.ServicePrincipalEndDateTime))" -ForegroundColor Red
}
}
if ($expiringCredentials.Count -gt 0) {
Write-Host ""
Write-Host "EXPIRING CREDENTIALS (WITHIN 30 DAYS):" -ForegroundColor Yellow
$expiringCredentials | ForEach-Object {
Write-Host " - $($_.Name): $($_.ServicePrincipalName) (expires $($_.ServicePrincipalEndDateTime))" -ForegroundColor Yellow
}
}
Write-Host ""
Write-Host "Output file: $fileName" -ForegroundColor Yellow
Write-Host "========================================================================================================================================================================"
Write-Host "Done."

View File

@@ -1,20 +1,129 @@
# PowerShell script to analyze renovate PRs across repositories with detailed statistics
<#
.SYNOPSIS
Analyzes Renovate pull requests across all repositories in an Azure DevOps project and generates detailed statistics.
.DESCRIPTION
This script connects to an Azure DevOps organization and project to analyze Renovate dependency update pull requests.
It processes all repositories (active, disabled, and locked) and generates comprehensive statistics including:
- Total Renovate PRs per repository
- Open, completed, and abandoned PR counts
- Latest creation and completion dates
- Branch information for latest PRs
- Repository status (active, disabled, locked, error)
The script outputs both a detailed text report and a CSV file for further analysis.
Renovate PRs are identified by their branch naming pattern: "refs/heads/renovate/*"
.PARAMETER Organization
The Azure DevOps organization name. Defaults to "effectory" if not specified.
.PARAMETER Project
The Azure DevOps project name. Defaults to "Survey Software" if not specified.
.PARAMETER PAT
The Azure DevOps Personal Access Token (PAT) used for authentication. This token must have
'Code (Read)' permissions to access repository and pull request information.
.PARAMETER OutputFile
The path and filename for the output text report. Defaults to a timestamped filename:
"RenovatePRs_Stats_yyyyMMdd_HHmmss.txt"
.EXAMPLE
.\renovate-stats.ps1 -PAT "your-personal-access-token"
Analyzes Renovate PRs using default organization and project settings.
.EXAMPLE
.\renovate-stats.ps1 -PAT "your-pat-token" -Organization "myorg" -Project "MyProject"
Analyzes Renovate PRs for a specific organization and project.
.EXAMPLE
.\renovate-stats.ps1 -PAT "your-token" -OutputFile "C:\Reports\renovate-analysis.txt"
Analyzes Renovate PRs and saves the report to a custom location.
.OUTPUTS
Creates two output files:
1. Text Report: Detailed statistics with tables and summaries (.txt)
2. CSV Export: Raw data for further analysis (.csv)
The text report includes:
- Repository-by-repository statistics table (sorted by last created date)
- Summary statistics (total repos, repos with/without Renovate, PR counts)
- List of disabled/locked/error repositories
The CSV contains columns:
- Repository: Repository name
- TotalRenovatePRs: Total count of Renovate PRs
- OpenPRs: Count of active Renovate PRs
- CompletedPRs: Count of completed Renovate PRs
- AbandonedPRs: Count of abandoned Renovate PRs
- LastCreated: Date of most recent Renovate PR creation (yyyy-MM-dd format)
- LastCompleted: Date of most recent Renovate PR completion (yyyy-MM-dd format)
- LatestOpenBranch: Branch name of most recent open Renovate PR
- LatestCompletedBranch: Branch name of most recent completed Renovate PR
- LastCompletedPRTitle: Title of most recent completed Renovate PR
.NOTES
Author: Cloud Engineering Team
Created: 2025
Requires: PowerShell 5.1 or later
Dependencies: Internet connectivity to Azure DevOps
The script uses Azure DevOps REST API version 6.0 to retrieve repository and pull request information.
Ensure your Personal Access Token has the necessary permissions before running the script.
Renovate is a dependency update tool that creates automated pull requests. This script specifically
looks for PRs with branch names matching the pattern "refs/heads/renovate/*"
The script handles various repository states:
- Active: Normal repositories that can be analyzed
- Disabled: Repositories marked as disabled in Azure DevOps
- Locked: Repositories that are locked (read-only)
- Error: Repositories that couldn't be accessed due to API errors
Date parsing is culture-invariant and includes fallback mechanisms to handle various date formats
from the Azure DevOps API.
.LINK
https://docs.microsoft.com/en-us/rest/api/azure/devops/git/pull-requests/get-pull-requests
https://renovatebot.com/
#>
param(
[Parameter(Mandatory=$false)]
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps organization name")]
[string]$Organization = "effectory",
[Parameter(Mandatory=$false)]
[Parameter(Mandatory=$false, HelpMessage="Azure DevOps project name")]
[string]$Project = "Survey Software",
[Parameter(Mandatory=$true)]
[Parameter(Mandatory=$true, HelpMessage="Azure DevOps Personal Access Token with Code (Read) permissions")]
[string]$PAT,
[Parameter(Mandatory=$false)]
[Parameter(Mandatory=$false, HelpMessage="Output file path for the text report")]
[string]$OutputFile = "RenovatePRs_Stats_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
)
<#
.SYNOPSIS
Outputs a message to both the console and the output file.
.DESCRIPTION
This helper function writes messages to the console with optional color formatting
and simultaneously appends the same message to the output file, unless ConsoleOnly is specified.
.PARAMETER Message
The message to output.
.PARAMETER ForegroundColor
The console text color. Defaults to "White".
.PARAMETER ConsoleOnly
If specified, the message will only be written to the console and not the output file.
Useful for progress messages that shouldn't clutter the report.
#>
function Write-Output-Both {
param (
[string]$Message,
@@ -29,28 +138,34 @@ function Write-Output-Both {
}
# Initialize the output file with a header
Set-Content -Path $OutputFile -Value "Renovate Pull Requests Statistics - $(Get-Date)`n"
# Prepare authentication for Azure DevOps REST API calls
# Personal Access Token must be base64 encoded with a colon prefix
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$PAT"))
$headers = @{
Authorization = "Basic $base64AuthInfo"
}
# Retrieve all repositories from the Azure DevOps project
$reposUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories?api-version=6.0"
$repositories = Invoke-RestMethod -Uri $reposUrl -Method Get -Headers $headers
$repoStats = @()
$reposWithoutRenovate = @()
$disabledRepos = @()
# Initialize arrays to categorize repositories and store statistics
$repoStats = @() # Active repositories with detailed statistics
$reposWithoutRenovate = @() # Active repositories with no Renovate PRs
$disabledRepos = @() # Disabled, locked, or error repositories
# Process each repository in the project
foreach ($repo in $repositories.value) {
$repoName = $repo.name
$repoId = $repo.id
Write-Output-Both "Analyzing repository: $repoName" -ForegroundColor Gray -ConsoleOnly
# Check repository status (disabled, locked, or active)
$isDisabled = $repo.isDisabled -eq $true
$repoDetailsUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$repoId`?api-version=6.0"
try {
@@ -62,10 +177,12 @@ foreach ($repo in $repositories.value) {
$isLocked = $false
}
# Skip analysis for disabled or locked repositories
if ($isDisabled -or $isLocked) {
$status = if ($isDisabled) { "DISABLED" } elseif ($isLocked) { "LOCKED" } else { "UNKNOWN" }
Write-Output-Both " Repository status: $status - Skipping analysis" -ForegroundColor Yellow -ConsoleOnly
# Create entry for disabled/locked repository with N/A values
$disabledRepo = [PSCustomObject]@{
Repository = "$repoName ($status)"
TotalRenovatePRs = "N/A"
@@ -83,23 +200,31 @@ foreach ($repo in $repositories.value) {
continue
}
# Retrieve all pull requests for the current repository
$prsUrl = "https://dev.azure.com/$Organization/$Project/_apis/git/repositories/$repoId/pullrequests`?api-version=6.0&searchCriteria.status=all"
try {
$pullRequests = Invoke-RestMethod -Uri $prsUrl -Method Get -Headers $headers
# Filter for Renovate PRs based on branch naming pattern
$renovatePRs = $pullRequests.value | Where-Object { $_.sourceRefName -like "refs/heads/renovate/*" }
if ($renovatePRs.Count -gt 0) {
# Categorize Renovate PRs by status
$openPRs = $renovatePRs | Where-Object { $_.status -eq "active" }
$completedPRs = $renovatePRs | Where-Object { $_.status -eq "completed" }
$abandonedPRs = $renovatePRs | Where-Object { $_.status -eq "abandoned" }
# Count PRs in each category
$openCount = $openPRs.Count
$completedCount = $completedPRs.Count
$abandonedCount = $abandonedPRs.Count
# Find the most recent PRs in each category for detailed reporting
$latestOpen = $openPRs | Sort-Object creationDate -Descending | Select-Object -First 1
$latestCompleted = $completedPRs | Sort-Object closedDate -Descending | Select-Object -First 1
$latestCreated = $renovatePRs | Sort-Object creationDate -Descending | Select-Object -First 1
# Extract key information from the latest PRs
$lastCreatedDate = if ($latestCreated) { $latestCreated.creationDate } else { "N/A" }
$lastCompletedDate = if ($latestCompleted) { $latestCompleted.closedDate } else { "N/A" }
$lastCompletedPRTitle = if ($latestCompleted) { $latestCompleted.title } else { "N/A" }
@@ -118,7 +243,20 @@ foreach ($repo in $repositories.value) {
LatestCompletedBranch = $latestCompletedBranch
LastCompletedPRTitle = $lastCompletedPRTitle
RepoStatus = "ACTIVE"
SortDate = if ($lastCreatedDate -eq "N/A") { [DateTime]::MinValue } else { [DateTime]::Parse($lastCreatedDate) }
SortDate = if ($lastCreatedDate -eq "N/A") {
[DateTime]::MinValue
} else {
try {
[DateTime]::Parse($lastCreatedDate, [System.Globalization.CultureInfo]::InvariantCulture)
} catch {
try {
[DateTime]::ParseExact($lastCreatedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture)
} catch {
Write-Output-Both " Warning: Could not parse date '$lastCreatedDate' for repository $repoName" -ForegroundColor Yellow -ConsoleOnly
[DateTime]::MinValue
}
}
}
}
$repoStats += $repoStat
@@ -160,41 +298,71 @@ foreach ($repo in $repositories.value) {
}
}
# Generate and display the main statistics report
Write-Output-Both "`n===== RENOVATE PULL REQUEST STATISTICS BY REPOSITORY (SORTED BY LAST CREATED DATE) =====" -ForegroundColor Green
if ($repoStats.Count -gt 0) {
# Sort repositories by last created date (most recent first)
$sortedStats = $repoStats | Sort-Object -Property SortDate -Descending
# Format the data for display with culture-invariant date parsing
$displayStats = $sortedStats | Select-Object Repository, TotalRenovatePRs, OpenPRs, CompletedPRs, AbandonedPRs,
@{Name="LastCreated"; Expression={
if ($_.LastCreatedDate -eq "N/A" -or $_.LastCreatedDate -eq "Error") { $_.LastCreatedDate }
else { [DateTime]::Parse($_.LastCreatedDate).ToString("yyyy-MM-dd") }
if ($_.LastCreatedDate -eq "N/A" -or $_.LastCreatedDate -eq "Error") {
$_.LastCreatedDate
} else {
try {
[DateTime]::Parse($_.LastCreatedDate, [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
try {
[DateTime]::ParseExact($_.LastCreatedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
$_.LastCreatedDate
}
}
}
}},
@{Name="LastCompleted"; Expression={
if ($_.LastCompletedDate -eq "N/A" -or $_.LastCompletedDate -eq "Error") { $_.LastCompletedDate }
else { [DateTime]::Parse($_.LastCompletedDate).ToString("yyyy-MM-dd") }
if ($_.LastCompletedDate -eq "N/A" -or $_.LastCompletedDate -eq "Error") {
$_.LastCompletedDate
} else {
try {
[DateTime]::Parse($_.LastCompletedDate, [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
try {
[DateTime]::ParseExact($_.LastCompletedDate, "MM/dd/yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture).ToString("yyyy-MM-dd")
} catch {
$_.LastCompletedDate
}
}
}
}},
LatestOpenBranch, LatestCompletedBranch, LastCompletedPRTitle
# Export detailed data to CSV for further analysis
$displayStats | Export-Csv -Path "$($OutputFile).csv" -NoTypeInformation
# Add a note to the original output file
Add-Content -Path $OutputFile -Value "Full data available in: $($OutputFile).csv"
# Add formatted table to the text report and display on console
$statsTable = $displayStats | Format-Table -Property Repository, TotalRenovatePRs, OpenPRs, CompletedPRs, AbandonedPRs, LastCreated, LastCompleted, LatestCompletedBranch, LastCompletedPRTitle | Out-String
Add-Content -Path $OutputFile -Value $statsTable.Trim() # Trim to remove extra whitespace
Add-Content -Path $OutputFile -Value $statsTable.Trim()
$displayStats | Format-Table -AutoSize
# Calculate summary statistics
$totalRepos = $repositories.value.Count
$reposWithRenovate = ($repoStats | Where-Object { $_.TotalRenovatePRs -gt 0 }).Count
$reposDisabledOrLocked = $disabledRepos.Count
$activeRepos = $totalRepos - $reposDisabledOrLocked
# Calculate percentage of active repositories with Renovate PRs
$percentWithRenovate = if ($activeRepos -gt 0) { [math]::Round(($reposWithRenovate / $activeRepos) * 100, 2) } else { 0 }
# Calculate totals across all active repositories
$totalPRs = ($repoStats | Measure-Object -Property TotalRenovatePRs -Sum).Sum
$totalOpenPRs = ($repoStats | Measure-Object -Property OpenPRs -Sum).Sum
$totalCompletedPRs = ($repoStats | Measure-Object -Property CompletedPRs -Sum).Sum
$totalAbandonedPRs = ($repoStats | Measure-Object -Property AbandonedPRs -Sum).Sum
# Display comprehensive summary statistics
Write-Output-Both "`n===== SUMMARY STATISTICS =====" -ForegroundColor Cyan
Write-Output-Both "Total repositories: $totalRepos"
Write-Output-Both "Disabled, locked, or error repositories: $reposDisabledOrLocked"
@@ -206,6 +374,7 @@ if ($repoStats.Count -gt 0) {
Write-Output-Both "Total completed renovate PRs: $totalCompletedPRs"
Write-Output-Both "Total abandoned renovate PRs: $totalAbandonedPRs"
# Display list of repositories that couldn't be analyzed
if ($disabledRepos.Count -gt 0) {
Write-Output-Both "`n===== DISABLED/LOCKED/ERROR REPOSITORIES (NOT INCLUDED IN MAIN REPORT) =====" -ForegroundColor Yellow
$disabledList = $disabledRepos | ForEach-Object { $_.Repository }
@@ -215,4 +384,5 @@ if ($repoStats.Count -gt 0) {
Write-Output-Both "No active repositories found." -ForegroundColor Yellow
}
# Display completion message
Write-Output-Both "`nReport saved to: $OutputFile" -ForegroundColor Cyan