Managing Azure DevOps service connections at scale gets messy fast. Over time, organisations end up with a mixture of active connections, outdated configurations, orphaned service principals, and authentication methods that no longer reflect current security best practices.

I recently used PowerShell to investigate service connections across an Azure DevOps organisation, identify problems, and recommend actions, including conversion from legacy Service Principal authentication to Workload Identity Federation (WIF).

TL;DR

The approach in this post covers four practical outcomes:

Why Investigate Service Connections?

Service connections are critical for pipelines, but they often accumulate quietly in the background. Some are still valid, some are stale, and some are effectively broken.

If you want a cleaner and more secure Azure DevOps estate, you need visibility first.

Moving to WIF helps because it removes dependency on stored secrets and aligns pipeline authentication with a more modern and maintainable approach.

The Investigation Script

The first step was to build a script that could inspect service connections across every project and enrich the raw Azure DevOps data with context from Azure and Microsoft Graph.

That script automated the following:

Here is a representative excerpt from the investigation workflow:

foreach ($serviceEndpoint in $serviceEndpoints) {
    $resultObject = [PSCustomObject]@{
        ProjectName         = $project.name
        ServiceEndpointName = $serviceEndpoint.name
        AuthScheme          = $serviceEndpoint.authorization.scheme
    }

    if ($serviceEndpoint.data.subscriptionid) {
        # ...lookup subscription...
    } elseif ($serviceEndpoint.data.managementGroupName) {
        # ...lookup management group...
    } else {
        # ...not found...
    }

    if ($serviceEndpoint.authorization.scheme -eq "ServicePrincipal") {
        # ...lookup SPN and secrets...
    }

    if ($resultObject.AuthScheme -eq "ServicePrincipal" -and $serviceEndpoint.name -notlike "*avs*") {
        $advice = "Convert to WIF"
        $action = "Convert to WIF"
    } else {
        $advice = "No action required"
        $action = "No action required"
    }

    $resultObject | Add-Member -NotePropertyName Advice -NotePropertyValue $advice
    $resultObject | Add-Member -NotePropertyName Action -NotePropertyValue $action

    $results += $resultObject
}

The script produced an output file with the status of every connection and a clear recommendation for what should happen next.

Typical findings included:

The full investigation script is available here:

Cleaning Up Service Connections with PowerShell

Once the investigation had identified what needed attention, the next step was to automate the clean-up and conversion process.

The clean-up script was designed to be interactive so that conversions and deletions could still be reviewed carefully before anything changed.

It supported scenarios like:

An excerpt from that flow looked like this:

$OrganizationUrl = "https://dev.azure.com/YOUR_ORG"

while ($true) {
    $selectedProjectName = Read-Host "Enter the name of the project you want to select (or type 'skip' to not pick a specific project, or 'exit' to quit)"

    foreach ($project in $projectsToProcess) {
        while ($true) {
            $selectedServiceEndpointName = Read-Host "Enter the name of the service endpoint you want to select (or type 'skip' to process all endpoints, or 'exit' to quit this project)"

            foreach ($serviceEndpoint in $serviceEndpoints) {
                $json = '{"name": "ADO-' + ($project.name -replace " ", "") + '", "issuer": "https://vstoken.dev.azure.com/YOUR_ORG_ID", "subject": "sc://YOUR_ORG/' + $project.name + '/' + $serviceEndpoint.name + '", "audiences": ["api://AzureADTokenExchange"]}'
                az ad app federated-credential create --id $serviceEndpoint.authorization.parameters.serviceprincipalid --parameters $json

                $convertBody = @{
                    id = $serviceEndpoint.id
                    type = "AzureRM"
                    authorization = @{ scheme = "WorkloadIdentityFederation" }
                } | ConvertTo-Json -Depth 100 -Compress

                $putApiUrl = "${OrganizationUrl}/_apis/serviceendpoint/endpoints/$($serviceEndpoint.id)?operation=ConvertAuthenticationScheme&api-version=${apiVersion}"
                Invoke-RestMethod -Uri $putApiUrl -Method Put -Body $convertBody -Headers $header
            }
        }
    }
}

The full clean-up script is here:

Benefits

This approach reduced manual effort significantly and made it much easier to modernise service connection authentication safely.

The biggest advantages were:

Conclusion

Auditing and converting Azure DevOps service connections does not need to be a slow, manual process.

With a focused PowerShell workflow, it is possible to investigate the current state, identify weak spots, and migrate eligible connections to Workload Identity Federation in a controlled way.

The result is a cleaner, more secure, and more maintainable Azure DevOps environment.