mirror of
https://dev.azure.com/effectory/Survey%20Software/_git/Cloud%20Engineering
synced 2026-02-27 18:52:18 +01:00
added documetation
This commit is contained in:
@@ -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 "========================================================================================================================================================================"
|
||||
@@ -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 "========================================================================================================================================================================"
|
||||
@@ -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 "========================================================================================================================================================================"
|
||||
@@ -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 "========================================================================================================================================================================"
|
||||
@@ -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."
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user