2

EDIT: I have a workaround since I just got schooled up on runtime versus compile time traits. I output the AllRepos variable as a stage/job variable, and ran a powershell script to split the string based on ',' and loop through the objects and inject the repo name into a JSON object which calls the pipeline template I wanted to use. Here is what I did to get around this limitation:

`

stages:
  - stage: GetAllProjectRepos
    pool:
      name: 'DevOps Pool'
    jobs:
     - job: GetRepos
       displayName: 'Get All Repos for Project'
       steps:
        - checkout: none
        - task: PowerShell@2
          displayName: 'Put all repo names into runtime variable'
          inputs:
            targetType: 'inline'
            script: |
              $env:AZURE_DEVOPS_EXT_PAT = '$(projectPAT)'
              $repos= (az repos list --organization '$(orgUrl)' --project '$(projectName)' | ConvertFrom-Json | Select-Object name).name -join ','

              Write-Host '============================================================================================'
              Write-Host 'Repos: ' $repos
              Write-Host '============================================================================================'

              Write-Host "##vso[task.setvariable variable=AllRepos;isOutput=true]$repos"
          name: ReposList

     - job: CleanUp
       displayName: 'Clean Undeleted PR Branches from Project repos'
       dependsOn: 'GetRepos'
       variables:
         AllRepos: $[ dependencies.GetRepos.outputs['ReposList.AllRepos'] ]
       steps:
        - checkout: none

        - task: PowerShell@2
          displayName: 'Get Pipeline ID'
          inputs:
            targetType: 'inline'
            script: |
                $env:AZURE_DEVOPS_EXT_PAT = '$(projectPAT)'
                az devops configure --defaults organization='$(orgUrl)'
                az devops configure --defaults project='$(projectName)'
                Write-Host '============================================================================================'
                Write-Host 'GitCleanup Pipeline ID (also called Definition)'
                $pipelineId = az pipelines list --query "[?name == 'Git Clean Branches'].id"
                $pipelineId = $pipelineId[1].Trim()
                Write-Host "##vso[task.setvariable variable=GitCleanPipelineId;]$pipelineId"
                Write-Host 'GitCleanup ID: ' $pipelineId

        - task: PowerShell@2
          displayName: 'Calling GitCleanup Pipeline'
          inputs:
            targetType: 'inline'
            script: |
              $base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f "","$(projectPAT)")))
              $uri = "$(orgUrl)/$(projectName)/_apis/pipelines/$(GitCleanPipelineId)/runs?api-version=6.0-preview.1"
              Write-Host '============================================================================================'
              Write-Host $uri
              Write-Host '============================================================================================'

              $repos = '$(AllRepos)'
              $repos.split(",") | ForEach {
                $jsonHashTable= @{
                    'templateParameters' = @{
                      'repoName' = $_
                      'deletePRBranches' = 'true'
                      'deleteStaleBranches' = 'false'
                    }
                  }

                $JSON = $jsonHashTable | ConvertTo-Json

                Write-Host '============================================================================================'
                Write-Host $JSON
                Write-Host '============================================================================================'
                Write-Host '============================================================================================'
                $reponse = Invoke-RestMethod -Uri $uri -Headers @{Authorization = "Basic $base64AuthInfo"} -Method Post -Body $JSON -ContentType application/json
                Write-Host "API Response"
                Write-Host $response
                Write-Host '============================================================================================'
              }

`

Trying to automate Azure Repos cleanup for stale and merged PR branches that sometimes stick around. Want to make the pipeline generic as possible and leverage templates for easier maintenance and reduced administrative workload.

I have a powershell script which gathers all repositories in a ADO project, converts from an array object to a string, and then joins each repo with a ',' delimiter for looping later in the pipeline. Once this is "working" I'll be using the repo names to inject them into a pipeline template as a parameter.

The powershell script works fine, it creates the comma-delimited string just fine, but the pipeline expression I am using just removes the comma, but treats the entire string as one object and doesn't loop.

`

stages:
  - stage: CleanProjectRepoBranches
    pool:
      name: 'DevOps Pool'
    jobs:
     - job: GetRepos
       displayName: 'Get All Repos for Project'
       steps:
        - checkout: none
        - task: PowerShell@2
          displayName: 'Gather all Repos in $(projectName)'
          inputs:
            targetType: 'inline'
            script: |
              $env:AZURE_DEVOPS_EXT_PAT = '$(projectPAT)'
              $repos= (az repos list --organization '$(orgUrl)' --project '$(projectName)' | ConvertFrom-Json | Select-Object name).name -join ','

              Write-Host '============================================================================================'
              Write-Host 'Repos: ' $repos
              Write-Host '============================================================================================'

              Write-Host "##vso[task.setvariable variable=AllRepos;]$repos"

        - script: echo "AllRepos is $(AllRepos)"

        - ${{ each repo in split('$(AllRepos)', ',') }}:
            - task: PowerShell@2
              displayName: 'Test'
              inputs:
                targetType: 'inline'
                script: |
                  Write-Host '============================================================================================'
                  Write-Host ${{repo}}
                  Write-Host '============================================================================================'

`

The "echo" successfully prints the string I theoretically need:

Log printout

The powershell script and injecting to a pipeline variable works fine, but the pipeline expression isn't behaving as I would think:

`

- ${{ each repo in split('$(AllRepos)', ',') }}:
            - task: PowerShell@2
              displayName: 'Test'
              inputs:
                targetType: 'inline'
                script: |
                  Write-Host '============================================================================================'
                  Write-Host ${{repo}}
                  Write-Host '============================================================================================'

`

The pipeline expression currently behaving in a way I wasn't anticipating, it "ignores" the split() method and when I print the ${{repo}} value it displays it as a string of all the same repo names,with all commas removed:

Log printout

Am I doing something wrong? Is this a limitation of the pipeline expressions MS implements?

3
  • Try double quotes instead of single quotes. Variables (dollar signs) are not substituted with single quotes. Commented Dec 7, 2022 at 16:34
  • @jdweng Do you mean the single quotes in - ${{ each repo in split('$(AllRepos)', ',') }}: ? Because doing so causes the pipeline to fail at runtime: Unexpected symbol: '"'. Located at position 19 within expression: split("$(AllRepos)", ','). For more help, refer to go.microsoft.com/fwlink/?linkid=842996 Commented Dec 7, 2022 at 19:17
  • ${{ }} is resolved at template compile time. You are setting AllRepos at runtime, which is after the template has been compiled. You cannot use runtime variables in compile-time expressions, period. What you are trying to do cannot work. Commented Dec 7, 2022 at 21:44

1 Answer 1

0

You can refer to the document Define variables - Azure Pipelines and learn more details about the three different ways to reference variables: macro, template expression, and runtime expression.

In a pipeline, template expression variables (${{ variables.var }}) get processed at compile time, before runtime starts. Macro syntax variables ($(var)) get processed during runtime before a task runs. Runtime expressions ($[variables.var]) also get processed during runtime but were designed for use with conditions and expressions.

As for ${{ each repo in split('$(AllRepos)', ',') }}, the template expression variable ${{ }} is resolved at template compile time and the macro syntax variable $(AllRepos) is resolved at runtime, which is after the template expression variable has been compiled. You can't use runtime variables in compile-time expressions.

It's recommended that you can try breaking it down into two pipelines to run.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! I modified my pipeline to use the runtime variable and call another pipeline. Will post an edit!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.