1

I need to read settings from a Json file for the execution of a pipeline. I use a Powershell task for this. In the next step I want to use those properties as input. However when I do that the variable does not get rendered. How can I achieve this?

- task: PowerShell@2
  displayName: 'Main -> Read App Setting'
  inputs:
    targetType: 'inline'
    script: |
      $settings = Get-Content "$(Build.Repository.LocalPath)\settings\appsettings.json" | ConvertFrom-Json
      Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)"      
  
- template: ./.lib/create_resources.yml
  parameters:
    type: $(type)
4
  • 3
    try Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)" Commented Jun 24, 2020 at 11:57
  • @ShaykiAbramczyk if you make this a bit longer and explain why you have to use the $($variable) syntax (which is called subexpression syntax), it would be a good answer for the question. Commented Jun 24, 2020 at 13:28
  • @FoxDeploy I will explain if it works for him, I'm not sure this is the issue. Commented Jun 24, 2020 at 13:54
  • @Shayki Abramczyk Good catch, as it was not working did not catch this. I have updated the question, to avoid confusion about the actual problem. Thanks Commented Jun 25, 2020 at 7:13

2 Answers 2

1

However when I do that the variable does not get rendered. How can I achieve this?

You can't pass parameter to template in this way. The variable did not get rendered because we can't use runtime variables in template parameters. It's the direct cause why it didn't get rendered.

This is by design of Azure Devops Service, check Process the pipeline:

To turn a pipeline into a run, Azure Pipelines goes through several steps in this order:

1.First, expand templates and evaluate template expressions.

2.Next, evaluate dependencies at the stage level to pick the first stage(s) to run.

3.For each stage selected to run, two things happen: 
All resources used in all jobs are gathered up and validated for authorization to run.
Evaluate dependencies at the job level to pick the first job(s) to run.

4.For each job selected to run, expand multi-configs (strategy: matrix or strategy: parallel in YAML) into multiple runtime jobs.

5.For each runtime job, evaluate conditions to decide whether that job is eligible to run.

6.Request an agent for each eligible runtime job.

So your variable comes after the task is executed(step6) while the parameter is evaluated at the very first of this process(step1 of pipeline run process). See:

This ordering helps answer a common question: why can't I use certain variables in my template parameters? Step 1, template expansion, operates solely on the text of the YAML document. Runtime variables don't exist during that step. After step 1, template parameters have been completely resolved and no longer exist.

In addition: Shayki Abramczyk's comment is right though it's not the main cause of your issue, you should use the Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)" so that the value of $(type) would be $settings.pipeline.type instead of $settings.

To use variable in next step:

We can check this document:

In order to use a variable as a task input, you must make the variable an output variable, and you must give the producing task a reference name.

More details about how to use that in yaml pipeline please check my another issue.

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

12 Comments

The ${{parameters.type}} do not work as input for a task, however they do display when used in a PS script in the template like echo ${{parameters.type}}
Additionally, how would I get a setup where I use templates like functions with input variables / parameters, that are called from a main yml file, whereby the main yml file reads the json file from the repo and sends the properties to the templates?
For your first comment: You're passing $(type) to parameter type, since template parameter is evaluated before runtime variables, so when you use ${{parameters.type}} as task input, it won't work, it should be plain text. When you use that in script, it becomes ${{parameters.type}}=>$(type), since $(type) is a runtime variable, it can be evaluated in script, so you can echo its value in script. This is the behavior you met.
If you only want to share variables across steps/tasks, job-scoped variable is enough. If you want to use the variable as next steps' condition, you can check this.
No, it actually displays the updated value nicely in the PS script. It only does not work when used as a value for a step input
|
1

As Lance described the problem is that template parameters are expanded first, so setting the parameters using a variable does not work when used as input for a tasks in the template. However I found out that in a PowerShell task in the template, the exact same parameters are showing the latest value.

Additionally I wanted to run templates sequential and have the output of template 1 be used for template 2 etc, without template1 knowing about template2, no dependencies. This way individual templates can still be run manually from DevOps.

So I used the following setup, which fulfills both requirements. It passes the output from template 1 to template 2, without hardcoding those variables in the templates itself & it shows template 1 passing an incoming parameter as input for a seconds step by using PowerShell to convert the parameter to a variable first. This way one could add a PowerShell script to each template and convert all parameters to variables and use those variables in the template instead. One extra step, but until MS includes dynamic parameters, quite acceptable I think.

Main:

steps:
- task: PowerShell@2
  displayName: 'Main -> Read App Setting'
  inputs:
    targetType: 'inline'
    script: |
      $settings = Get-Content "$(Build.Repository.LocalPath)\settings\appsettings.json" | ConvertFrom-Json
      Write-Host "##vso[task.setvariable variable=type]$($settings.pipeline.type)"

- template: ./template1.yml
  parameters:
    input: $(type)
    outputVariable: template1Output

- template: ./template2.yml
  parameters:
    input: $(template1Output)

Template1:

parameters:
- name: input
  type: string
- name: outputVariable
  type: string

steps:
  
- task: PowerShell@2
  displayName: Task in Template 1
  inputs:
    targetType: inline
    script: |
      $input = "${{parameters.input}}"
      $output = $input + " some extra value"
      Write-Host "##vso[task.setvariable variable=input]${{parameters.input}}"
      Write-Host "##vso[task.setvariable variable=${{parameters.outputVariable}}]$output"

- template: ./template2.yml
  parameters:
    input: $(input)

Template2:

parameters:
- name: input
  type: string

steps:
  
- task: PowerShell@2
  displayName: Task in Template 2
  inputs:
    targetType: 'inline'
    script: |
      echo "${{parameters.input}}"

1 Comment

Hi friend, please consider accepting it as answer so that more members with similar issue can easily find the useful info from marked answer. Just a reminder of can i ...

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.