Automating Searchable Branch Configuration in Azure DevOps Repos via REST API

Automating Searchable Branch Configuration in Azure DevOps Repos via REST API


🎯 TL;DR: Bulk Configure Searchable Branches in Azure DevOps via Hidden Policy API

Azure DevOps code search only indexes the default branch (master/main) by default, causing issues when teams use develop branches for JFrog Artifactory detection scripts. Problem: No documented API exists for bulk updating searchable branches across thousands of repositories. Solution: Use the undocumented Policy Configuration API with policy type 0517f88d-4ec5-4343-9d26-9930ebd53069 to programmatically add branches to the searchable list. This approach leverages the same API calls the Azure DevOps UI uses internally, enabling automation of what would otherwise require manual configuration across massive repository collections.


Recently, I encountered an interesting challenge while working on a JFrog Artifactory adoption tracking project across a large Azure DevOps organization. The requirement was to scan repositories for JFrog URL references to determine which teams had successfully onboarded to their new artifact management system. The problem? Some development teams exclusively work in develop branches instead of master or main, and Azure DevOps code search only indexes the default branch by default.

This seemingly simple requirement - adding develop to the searchable branches for thousands of repositories - turned into a fascinating exploration of Azure DevOps’ undocumented APIs. While there’s no official documentation for bulk updating searchable branches, I discovered that the Azure DevOps UI uses a specific Policy Configuration API under the hood that we can leverage for automation.

This blog post shares a practical approach to programmatically configure searchable branches across large Azure DevOps organizations using REST APIs that Microsoft doesn’t officially document but absolutely supports.

The Challenge: Azure DevOps Code Search Limitations

Azure DevOps code search is a powerful feature, but it comes with a significant limitation that affects many organizations: by default, only the repository’s default branch (typically master or main) is indexed for search operations.

This creates problems in several scenarios:

JFrog Adoption Tracking: Organizations implementing JFrog Artifactory need to scan all repositories for configuration files and dependency references, but teams using feature branches or develop as their primary branch won’t be detected.

