Passing Variables in Azure Pipelines

Natan Bensoussan
Israeli Tech Radar
Published in
6 min readJan 19, 2023

--

Passing variables in Azure DevOps Pipelines is not straight forward and can be confusing. Reading the docs and googling the examples was still unclear to me to understand where my code failed. After fighting with the syntax, typos and logics, I will try to clarify the traps as much as I can.

Passing Variables in Azure Pipelines

My post is based on David Gardiner’s post, which is a great one and covers more than I present here. The goal of this post is to simplify, elaborate and emphasize the ways to escape the pits in the flow.

The method to pass the variables varies with the following scenarios:

Post’s Context:

  1. Variable names
  2. Syntax to declare the variable
  3. Simple example: Passing variable between Tasks within the same Job
  4. Variable hierarchy (Tasks / Jobs / Stages)
  5. Summary (Cheat Sheet)

Git Source code:

The pipelines code in this post can be found here:
https://github.com/natanbs/Passing_Variables_in_Azure_Pipelines

Variable names:

Passing the variable comes in three variations (just to make sure you get confused :-)
The variable used in this tutorial:
FOO: The initial variable with the value needed to pass.
BAR: The name of the variable to pass in the syntax (exported variable).
BAZ: The renamed variable (imported variable) in the Job / Stage.

Note: All variable naming variation can be the same (like FOO for all).
The variations allows having different names of the variable in different Jobs or Stages according to the needs.

Syntax to declare the variable:

As the syntax is not trivial (to say the least) and can look complex, scary and confusing on the first sight and needs to get used to.
IMHO, a yaml format could be used and would have been much easier to read and understand.

In this tutorial, I will be using Bash, but with a slight syntax adjustment, it is all the same as Powershell.

# Bash
FOO="someValue"
echo "##vso[task.setvariable variable=BAR;isOutput=true]$FOO"
# Powershell
$FOO = "someValue"
Write-Host "##vso[task.setvariable variable=BAR;isOutput=true]$FOO"

Simple example — Between Tasks:

Passing variable between Tasks within the same Job:
Notice the couple “- bash” blocks used under steps:

Note: each “- bash ” block is considered a Task (also called a step).

trigger: none

pool:
vmImage: 'ubuntu-latest'

stages:
- stage: Stage1
displayName: Stage 1
jobs:
- job: Stage1_Job1
displayName: Stage 1 Job 1
steps:
- bash: |
echo SET FOO $FOO
export FOO="'FooValue'"
echo SET BAR with the value of FOO
echo "##vso[task.setvariable variable=BAR;isOutput=true]$FOO"
name: Stage1_Job1_Task1
displayName: Task1 - Set FOO into BAR

- bash: |
BAZ_Task2=$(Stage1_Job1_Task1.BAR)
echo BAZ_Task2 $BAZ_Task2
echo "BAZ in Task2 equals BAR with the value of FOO: $(Stage1_Job1_Task1.BAR)"
displayName: "Task2, BAZ = BAR From FOO"

The process requires two Tasks in the Job!
1st Task: to declare the variable
2nd Task: to use the variable.

On the first Task we declare variable BAR that will be passed to other Tasks, Jobs or Stages with content of value FOO in that Task.

On the second Task we call the variable BAR from the first Task (named Stage1_Job1_Task1) using the format: $({stage name}.BAR)
In our case: $(Stage1_Job1_Task1.BAR)

Note: The syntax calling the variable uses the brackets as the Azure Pipelines variables and not as the bash format:
Incorrect syntax: $BAZ
Correct syntax: $(BAZ)

Note: We could directly use the value instead of using FOO:

export FOO="FooValue"
echo "##vso[task.setvariable variable=BAR;isOutput=true]$FOO"
# Would be the same as
echo "##vso[task.setvariable variable=BAR;isOutput=true]FooValue"

Job’s output shows that the BAZ variable in Task2 receives the value from the declared BAR in Task1 which contains the value of FOO.

Variable hierarchy (Tasks / Jobs / Stages)

The usage of the variables differ when called between Tasks, Jobs or Stages:

Between Tasks:

In the code above, the ##vso declaration of variable BAR is enough, using the Task’s name, can be used with other Tasks within the same Job.

To call the BAR variable: $(Stage1_Job1_Task1.BAR).

Between Jobs (same stage):

There is a need first to declare the variable BAZ (BAZ_Stage1_Job2) calling BAR from the other Job (Stage1_Job1):

  - job: Stage1Job2
displayName: Stage 1 Job 2
variables:
BAZ_Stage1_Job2: $[ dependencies.Stage1_Job1.outputs['Stage1_Job1_Task1.BAR'] ]
dependsOn: Stage1_Job1

The BAZ (BAZ_Stage1_Job2) variable is declared in the Job block (in this case BAZ_Stage1_Job2).

Note: The variable can be called also FOO or BAR or just BAZ or whatever.
This method allows to call BAR using from different Jobs / Stages with different variable names.

Notice dependsOn: Source Job (Stage1_Job1) is placed in the Job block.

The syntax for BAZ variable declaration to call the BAR variable:

