Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Api/ServiceConnection/Set-DevOpsServiceConnection.ps1

Function Set-DevOpsServiceConnection
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$ApiUri,
        [Parameter(Mandatory)][string]$ProjectId,
        [Parameter(Mandatory)][string]$ProjectName,
        [Parameter(Mandatory)][string]$ServiceConnectionId,
        [Parameter(Mandatory)][string]$ServiceConnectionName,
        [Parameter(Mandatory)][string]$ServiceConnectionType,
        [Parameter()][string]$Description,
        [Parameter()][bool]$IsShared = $false,
        [Parameter()][bool]$IsReady = $true,
        [Parameter()][hashtable]$Authorization = @{},
        [Parameter()][hashtable]$Data = @{},
        # Full project reference array for an endpoint shared across projects (issue #79). See
        # New-DevOpsServiceConnection for the shape and the single-project default.
        [Parameter()][Object[]]$ProjectReferences,
        # The endpoint's existing top-level url, used when Data carries no 'url' key. The update
        # call rejects a body without one ("Value cannot be null. Parameter name: endpoint.Url").
        [Parameter()][string]$Url,
        [Parameter()][string]$ApiVersion = '7.1-preview.4'
    )
    $endpointUrl = if ($Data.url) { $Data.url } elseif ($Data.Url) { $Data.Url } elseif ($Url) { $Url } else { '' }

    # The url goes top-level only. Sending it inside 'data' as well makes the update fail with
    # AuditLogEntryContainsDuplicateDataKeyException (key=url), because the modify audit records
    # both. -ne is case-insensitive, so 'Url' is dropped too.
    $dataBody = @{}
    foreach ($key in $Data.Keys)
    {
        if ($key -ne 'url') { $dataBody[$key] = $Data[$key] }
    }

    # Same reshaping as New-DevOpsServiceConnection: the API wants credential values nested
    # under authorization.parameters, with only 'scheme' at the top level.
    if ($Authorization.Count -gt 0 -and -not $Authorization.ContainsKey('parameters'))
    {
        $scheme     = $Authorization['scheme']
        $parameters = @{}
        foreach ($key in $Authorization.Keys)
        {
            if ($key -ne 'scheme') { $parameters[$key] = $Authorization[$key] }
        }
        $Authorization = @{ scheme = $scheme; parameters = $parameters }
    }

    # NOTE: wrapped in @(...) - see New-DevOpsServiceConnection for why (CLAUDE.md gotcha #7:
    # a one-element array emitted from an if/else expression unrolls to a bare hashtable,
    # which ConvertTo-Json then serializes as an object instead of an array).
    $serviceEndpointProjectReferences = @(if ($ProjectReferences) { $ProjectReferences } else {
        @{
            projectReference = @{ id = $ProjectId; name = $ProjectName }
            name             = $ServiceConnectionName
            description      = $Description
        }
    })

    $params = @{
        Uri         = '{0}/{1}/_apis/serviceendpoint/endpoints/{2}?api-version={3}' -f $ApiUri.TrimEnd('/'), $ProjectName, $ServiceConnectionId, $ApiVersion
        Method      = 'PUT'
        ContentType = 'application/json'
        Body        = @{
            id            = $ServiceConnectionId
            name          = $ServiceConnectionName
            type          = $ServiceConnectionType
            url           = $endpointUrl
            description   = $Description
            isShared      = $IsShared
            isReady       = $IsReady
            authorization = $Authorization
            data          = $dataBody
            serviceEndpointProjectReferences = $serviceEndpointProjectReferences
        } | ConvertTo-Json -Depth 10
    }
    try   { return Invoke-AzDevOpsApiRestMethod @params }
    catch { Throw "[Set-DevOpsServiceConnection] Failed to update service connection '$ServiceConnectionId': $_" }
}