Search code examples
powershellazure-devopsazure-cli

Escaping issue with az ad app update in Azure DevOps Pipelines


I'm trying to update the scopes for a app registration in Entra ID using the CLI authV2 extension but am seeing weird json handling when running from a pipeline in Azure DevOps.

The block of code essentially is this:

$appId = 'xxxxxx'

# get the API app reg details 
$appRegApi = (az ad app show --id $appId --query 'api' | ConvertFrom-Json)

# check for scope
$scopes = $appRegApi.oauth2PermissionScopes

$scopeId = [guid]::NewGuid().Guid
$userImpersonationScope = [ordered]@{
    adminConsentDescription = "user impersonation"
    adminConsentDisplayName = "user_impersonation"
    id                      = "$scopeId"
    isEnabled               = "true"
    type                    = "User"
    userConsentDescription  = "user impersonation"
    userConsentDisplayName  = "user_impersonation"
    value                   = "user_impersonation"
}

$update = @{
    oauth2PermissionScopes = @($scopes; @($userImpersonationScope))
}
$updateJson = ConvertTo-Json $update -Depth 4 -Compress 
$escapedJson = ConvertTo-Json $updateJson

az ad app update --id $appId --set api=$escapedJson --debug

If I run this locally, in the output from the --debug flag, I can see the payload looks like

{"api": {"oauth2PermissionScopes": [{"adminConsentDisplayName": "user_impersonation", "userConsentDisplayName": "user_impersonation", "value": "user_impersonation", "id": "xxxxxx", "type": "User", "isEnabled": "true", "userConsentDescription": "user impersonation", "adminConsentDescription": "user impersonation"}]}}

But from my pipeline, it looks like this:

{"api": "{\"oauth2PermissionScopes\":[{\"adminConsentDescription\":\"user", "impersonation\",\"adminConsentDisplayName\":\"user_impersonation\",\"id\":\"xxxxxx\",\"isEnabled\":\"true\",\"type\":\"User\",\"userConsentDescription\":\"user": "", "impersonation\",\"userConsentDisplayName\":\"user_impersonation\",\"value\":\"user_impersonation\"}]}": ""}

It is running in an AzureCLI@2 task on a windows-latest agent and the powershell version matches the one I'm running locally (7.6.2) as does the CLI version (2.67.0)

Anyone have any clue why the json is being handled differently and what I need to do to get it working in the pipeline?

Update:

I've been doing some more debugging today and have seen that the values for $updateJson and $escapedJson are identical in the pipeline and locally so I don't think that the double use of ConvertTo-Json is the problem. I have also checked and both the pipeline and my machine are using v0.1.3 of the authV2 extension, but clearly this is the thing that is behaving differently on the pipeline agents


Solution

  • Based on your description, I created the following YAML pipeline to conduct tests. Please note that the results obtained when executing the az ad app update command using api=$updateJson and api=$updateJson in this pipeline are consistent with those observed when running the command in a local PowerShell Core environment.

    pool:
      vmImage: windows-latest
    
    parameters:
    - name: json
      default: $escapedJson
      values:
        - $updateJson
        - $escapedJson
    
    variables:
      system.debug: true
      appId: xxxxx
    
    steps:
    - checkout: none
    - task: AzureCLI@2
      inputs:
        azureSubscription: 'ARMSvcCnnWIFRootMG'
        scriptType: 'pscore'
        scriptLocation: 'inlineScript'
        inlineScript: |
          $appId = "$(appId)"
          
          # get the API app reg details 
          $appRegApi = (az ad app show --id $appId --query 'api' | ConvertFrom-Json)
          
          # check for scope
          $scopes = $appRegApi.oauth2PermissionScopes
          
          $scopeId = [guid]::NewGuid().Guid
          $userImpersonationScope = [ordered]@{
              adminConsentDescription = "user impersonation"
              adminConsentDisplayName = "user_impersonation"
              id                      = "$scopeId"
              isEnabled               = "true"
              type                    = "User"
              userConsentDescription  = "user impersonation"
              userConsentDisplayName  = "user_impersonation"
              value                   = "user_impersonation"
          }
          
          $update = @{
              oauth2PermissionScopes = @($scopes; @($userImpersonationScope))
          }
          $updateJson = ConvertTo-Json $update -Depth 4 -Compress
          $escapedJson = ConvertTo-Json $updateJson
          Write-Host '================ Check $updateJson ================'
          $updateJson
          Write-Host '================ Check $escapedJson ================'
          $escapedJson
          Write-Host '================ Set api permission with ${{ parameters.json }} ================'
          az ad app update --id $appId --set api=${{ parameters.json }} # --debug
    
    

    Image Image Image

    As demonstrated in the provided images, the behavior is consistent in both the Azure DevOps pipeline and the local PowerShell Core environment. Therefore, the issue does not appear to be related to Azure Pipelines.

    Additionally, as outlined in the document of Azure CLI ,

    If the JSON value contains double quotes, you must escape them.

    When working with JSON parameter values, consider using Azure CLI's @<file> convention and bypass the shell's interpretation mechanisms.