Azure Pipelines: Storing objects in variables from a PowerShell task
In this quick post I will share a trick I have found recently to store an object in an Azure Pipelines variable from a PowerShell script.
The problem
Setting a variable in a script for later use in an Azure DevOps’ pipeline is possible using the task.setvariable
command as described here in the docs.
This works great for simple variables like this:
1
2
3
4
5
steps:
- pwsh: |
Write-Host "##vso[task.setvariable variable=variableName]variableValue"
- pwsh: |
Write-Host "Value from previous step: $(variableName)"
But it’s a little bit trickier for complex variables likes objects, arrays, or arrays of objects.
As an example, let’s say I want to retrieve the name, type and resource group of all the resources in an Azure subscription like this:
1
$resources = Get-AzResource | Select-Object -Property Name,Type,ResourceGroupName
Using this line and replacing variableValue
with $resources
in the previous yaml snippet will not work as you can check from your terminal: I have cropped the image for obvious sensitive reasons You see that a complex variable in a string is empty, even if tabular data is shown when the variable is typed alone in a terminal line. As you can guess the same code in a pipeline will result in an empty variable, and fortunately there is a simple solution to this.
The solution
To store the list of resources in variable, we simply serialize it in JSON and put it on a single like this:
1
$resourcesJson = $resources | ConvertTo-Json -Compress
Notice the -Compress
flag which puts all the JSON on a single line.
In a later step the JSON is deserialized like that:
1
$resources = '$(resources)' | ConvertFrom-Json
This is how it looks in a simple yet complete pipeline:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pool:
vmImage: ubuntu-latest
steps:
- task: AzurePowerShell@5
inputs:
azureSubscription: $(azureServiceConnection)
azurePowerShellVersion: LatestVersion
ScriptType: InlineScript
Inline: |
$resources = Get-AzResource | Select-Object -Property Name,Type,ResourceGroupName
$resourcesJson = $resources | ConvertTo-Json -Compress
Write-Host "##vso[task.setvariable variable=resources]$resourcesJson"
- pwsh: |
$resources = '$(resources)' | ConvertFrom-Json
Write-Host "There are $($resources.Count) resources in the list"
And the working result from the Azure Pipelines UI:
Wrapping up
As conclusion, let me explain the real-world scenario where I needed this trick, as you might wonder why we do this instead of putting everything in a single script.
Well, I needed to perform some operations on a set of virtual machines, with waiting times between them. To avoid using the Azure Pipelines agents in the waiting times, I have used agentless jobs. Thus I have split my pipeline in several jobs, the actual jobs running on my agents, and the Delay tasks handled by agentless jobs.
The role of the first job in my pipeline was to retrieve the list of targeted machines, and store in a variable to ensure the same set of machines was used for the whole pipeline.
That’s why I have simplified the examples in this post with a single-job pipeline. I can’t recommend enough to carefully read the task.setvariable
documentation, as there are some subtle differences whether you’re setting a variable within a job, for another job or for another stage.