11

I have task in VSTS release management that deletes files. I want to have the Contents come from a variable. I have created a variable but I can't figure out how to create a multi-line variable. So for example a variable that deletes the three types of files:

Variable Name= ExcludeFiles
Variable Value= "Lib" "bin\*.pdb" "bin\*.dll.config"

enter image description here

Thomas F. Abraham
  • 2,082
  • 1
  • 19
  • 24
Pragmatic Coder
  • 484
  • 1
  • 4
  • 17

6 Answers6

27

It's possible to use a variable with multiple logical lines as input to a multi-line task parameter, but it may not work for every task. This approach is tested on VSTS using the VS Test 2.x task, which I'll use as an example below. I expect it will work for most of the Microsoft-provided tasks.

Background

Tasks define a set of parameters via a JSON file. Each parameter has an internal name in addition to the display name shown in the UI. It's possible to see the internal parameter name using the "Link settings" button on a task (or by finding the task's source code).

In the "Link settings" dialog, the VS Test 2.x task has a "Setting to link" called "Test assemblies", which is a multi-line string. Looking at "Process parameter to link to this setting", we see the value "Parameters.testAssemblyVer2". testAssemblyVer2 is the name of the internal parameter.

When a task executes, it needs to obtain values for its parameters. Most tasks do this by searching the current environment variables for anything starting with "INPUT_". In the case of testAssemblyVer2, the task will look for an environment variable named INPUT_TESTASSEMBLYVER2.

Just before the task executes, we can turn a delimited variable value into an encoded multi-line value, and write it into the environment variable where it's picked up by the task.

Solution

First, define a variable, "Custom.TestAssemblies" with a semicolon-delimited value **\$(BuildConfiguration)\*.tests.dll;!**\obj\**. The semicolon will become the line split.

Next, add a PowerShell task to the build process just before the VS Test task. Configure it as an Inline script with one Argument "$(Custom.TestAssemblies)". Here, the double quotes are critical.

The inline script looks like this:

Param([String]$toMultiLine)

$newlineDelimited = $toMultiLine -replace ';', "%0D%0A"

Write-Host "##vso[task.setvariable variable=INPUT_TESTASSEMBLYVER2]$newlineDelimited"

That's it! The delimiters in the variable value are converted to URL-encoded CR/LF's, and the agent is instructed to update INPUT_TESTASSEMBLYVER2 with that value. The task picks up the value and parses it for '\n', which matches the embedded %0D%0A's.

Summary

  1. Pick a delimiter like ; and use it to divide the parts of your variable value
  2. Obtain the task's internal parameter name using "Link settings"
  3. Insert a PowerShell task just before the target task and insert the code above, substituting the correct variable and task parameter names

If you set the variable system.debug to true, you'll generally see the various INPUT_ parameters and some of the parsing in the trace output. It depends on the implementation of the specific task.

This solution should work equally well for Build and Release sequences.

Thomas F. Abraham
  • 2,082
  • 1
  • 19
  • 24
  • Thanks! This worked for my case. One question, what happens if I have two test tasks? I guess there should be two `INPUT_TESTASSEMBLYVER2` variables, with a different name each. – MauriR Jul 18 '19 at 17:10
  • 1
    If you have two distinct test tasks in the pipeline, it may be simplest to have two variables, one for each task, and repeat the PowerShell step above before each of the two test tasks. Each one would map the corresponding input variable to INPUT_TESTASSEMBLYVER2. – Thomas F. Abraham Jul 21 '19 at 05:29
  • Thank you so much for this! Works very well for me. – Rik De Peuter Mar 05 '21 at 11:12
  • Any idea why we can't directly write %0D%0A" in the parameter text? Though it would be ugly, it would prevent from having to add an additional task to every pipeline. I tried using it for VsTest TestAssemblies parameter with something like `"**\*test*.dll%0A%0D!**\*TestAdapter.dll"` but it didn't work. They all got in a separate line in the log but were not splitted by the task. Adding the powershell set variable `BR`, I was able to use `"**\*test*.dll$(BR)!**\*TestAdapter.dll"` though. – bkqc Nov 29 '22 at 21:30
