1

I have a use case, where I want to add a pickList-type parameter to my task group. This seems to be doable; the UI renders fine, and the variable is also correct when running the pipeline using the task group. I understand that you cannot configure the parameter directly to be a pick list, and that this has to be done manually via JSON.

What I then did:

  • Created a task group with my tasks and the necessary variables. All good, and the variables came up as parameters, $(someType) and $(someValue).
  • I wanted the $(someType) variable to be a pickList - I exported said task group, removed all relations to the original task group (id's and what-not) from the JSON, edited my input field to be of type pickList, and added the necessary options to the options-array.
  • Imported said task group with the edited values. Works fine. Used it in a release pipeline. Worked fine.
  • Experienced an issue with one of my scripts in the task group, went to edit the task group. As soon as I press save, it will convert all parameters to strings, and now it's obviously broken.

I've included a minimal working JSON example of a task group, where a multiline and pickList type parameter is included.

{
   "tasks":[
      {
         "environment":{
            
         },
         "displayName":"PowerShell Script",
         "alwaysRun":false,
         "continueOnError":false,
         "condition":"succeeded()",
         "enabled":true,
         "timeoutInMinutes":0,
         "inputs":{
            "targetType":"inline",
            "filePath":"",
            "arguments":"",
            "script":"Write-Host \"$(picklisttype)\"\nWrite-Host \"$(mlvalue)\"",
            "errorActionPreference":"stop",
            "failOnStderr":"false",
            "showWarnings":"false",
            "ignoreLASTEXITCODE":"false",
            "pwsh":"false",
            "workingDirectory":""
         },
         "task":{
            "id":"e213ff0f-5d5c-4791-802d-52ea3e7be1f1",
            "versionSpec":"2.*",
            "definitionType":"task"
         }
      }
   ],
   "runsOn":[
      "Agent",
      "DeploymentGroup"
   ],
   "name":"my-task-group-with-picklist",
   "version":{
      "major":1,
      "minor":0,
      "patch":0,
      "isTest":false
   },
   "iconUrl":"https://my-own-custom-image.com/images/icon.png",
   "friendlyName":"My Task Group w/ PickList",
   "description":"This task group contains a picklist. Awesome.",
   "category":"Deploy",
   "definitionType":"metaTask",
   "author":"Myself",
   "demands":[
      
   ],
   "groups":[
      
   ],
   "inputs":[
      {
        "aliases": [],
        "options": {
            "option1": "First option",
            "option2": "Second option (default)",
            "option3": "Third option"
        },
        "properties": {},
        "name": "picklisttype",
        "label": "Pick a type",
        "defaultValue": "option2",
        "required": true,
        "type": "pickList",
        "helpMarkDown": "Just pick a type!",
        "groupName": ""
      },
      {
         "aliases":[],
         "options":{},
         "properties":{},
         "name":"mlvalue",
         "label":"Write several lines",
         "defaultValue":"This contains\nseveral lines\nof text.\nHowever, you it is\nquite small and it\nis not possible to alter\nits appearance...",
         "required":true,
         "type":"multiLine",
         "helpMarkDown":"Write some awesome text.",
         "groupName":"",
         "visibleRule": "picklisttype != option3"
      }
   ],
   "satisfies":[
      
   ],
   "sourceDefinitions":[
      
   ],
   "dataSourceBindings":[
      
   ],
   "instanceNameFormat":"Default name with default value $(picklisttype)",
   "preJobExecution":{
      
   },
   "execution":{
      
   },
   "postJobExecution":{
      
   }
}

If you import said JSON, add the task group to a release pipeline and run it, you'll see it outputs whatever you chose from the pick list correctly.

If you then go edit the task group, as soon as you save it, it will be rendered broken (will convert the multiline and pick list to string types).

Anyone have any experience with this, if it is possible to achieve in any way (using other than string types for your parameters)?

Kristin
  • 1,336
  • 7
  • 23

2 Answers2

3

Please do not update the task group task in the UI, you should update it via REST API.

Get task group info via REST API

GET https://dev.azure.com/{organization}/{project}/_apis/distributedtask/taskgroups/{taskGroupId}?api-version=6.0-preview.1

Then update the task group info via this REST API

PUT https://dev.azure.com/{organization}/{project}/_apis/distributedtask/taskgroups/{taskGroupId}?api-version=6.0-preview.1

You could try this power shell script to update the power shell print info.

$url = "https://dev.azure.com/{org name}/{project name}/_apis/distributedtask/taskgroups/{task group ID}?api-version=6.0-preview.1"

$connectionToken="{pat}"

$base64AuthInfo= [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($connectionToken)"))

$taskGroups = Invoke-RestMethod -Uri $url -Headers @{authorization = "Basic $base64AuthInfo"} -Method Get

#Write-Host $taskGroups.value.tasks.inputs.script

$taskGroups.value.tasks.inputs.script = 'Write-Host "$(picklisttype)"
Write-Host "$(mlvalue)" 
Write-Host "$(picklisttype)"'

$json = $taskGroups.value | ConvertTo-Json -Depth 10

$response = Invoke-RestMethod -Uri $url -ContentType "application/json" -Body $json -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -Method PUT

#Write-Host $taskGroups.value.tasks.inputs.script

Result:

Task group page:

enter image description here

Release print info:

enter image description here

Vito Liu
  • 7,525
  • 1
  • 8
  • 17
  • Thank you, this worked. I have added an answer myself as well, expanding your script making it easier to work with, as APIs generally are targeted towards code, not humans. ;-) – Kristin Nov 27 '20 at 11:20
0

Just as an expansion to @Vito Liu-MSFT's answer, here's a simple PS commandlet that I ended up using (I'm no expert at PS scripts, so this could probably be improved):

Function Get-TaskGroup {
    [CmdletBinding()]
        param (
        [Parameter(Mandatory=$true, HelpMessage="DevOps organization")][string]$org,
        [Parameter(Mandatory=$true, HelpMessage="Organization project")][string]$project,
        [Parameter(Mandatory=$true, HelpMessage="Task group ID")][string]$tgId,
        [Parameter(Mandatory=$true, HelpMessage="DevOps PAT")][string]$pat,
        [Parameter(Mandatory=$false, HelpMessage="DevOps PAT")][string]$path
    )
    
    $auth = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$pat"))
    $url = "https://dev.azure.com/$org/$project/_apis/distributedtask/taskgroups/$tgId`?api-version=6.0-preview.1"
    $tg = Invoke-RestMethod -Uri $url -Headers @{authorization = "Basic $auth"} -Method GET -ErrorAction Stop
    if ($tg -and $tg.value) {
        echo $tg.value
            
        if ($path) {
            echo "$($tg.value | ConvertTo-Json -Depth 10)" > $path
        }
    }
    else {
        throw "Failed to fetch task group with id $tgId"
    }
}

Function Put-TaskGroup {
    [CmdletBinding()]
        param (
        [Parameter(Mandatory=$true, HelpMessage="DevOps organization")][string]$org,
        [Parameter(Mandatory=$true, HelpMessage="Organization project")][string]$project,
        [Parameter(Mandatory=$true, HelpMessage="Task group ID")][string]$tgId,
        [Parameter(Mandatory=$true, HelpMessage="DevOps PAT")][string]$pat,
        [Parameter(Mandatory=$false, HelpMessage="Valid task group object as JSON.")][string]$json,
        [Parameter(Mandatory=$false, HelpMessage="Path to file containing valid task group object as JSON.")][string]$path
    )
    BEGIN {
        if (!$json -and !$path) {
            throw "Must provide either a valid JSON string using the -json parameter or a path to a file with a valid JSON object using the -path parameter."
        }
        ElseIf ($json -and $path) {
            echo "Both -json and -path supplied, using string provided with -json"
        }
        ElseIf (!$json -and $path -and ![System.IO.File]::Exists($path)) {
            throw "$path does not exist"
        }
    }
    PROCESS {
        $body = ""
        if ($json) {
            $body = $json
        }
        else {
            $body = Get-Content $path
        }
        
        $auth = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$pat"))
        $url = "https://dev.azure.com/$org/$project/_apis/distributedtask/taskgroups/$tgId`?api-version=6.0-preview.1"
        $tg = Invoke-RestMethod -Uri $url -ContentType "application/json" -Body $body -Headers @{authorization = "Basic $auth"} -Method PUT -ErrorAction Stop -TimeoutSec 10
        if ($tg) {
            echo $tg
        }
    }
}

Example:

$tg = Get-TaskGroup -org $org -project $project -tgId $tgId -pat $pat -path $path
$tg = Put-TaskGroup -org $org -project $proj -tgId $tgId -pat $pat -path $path
Kristin
  • 1,336
  • 7
  • 23