Files
Cloud-20Engineering/Powershell/Lists/DevOps/ServiceConnections.ps1
Jurjen Ladenius a226ca97ac added documetation
2025-11-03 08:12:01 +01:00

256 lines
12 KiB
PowerShell

<#
.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 = "" # 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 "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 "========================================================================================================================================================================"
# 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-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 {
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
# 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
# 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
}
}
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
}
# 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 "========================================================================================================================================================================"