I'm having trouble accessing an output variable from a PowerShell task in my Azure DevOps pipeline and passing it into a deployment template. When I set the variable in a PowerShell script and try to pass it into my template, it doesn't resolve as expected.
The issue is that the variable $(filteredWebsitesPath) in my template is not resolving to the expected value. Instead, it just prints as $(filteredWebsitesPath) instead of the actual paths or values I need.
trigger:
- main
pool:
vmImage: 'windows-latest'
stages:
- stage: Build
displayName: 'Build Stage'
jobs:
- job: BuildJob
steps:
- task: Npm@1
- task: Npm@1
- task: PublishBuildArtifacts@1
inputs:
PathtoPublish: 'build'
ArtifactName: 'drop'
- stage: Deploy
displayName: 'Deploy to Production'
dependsOn: Build
condition: succeeded()
jobs:
- deployment: PreDeployJob
displayName: 'PreDeployJob'
environment:
name: 'Production'
resourceType: VirtualMachine
strategy:
runOnce:
deploy:
steps:
- task: PowerShellOnTargetMachines@3
displayName: 'Get Website Names'
name: GetWebsites
inputs:
Machines: 'ip:port'
InlineScript: |
$websites = Get-ChildItem -Directory -Path 'C:\myPath\' | Select-Object -ExpandProperty Name
$filteredWebsites = $websites -join ','
Write-Host "##vso[task.setvariable variable=filteredWebsitesNew;isOutput=true]$filteredWebsites"
- script: echo "Output: $(GetWebsites.filteredWebsitesNew)"
name: echovar
- job: SetVariableJob
displayName: "Set Variable for Template"
dependsOn: PreDeployJob
variables:
filteredWebsitesPath: $[ dependencies.PreDeployJob.outputs['PreDeployJob.GetWebsites.filteredWebsitesNew'] ]
steps:
- script: echo "Filtered Websites Path $(filteredWebsitesPath)"
- template: templates/deploy-template.yml
parameters:
filteredWebsitesNew: $(filteredWebsitesPath)
And here’s the template, where I’m trying to use filteredWebsitesNew:
parameters:
filteredWebsitesNew: ''
jobs:
- ${{ each website in split(parameters.filteredWebsitesNew, ',') }}:
- deployment: DeployJob
displayName: 'Deploy to ${{ website }}'
environment:
name: 'Production'
resourceType: VirtualMachine
strategy:
runOnce:
deploy:
steps:
- script: echo "Deploying to website ${{ website }}"
Despite setting the variable in the SetVariableJob, it isn’t being resolved when used in the deploy-template.yml template. Instead, I see $folderPath = "C:\myPath$(filteredWebsitesPath)\images" in the logs, where $(filteredWebsitesPath) doesn’t resolve to the actual variable value.
What am I missing, or is there an alternative way to ensure the variable resolves correctly in the template?
I tried setting the output variable filteredWebsitesNew in a PowerShell task within the PreDeployJob and expected it to resolve in the deploy-template.yml template. In SetVariableJob, I referenced the output variable using dependencies syntax and passed it to the template as $(filteredWebsitesPath). I expected the template to receive the resolved paths, but instead, it printed the literal variable name $(filteredWebsitesPath) without resolving it to the actual paths.
As mentioned in the comments, template expressions such as ${{ each ... }}
are compiled before the pipeline starts and before the output variable is set.
If you want to run each deployment in parallel I suggest you create a dedicated pipeline for the deployments, that is triggered after the output variable is set.
For example, consider a pipeline named deploySomething
:
parameters:
- name: filteredWebsitesNew
displayName: 'A comma separated list of websites to deploy'
type: string
pool:
vmImage: 'ubuntu-latest'
# Disable CI-CD trigger
trigger: none
jobs:
- ${{ each website in split(parameters.filteredWebsitesNew, ',') }}:
- job: DeployJob_${{ website }}
displayName: 'Deploy to ${{ website }}'
environment:
name: 'Production'
resourceType: VirtualMachine
steps:
- script: echo "Deploying to website ${{ website }}"
displayName: 'Deploy to ${{ website }}'
After setting the output variable in the original pipeline you can configure a job to trigger the deploySomething
pipeline as follows:
jobs:
- deployment: PreDeployJob
displayName: 'PreDeployJob'
environment:
name: A
strategy:
runOnce:
deploy:
steps:
- pwsh: |
$filteredWebsites='foo,bar'
Write-Host "##vso[task.setvariable variable=filteredWebsitesNew;isOutput=true]$filteredWebsites"
name: GetWebsites
- script: |
echo "Output: $(GetWebsites.filteredWebsitesNew)"
name: echovar
- job: TriggerBuild
displayName: 'Trigger Build'
dependsOn: PreDeployJob
# don't run this job if the output variable 'filteredWebsitesNew' is not set / is empty
condition: >-
and(
succeeded(),
ne(dependencies.PreDeployJob.outputs['PreDeployJob.GetWebsites.filteredWebsitesNew'], '')
)
variables:
filteredWebsitesPath: $[ dependencies.PreDeployJob.outputs['PreDeployJob.GetWebsites.filteredWebsitesNew'] ]
timeoutInMinutes: 3
cancelTimeoutInMinutes: 1
steps:
- checkout: none
- script: |
echo "Filtered Websites Path: $(filteredWebsitesPath)"
displayName: 'Show variable'
- pwsh: >-
$run=$(
az pipelines run
--name "deploySomething"
--organization $(System.CollectionUri)
--project $(System.TeamProject)
--branch $(Build.SourceBranchName)
--parameters filteredWebsitesNew="$(filteredWebsitesPath)"
--output json) | ConvertFrom-Json
$pipelineUrl = "$(System.CollectionUri)$(System.TeamProject)/_build/results?buildId=$($run.id)"
Write-Host "##vso[task.logissue type=warning]Pipeline was triggered. Web URL: $pipelineUrl"
displayName: 'Trigger the deployment build'
env:
AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)
The script uses the azure pipeline run command of the Azure DevOps CLI. As an alternative, you can use the Trigger Build Task for more advanced scenarios.
Please note you might need to configure the new pipeline's permissions for the user/group that is running the build: