Private/Get-AzureDevOpsTenantId.ps1
|
function Get-AzureDevOpsTenantId { <# .SYNOPSIS Discovers the Entra tenant that backs an Azure DevOps collection, or $null. .DESCRIPTION Azure DevOps returns the tenant GUID in the 'X-VSS-ResourceTenant' response header of an unauthenticated connectionData probe, so no credential is needed to find it. Returns $null - never throws - when the collection is unreachable, or is an on-premises Azure DevOps Server collection with no Entra tenant behind it. The caller decides what that means; for Get-EntraAccessToken it means "use another auth method". Results are cached per collection for the session: this runs before every REST call that has not already resolved its auth. .PARAMETER Collection Collection or organisation URL. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Collection ) $key = $Collection.TrimEnd('/').ToLowerInvariant() if (-not $script:TenantIdCache) { $script:TenantIdCache = @{} } if ($script:TenantIdCache.ContainsKey($key)) { return $script:TenantIdCache[$key] } $tenantId = $null try { $uri = '{0}/_apis/connectionData?api-version=7.1-preview' -f $Collection.TrimEnd('/') $response = Invoke-WebRequest -Uri $uri -Method Get -SkipHttpErrorCheck -ErrorAction Stop $raw = $response.Headers['X-VSS-ResourceTenant'] if ($raw) { # The header can be an array and/or comma-separated; take the first GUID. $tenantId = (($raw -join ',') -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -as [guid] -and $_ -ne [guid]::Empty } | Select-Object -First 1) } } catch { Write-Verbose "Tenant discovery failed for '$Collection': $($_.Exception.Message)" } $script:TenantIdCache[$key] = $tenantId return $tenantId } |