someJobBAZ: $[ dependencies.{Job}.outputs['[{task}.BAR'] ]
# In our example:
BAZ_Stage1_Job2: $[ dependencies.Stage1_Job1.outputs['Stage1_Job1_Task1.BAR'] ]

The Job and the Task are taken from the source Job (Stage1_Job1) and Task (Stage1_Job1_Task1) where BAR was declared (with the ##vso).

Notice the dependencies. This is the Job format. Don’t confuse with the Stage format: stageDependencies.

trigger: none

pool:
vmImage: 'ubuntu-latest'

stages:
- stage: Stage1
displayName: Stage 1
jobs:
- job: Stage1_Job1
displayName: Stage 1 Job 1
steps:
- bash: |
echo SET FOO $FOO
export FOO="'FooValue'"
echo SET BAR with the value of FOO
echo "##vso[task.setvariable variable=BAR;isOutput=true]$FOO"
name: Stage1_Job1_Task1
displayName: Task1 - Set FOO into BAR

- job: Stage1Job2
displayName: Stage 1 Job 2
variables:
BAZ_Stage1_Job2: $[ dependencies.Stage1_Job1.outputs['Stage1_Job1_Task1.BAR'] ]
dependsOn: Stage1_Job1
steps:
- bash: |
echo "BAZ in Job2 equals BAR with the value of FOO: $(BAZ_Stage1_Job2)"
displayName: "Job2, BAZ = BAR From FOO"

Job’s output shows that the BAZ variable in Job2 Task1 receives the value from the declared BAR in Job1 Task1 which contains the value of FOO.

Between Stages:

Here we will declare the variable BAZ (BAZ_Stage2_Job1) calling BAR from the other Stage:

- stage: Stage2
displayName: Stage 2
dependsOn: Stage1
jobs:
- job: Stage2Job1
displayName: Stage 2 Job 1
variables:
BAZ_Stage2_Job1: $[ stageDependencies.Stage1.Stage1_Job1.outputs['Stage1_Job1_Task1.BAR'] ]

The BAZ variable (BAZ_Stage2_Job1) declared in the Job block as well (in this case BAZ_Stage2_Job1).

Notice dependsOn: Other Stage (Stage1), placed in the Stage block.

The syntax for BAZ variable declaration to call the BAR variable:

someStageBAZ: $[ stageDependencies.{Stage}.{Job}.outputs['{task}.BAR'] ]
# In our example:
BAZ_Stage2_Job1: $[ stageDependencies.Stage1.Stage1_Job1.outputs['Stage1_Job1_Task1.BAR'] ]

The Stage, Job and Task are taken from the source Stage (Stage1), Job (Stage1_Job1) and Task (Stage1_Job1_Task1) where BAR was declared (with the ##vso).

Notice to the added Stage in the syntax added to the one in the Job.

Notice the StageDependencies. This is the Stage format. Don’t confuse with the Job format: dependencies.

trigger: none

pool:
vmImage: 'ubuntu-latest'

stages:
- stage: Stage1
displayName: Stage 1
jobs:
- job: Stage1_Job1
displayName: Stage 1 Job 1
steps:
- bash: |
echo SET FOO $FOO
export FOO="'FooValue'"
echo SET BAR with the value of FOO
echo "##vso[task.setvariable variable=BAR;isOutput=true]$FOO"
name: Stage1_Job1_Task1
displayName: Task1 - Set FOO into BAR

- stage: Stage2
displayName: Stage 2
dependsOn: Stage1
jobs:
- job: Stage2Job1
displayName: Stage 2 Job 1
variables:
BAZ_Stage2_Job1: $[ stageDependencies.Stage1.Stage1_Job1.outputs['Stage1_Job1_Task1.BAR'] ]
steps:
- bash: |
echo "BAZ in Stage2 equals BAR with the value of FOO: $(BAZ_Stage2_Job1)"
displayName: "Stage2, BAZ = BAR From FOO"

Job’s output shows that the BAZ variable in Stage2 Job1 Task1 receives the value from the declared BAR in Stage1 Job1 Task1 which contains the value of FOO.

Summary

+-------+-----+------------+-------------------+----------------------------+
| o | var | variables: | Dependencies | DependsOn: |
+-------+-----+------------+-------------------+----------------------------+
| Task | BAR | ##vso | No | No |
| Job | BAZ | Required | dependencies | In Job Block, Job Name |
| Stage | BAZ | Required | stageDependencies | In Stage Block, Stage name |
+-------+-----+------------+-------------------+----------------------------+
-------+------------------------------------------------------------+
| o | Hierarchy |
+-------+------------------------------------------------------------+
| Task | None |
| Job | $[ dependencies.{Job}.outputs['[{task}.BAR'] ] |
| Stage | $[ stageDependencies.{Stage}.{Job}.outputs['{task}.BAR'] ] |
+-------+------------------------------------------------------------+
+-------+--------------------------+
| o | Call variable |
+-------+--------------------------+
| Task | $(Stage1_Job1_Task1.BAR) |
| Job | $(BAZ_Stage1_Job2) |
| Stage | $(BAZ_Stage2_Job1) |
+-------+--------------------------+

On David Gardiner’s post you can find more scenarios passing variables in the Azure pipelines.

If this post was helpful to you, you are more than welcome to clap :-)

--

--