11

Multi-line variable is not supported, I submit a user voice here: Multiple lines variable in Build and Release.

Based on the source code of Delete Files task, it splits contents value by ‘\n’, but based on my test, add ‘\n’ to variable isn’t working (e.g. t1.txt \n t2.txt or t1.txt\nt2.txt).

You can custom build/release task per to the source code of Delete Files task or to do it with your logical and execute it through PowerShell/Command Line task.

Simon Ness
  • 2,272
  • 22
  • 28
starian chen-MSFT
  • 33,174
  • 2
  • 29
  • 53
6

Bash solution:

emailbody=$(echo "$output" | sed ':a;N;$!ba;s/\n/%0D%0A/g')
Josh Johanning
  • 840
  • 9
  • 16
2

The PowerShell solution from Thomas F. Abraham solved my problem. This modification makes it a bit simpler, no input parameter needed:

$newline = "%0D%0A `t"
Write-Host "##vso[task.setvariable variable=LineBreak]$newline"

I also added a tab character so my next line would be indented. Then just refer to the variable $(LineBreak) where ever you want it.

2

Solution from Thomas also helped point us in right direction.

No issue loading multiline certificates/keys using "bash" task in azure pipeline with,

export CERTIFICATE=$(echo "$(CERTIFICATE_BASE64)" | base64 -d -w 0)
echo "##vso[task.setvariable variable=CERTIFICATE;]$(echo $CERTIFICATE)"

However trying same using "powershell" task in azure pipeline didn't work with,

$CERTIFICATE = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("$(CERTIFICATE_BASE64)"))
$CERTIFICATE.Replace("`n","`r`n")
Write-Output "##vso[task.setvariable variable=CERTIFICATE;]$CERTIFICATE"

Swapping in the following did work,

$CERTIFICATE.Replace("`n","%0D%0A")
2

Solution from "Thomas F. Abraham" helped to write a yml template for the VsTest task:

#Note that it is tricky to specify the multiline-string for the VSTest task
#see 'https://github.com/MicrosoftDocs/azure-devops-docs/issues/1580'
#see 'https://stackoverflow.com/questions/44464976/vsts-release-multi-line-variable' for the solution used in this template
#we set the environment variable 'INPUT_TESTASSEMBLYVER2' instead of setting the input 'testAssemblyVer2' for the task !!!

parameters:
- name: testAssemblies
  type: string
  default: '**\*test*.dll,!**\*TestAdapter.dll,!**\obj\**'
- name: searchFolder
  type: string
  default: $(Build.Repository.Name)
- name: codeCoverageEnabled
  type: boolean
  default: false

steps:
- script: |
    echo Paramater testAssemblies: ${{ parameters.testAssemblies }}
    echo Paramater searchFolder: ${{ parameters.searchFolder }}
    echo Paramater codeCoverageEnabled: ${{ parameters.codeCoverageEnabled }}
  displayName: 'Parameters for VSTest'

- powershell: |
    $newline = "%0D%0A"
    $newlineDelimitedTestAssemblies = '${{ parameters.testAssemblies }}' -replace ',', $newline
    Write-Host "##vso[task.setvariable variable=INPUT_TESTASSEMBLYVER2]$newlineDelimitedTestAssemblies"
  displayName: 'Set INPUT_TESTASSEMBLYVER2 for VSTest task'

- task: VSTest@2
  inputs:
    testSelector: 'testAssemblies'
    searchFolder: '${{ parameters.searchFolder }}'
    vstestLocationMethod: 'location'
    vstestLocation: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\TestPlatform'
    codeCoverageEnabled: ${{ parameters.codeCoverageEnabled }}
  displayName: 'Run VSTest VS2022'
PainElemental
  • 687
  • 6
  • 14