internal/Get-MtDataverseEnvironmentUrl.ps1

<#
.SYNOPSIS
    Auto-discovers a Dataverse environment URL using the Global Discovery Service.

.DESCRIPTION
    Calls the Dataverse Global Discovery Service (GDS) to find environments
    accessible by the current user. Returns the ApiUrl of the first enabled
    environment, or $null if none are found.

    The GDS endpoint is selected based on the current Azure environment
    (AzureCloud, AzureUSGovernment, AzureChinaCloud).

    This function is called by Connect-Maester as a fallback when
    DataverseEnvironmentUrl is not explicitly configured in maester-config.json.

.EXAMPLE
    Get-MtDataverseEnvironmentUrl

    Returns a URL like 'https://org12345.api.crm.dynamics.com' or $null.

.LINK
    https://learn.microsoft.com/en-us/power-apps/developer/data-platform/discovery-service
#>


function Get-MtDataverseEnvironmentUrl {
    [CmdletBinding()]
    [OutputType([string])]
    param()

    # Determine the Azure environment from the current Az context
    $azContext = Get-AzContext -ErrorAction SilentlyContinue
    if (-not $azContext) {
        Write-Warning "No active Azure context. Ensure you are connected via 'Connect-Maester -Service Dataverse'."
        return $null
    }

    # Map Azure environment to Global Discovery Service URL
    # Commercial: covers standard commercial tenants
    # AzureUSGovernment: covers GCC High and USG (GCC uses AzureCloud and can configure DataverseEnvironmentUrl manually)
    # AzureChinaCloud: covers 21Vianet China
    $gdsUrlMap = @{
        'AzureCloud'        = 'https://globaldisco.crm.dynamics.com'
        'AzureUSGovernment' = 'https://globaldisco.crm.microsoftdynamics.us'
        'AzureChinaCloud'   = 'https://globaldisco.crm.dynamics.cn'
    }

    $azEnvName = $azContext.Environment.Name
    $gdsBaseUrl = $gdsUrlMap[$azEnvName]
    if (-not $gdsBaseUrl) {
        Write-Verbose "Unknown Azure environment '$azEnvName', defaulting to commercial Global Discovery Service."
        $gdsBaseUrl = $gdsUrlMap['AzureCloud']
    }

    Write-Verbose "Using Global Discovery Service: $gdsBaseUrl (Azure environment: $azEnvName)"

    # Get access token for the Global Discovery Service
    try {
        $gdsTokenResult = Get-AzAccessToken -ResourceUrl $gdsBaseUrl -ErrorAction Stop
        if ($gdsTokenResult.Token -is [System.Security.SecureString]) {
            $gdsToken = $gdsTokenResult.Token | ConvertFrom-SecureString -AsPlainText
        } else {
            $gdsToken = $gdsTokenResult.Token
        }
    } catch {
        Write-Warning "Failed to get Global Discovery Service token. Ensure you are connected via 'Connect-Maester -Service Dataverse'. Error: $_"
        return $null
    }

    $gdsHeaders = @{
        Authorization = "Bearer $gdsToken"
        Accept        = 'application/json'
    }

    # Query enabled Dataverse environments accessible by the current user
    try {
        $gdsResponse = Invoke-RestMethod -Uri "$gdsBaseUrl/api/discovery/v2.0/Instances?`$select=ApiUrl,FriendlyName,State&`$filter=State eq 0" -Headers $gdsHeaders -ErrorAction Stop
    } catch {
        Write-Warning "Failed to query Global Discovery Service for Dataverse environments: $_"
        return $null
    }

    if (-not $gdsResponse.value -or $gdsResponse.value.Count -eq 0) {
        Write-Warning "No Dataverse environments found via Global Discovery Service. If you have a GCC environment or a specific environment URL, configure 'DataverseEnvironmentUrl' in maester-config.json GlobalSettings."
        return $null
    }

    $selectedEnv = $gdsResponse.value[0]
    $discoveredUrl = $selectedEnv.ApiUrl

    if ($gdsResponse.value.Count -gt 1) {
        Write-Verbose "Found $($gdsResponse.value.Count) Dataverse environments. Using the first: '$($selectedEnv.FriendlyName)' ($discoveredUrl). To use a different environment, configure 'DataverseEnvironmentUrl' in maester-config.json GlobalSettings."
    } else {
        Write-Verbose "Auto-discovered Dataverse environment: '$($selectedEnv.FriendlyName)' ($discoveredUrl)"
    }

    return $discoveredUrl
}