Multi-Branch Development: Teams practicing GitFlow or similar branching strategies may have critical code in develop, release/*, or feature branches that needs to be searchable.

Compliance and Security Scanning: Security tools and compliance scripts that rely on code search may miss important files if they’re not in the default branch.

Understanding Azure DevOps Searchable Branches

In Azure DevOps, searchable branches are configured at the repository level through the UI:

Manual Configuration Path:

  1. Navigate to Project Settings → Repositories
  2. Select your repository
  3. Go to Settings → Searchable Branches
  4. Add additional branches (maximum of 5 extra branches)

What Actually Happens:
When you configure searchable branches through the UI, Azure DevOps creates or updates a policy configuration with a specific policy type. This policy type - 0517f88d-4ec5-4343-9d26-9930ebd53069 - controls which branches are indexed for code search.

The Discovery: Hidden Policy Configuration API

After extensive research and reverse engineering the Azure DevOps UI network traffic, I discovered that searchable branch configuration is managed through the Policy Configuration API with a specific, undocumented policy type.

Key Insights:

  • Azure DevOps doesn’t expose a direct “searchable branches” API endpoint
  • The UI uses the Policy Configuration API with policy type 0517f88d-4ec5-4343-9d26-9930ebd53069
  • This API is officially supported but not documented for this specific use case
  • The same approach works for both Azure DevOps Server and Azure DevOps Cloud

The Solution: Automated Policy Configuration

The approach involves three main steps:

Step 1: Retrieve Existing Policy Configuration

First, we need to check if a searchable branches policy already exists for the repository:

1
2
3
4
5
6
7
8
9
# Get existing searchable branch policy for a repository
$policyUrl = "https://dev.azure.com/{organization}/{project}/_apis/policy/configurations"
$params = @{
'repositoryId' = $repositoryId
'policyType' = '0517f88d-4ec5-4343-9d26-9930ebd53069'
'api-version' = '7.1-preview.1'
}

$existingPolicy = Invoke-RestMethod -Uri $policyUrl -Headers $headers -Method Get -Body $params

Step 2: Create or Update Policy Configuration

If a policy exists, we update it. If not, we create a new one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Policy configuration for searchable branches
$policySettings = @{
'type' = @{
'id' = '0517f88d-4ec5-4343-9d26-9930ebd53069'
}
'isEnabled' = $true
'isBlocking' = $false
'settings' = @{
'searchBranches' = @(
'refs/heads/master',
'refs/heads/main',
'refs/heads/develop'
)
}
'scope' = @(
@{
'repositoryId' = $repositoryId
'refName' = 'refs/heads/master'
'matchKind' = 'exact'
}
)
}

Step 3: Apply the Configuration

Send the policy configuration to Azure DevOps:

1
2
3
4
5
6
7
8
9
10
if ($existingPolicy.count -gt 0) {
# Update existing policy
$configId = $existingPolicy.value[0].id
$updateUrl = "https://dev.azure.com/{organization}/{project}/_apis/policy/configurations/$configId"
Invoke-RestMethod -Uri $updateUrl -Headers $headers -Method Put -Body ($policySettings | ConvertTo-Json -Depth 10) -ContentType "application/json"
} else {
# Create new policy
$createUrl = "https://dev.azure.com/{organization}/{project}/_apis/policy/configurations"
Invoke-RestMethod -Uri $createUrl -Headers $headers -Method Post -Body ($policySettings | ConvertTo-Json -Depth 10) -ContentType "application/json"
}

Complete PowerShell Implementation

Here’s a complete script that implements this solution for bulk updating searchable branches across multiple repositories:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# Azure DevOps configuration
$organization = "your-org"
$project = "your-project"
$pat = "your-personal-access-token"

# Create authentication headers
$headers = @{
'Authorization' = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$pat"))
'Content-Type' = 'application/json'
}

# Function to update searchable branches for a repository
function Update-SearchableBranches {
param(
[string]$organizationName,
[string]$projectName,
[string]$repositoryId,
[array]$branches,
[hashtable]$authHeaders
)

$policyTypeId = '0517f88d-4ec5-4343-9d26-9930ebd53069'
$baseUrl = "https://dev.azure.com/$organizationName/$projectName/_apis/policy/configurations"

try {
# Check for existing policy
$getUrl = "$baseUrl" + "?repositoryId=$repositoryId&policyType=$policyTypeId&api-version=7.1-preview.1"
$existingPolicy = Invoke-RestMethod -Uri $getUrl -Headers $authHeaders -Method Get

# Format branches with refs/heads/ prefix
$searchBranches = $branches | ForEach-Object {
if ($_ -like "refs/heads/*") { $_ } else { "refs/heads/$_" }
}

# Create policy configuration
$policyConfig = @{
'type' = @{ 'id' = $policyTypeId }
'isEnabled' = $true
'isBlocking' = $false
'settings' = @{
'searchBranches' = $searchBranches
}
'scope' = @(
@{
'repositoryId' = $repositoryId
'refName' = $searchBranches[0]
'matchKind' = 'exact'
}
)
}

$jsonBody = $policyConfig | ConvertTo-Json -Depth 10

if ($existingPolicy.count -gt 0) {
# Update existing policy
$configId = $existingPolicy.value[0].id
$updateUrl = "$baseUrl/$configId" + "?api-version=7.1-preview.1"
$result = Invoke-RestMethod -Uri $updateUrl -Headers $authHeaders -Method Put -Body $jsonBody
Write-Host "Updated searchable branches for repository $repositoryId" -ForegroundColor Green
} else {
# Create new policy
$createUrl = "$baseUrl" + "?api-version=7.1-preview.1"
$result = Invoke-RestMethod -Uri $createUrl -Headers $authHeaders -Method Post -Body $jsonBody
Write-Host "Created searchable branches policy for repository $repositoryId" -ForegroundColor Green
}

return $result
}
catch {
Write-Error "Failed to update searchable branches for repository $repositoryId`: $($_.Exception.Message)"
return $null
}
}

# Get all repositories in the project
$reposUrl = "https://dev.azure.com/$organization/$project/_apis/git/repositories?api-version=7.1"
$repositories = Invoke-RestMethod -Uri $reposUrl -Headers $headers

# Define branches to make searchable
$searchableBranches = @('master', 'main', 'develop')

# Update searchable branches for each repository
foreach ($repo in $repositories.value) {
Write-Host "Processing repository: $($repo.name)" -ForegroundColor Yellow

$result = Update-SearchableBranches -organizationName $organization -projectName $project -repositoryId $repo.id -branches $searchableBranches -authHeaders $headers

if ($result) {
Write-Host "Successfully configured searchable branches for $($repo.name)" -ForegroundColor Green
} else {
Write-Host "Failed to configure searchable branches for $($repo.name)" -ForegroundColor Red
}

# Add delay to avoid rate limiting
Start-Sleep -Milliseconds 500
}

Write-Host "Searchable branches configuration complete!" -ForegroundColor Cyan

Important Considerations and Limitations

API Version and Stability

Preview API: The Policy Configuration API is in preview (7.1-preview.1), which means:

  • The API may change without notice
  • Microsoft doesn’t guarantee backward compatibility
  • Monitor for API updates and test thoroughly before production use

Repository Limitations

Branch Limits: Azure DevOps allows a maximum of 6 searchable branches per repository (including the default branch).

Indexing Delays: After updating searchable branches, Azure DevOps may take several hours to index the new branches. Search results won’t be immediately available.

Performance Considerations

Rate Limiting: Implement appropriate delays between API calls to avoid hitting rate limits, especially when processing thousands of repositories.

Batch Processing: For large organizations, consider processing repositories in batches and implementing retry logic for failed requests.

Error Handling

Repository States: Some repositories may not have branches configured or may be in archived states. Implement proper error handling for these scenarios.

Permission Issues: Ensure your Personal Access Token has sufficient permissions for policy configuration (Project Settings, Read & Manage).

Authentication and Security Setup

Personal Access Token Configuration

Create a Personal Access Token with the following permissions:

  • Code (Read) - To access repository information
  • Project and Team (Read) - To list projects and repositories
  • Policy Configuration (Read & Manage) - To update searchable branch policies

Security Best Practices

Token Storage: Store Personal Access Tokens securely and rotate them regularly according to your organization’s security policies.

Least Privilege: Use dedicated service accounts with minimal required permissions for automation scripts.

Audit Logging: Log all policy changes for compliance and troubleshooting purposes.

Advanced Usage Scenarios

Conditional Branch Configuration

Configure different searchable branches based on repository naming conventions or team requirements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Example: Configure different branches based on repository name patterns
$branchConfig = @{
'web-*' = @('master', 'develop', 'staging')
'api-*' = @('master', 'develop', 'release')
'mobile-*' = @('master', 'develop', 'feature/*')
}

foreach ($repo in $repositories.value) {
$branches = @('master', 'main') # Default branches

# Add specific branches based on repository name
foreach ($pattern in $branchConfig.Keys) {
if ($repo.name -like $pattern) {
$branches += $branchConfig[$pattern]
break
}
}

# Remove duplicates and configure
$uniqueBranches = $branches | Select-Object -Unique
Update-SearchableBranches -repositoryId $repo.id -branches $uniqueBranches
}

Integration with CI/CD Pipelines

Integrate searchable branch configuration into your DevOps workflows:

1
2
3
4
5
6
7
8
# Azure DevOps Pipeline example
- task: PowerShell@2
displayName: 'Configure Searchable Branches'
inputs:
targetType: 'inline'
script: |
# Your PowerShell script here
# Use pipeline variables for organization, project, and PAT

Troubleshooting Common Issues

Policy Configuration Not Found

If the GET request returns empty results, the repository may not have searchable branches configured yet. Create a new policy instead of updating an existing one.

Invalid Branch References

Ensure branch names use the correct format:

  • Correct: refs/heads/develop
  • Incorrect: develop or origin/develop

Permission Denied Errors

Verify that your Personal Access Token has the required permissions and that you have administrative access to the project.

Indexing Delays

After configuration, search indexing may take several hours. Test with small repositories first to verify the configuration is working before processing large numbers of repositories.

Real-World Use Cases

JFrog Artifactory Adoption Tracking

The original use case that sparked this solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Search for JFrog references across all configured branches
$searchQuery = "jfrog.yourcompany.com"
$searchUrl = "https://dev.azure.com/$organization/$project/_apis/search/codesearchresults?api-version=7.1-preview.1"

$searchBody = @{
searchText = $searchQuery
includeFacets = $true
top = 1000
} | ConvertTo-Json

$searchResults = Invoke-RestMethod -Uri $searchUrl -Headers $headers -Method Post -Body $searchBody -ContentType "application/json"

# Process results to identify repositories using JFrog

Security Compliance Scanning

Scan for security-sensitive patterns across multiple branches:

1
2
3
4
5
6
7
8
9
10
11
# Search for potential security issues across all searchable branches
$securityPatterns = @(
"password\s*=",
"api[_-]?key\s*[:=]",
"secret[_-]?key\s*[:=]"
)

foreach ($pattern in $securityPatterns) {
# Search across all configured branches
# Generate compliance reports
}

Code Quality Assessment

Analyze code patterns and best practices across development branches:

1
2
3
4
5
6
7
8
# Search for deprecated patterns across main and develop branches
$deprecatedPatterns = @(
"System.Web.HttpContext",
"ConfigurationManager.AppSettings",
"HttpResponse.Write"
)

# Generate modernization reports

Future Considerations and Alternatives

Azure DevOps CLI Integration

While the Azure DevOps CLI doesn’t have native support for searchable branches, you can use az devops invoke to call the REST APIs:

1
az devops invoke --area policy --resource configurations --route-parameters project=YourProject --http-method GET --api-version 7.1-preview.1

PowerShell Module Development

Consider creating a dedicated PowerShell module for searchable branch management:

1
2
3
4
5
# Future PowerShell module structure
Import-Module AzureDevOpsSearchableBranches

Get-AdoSearchableBranches -Organization "YourOrg" -Project "YourProject" -Repository "YourRepo"
Set-AdoSearchableBranches -Organization "YourOrg" -Project "YourProject" -Repository "YourRepo" -Branches @("master", "develop")

Microsoft Graph Integration

For organizations using Microsoft Graph, consider integrating searchable branch configuration with broader DevOps governance workflows.

Key Takeaways

Working with Azure DevOps searchable branches requires understanding the underlying Policy Configuration API that Microsoft uses internally but doesn’t officially document for this purpose. The key insights from this solution are:

  • Hidden APIs Exist: Azure DevOps has powerful APIs that aren’t always documented for every use case
  • UI Reverse Engineering: Network traffic analysis can reveal API patterns for automation
  • Policy-Based Configuration: Many Azure DevOps features use the Policy Configuration API under the hood
  • Batch Processing Considerations: Large-scale automation requires careful attention to rate limiting and error handling

This solution provides a robust foundation for any Azure DevOps organization needing to manage searchable branches at scale. Whether you’re tracking technology adoption, performing security scanning, or implementing code quality initiatives, this approach enables the automation that makes these tasks feasible across large repository collections.

The undocumented nature of this API means it should be used with appropriate caution and monitoring, but for organizations with thousands of repositories, it’s currently the only viable approach for bulk searchable branch configuration.

References

Author

Ricky Gummadi

Posted on

2025-08-15

Updated on

2025-09-14

Licensed under

Comments