TFS 2018 - Custom Build Task with boolean parameters - powershell

I've created a custom build task and its extension for TFS 2018. Two of my parameter are boolean. On task.json I set the default value to false. When I execute a build definition with my task I get the following error:
System.Management.Automation.ParameterBindingArgumentTransformationException:
Cannot process argument transformation on parameter 'isToDeploy'.
Cannot convert value "System.String" to type "System.Boolean". Boolean
parameters accept only Boolean values and numbers, such as $True,
$False, 1 or 0. --->
System.Management.Automation.ArgumentTransformationMetadataException:
Cannot convert value "System.String" to type "System.Boolean". Boolean
parameters accept only Boolean values and numbers, such as $True,
$False, 1 or 0. --->
System.Management.Automation.PSInvalidCastException: Cannot convert
value "System.String" to type "System.Boolean". Boolean parameters
accept only Boolean values and numbers, such as $True, $False, 1 or 0.
Here is my powershell
[CmdletBinding()]
param(
[string][Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()] $qaApiEndpoint,
[string][Parameter(Mandatory=$true)][ValidateNotNullOrEmpty()] $deployfxEndpoint,
[bool][Parameter(Mandatory=$true)] $isToDeploy,
[string][Parameter(Mandatory=$false)] $deploymentType,
[string][Parameter(Mandatory=$false)] $environment,
[bool][Parameter(Mandatory=$false)] $isToArchivePackage,
[string][Parameter(Mandatory=$false)] $archiveLocation
)
Here is my Task.json (input part) (ignore ??? as this task is still in development)
"inputs": [
{
"name": "qaApiEndpoint",
"type": "string",
"label": "QA Web API Endpoint",
"defaultValue": "",
"required": true,
"helpMarkDown": "Endpoint for the QA Web API.",
"groupName": "GeneralGroup"
},
{
"name": "deployfxEndpoint",
"type": "string",
"label": "Deploy Fx Endpoint???",
"defaultValue": "",
"required": true,
"helpMarkDown": "???",
"groupName": "GeneralGroup"
},
{
"name": "isToDeploy",
"type": "boolean",
"label": "Deploy?",
"defaultValue": false,
"required": false,
"helpMarkDown": "Should the task perform the application's deployment?",
"groupName": "DeploymentGroup"
},
{
"name": "deploymentType",
"type": "string",
"label": "Deployment Type",
"defaultValue": "",
"required": false,
"helpMarkDown": "Ex: Full, Update, Patch",
"groupName": "DeploymentGroup"
},
{
"name": "environment",
"type": "string",
"label": "Environment to deploy",
"defaultValue": "",
"required": false,
"helpMarkDown": "Ex: DEV, TST, QA, PRD",
"groupName": "DeploymentGroup"
},
{
"name": "isToArchivePackage",
"type": "boolean",
"label": "Archive Package?",
"defaultValue": false,
"required": false,
"helpMarkDown": "Should the package be archived?",
"groupName": "PackageGroup"
},
{
"name": "archiveLocation",
"type": "string",
"label": "Archive Location",
"defaultValue": "",
"required": false,
"helpMarkDown": "Path for the package archive",
"groupName": "PackageGroup"
}
]
Like the error message says, I've already tried to change the value, in Task.json, for the booleans to $False, 0 and even "false". I've also tried to change the type to bool instead and even change the parameters in json and powershell to string and then convert the values to boolean in powershell. All my tries ended up with the same error.
One thing even weirder is that I've deletied the booleans from powershell and json but I got the same error...which makes no sense at all and makes me question if i'm hitting some cache issue or something. (yes, I've restarted the machine in between some of the times).
==Edit==
The weird behavior above happens because I wasn't updating the task.json and vss-extension.json id. Need to do it between each try.
==End Edit==
The way I do to "update" my customs task is, I simply delete the task in the build definition, delete the extension from the collection and uninstall it from TFS, then I install everything again.
Combinations tested
boolean and false
boolean and 0
boolean and "false"
boolean and $False
boolean and "$False"
boolean and "$false"
boolean and $false
bool and 0

Not sure if this is still relevant or if you found a solution already.
Just providing an answer because I ran into the same issue a couple of days ago.
Was getting the same error and it appears that TFS stores its inputs as strings. For me the solution was to add -AsBool to the declaration of my variable / parameter in the executed PS1 script:
[CmdletBinding()]
[bool]$ExcludeGated = Get-VstsInput -Name ExcludeGated -AsBool
This however does require the use of function Get-VstsInput which comes with VstsTaskSdk
The JSON part for this particular Boolean looks like a standard entry:
{
"name": "ExcludeGated",
"type": "Boolean",
"label": "Gated Exclusion flag",
"defaultValue": "true",
"required": false,
"helpMarkDown": "your helpful markdown comment here"
}

Related

How get the latest value in pick list

I'm trying to develop an extension/pipeline task for Azure DevOps for release gates. Not getting how to bind the the latest value to the pick list as shown here:
Each time a build completes, pick list should have latest build Id as the selected value.
Here is the code snippet i tried,
This creates a pick list
{
"name": "buildId",
"type": "pickList",
"label": "Artifact",
"required": true,
"defaultValue": "Latest",
"properties": {
"DisableManageLink": "True",
"EditableOptions": "True"
}
"helpMarkDown": "Build Artifacts"
},
this binds the values for the picklist
{
"target": "buildId",
"endpointId": "tfs:teamfoundation",
"endpointUrl": "{{endpoint.url}}/{{system.teamProject}}/_apis/build/builds?api-version=5.0",
"resultSelector": "jsonpath:$.value[*]",
"resultTemplate": "{ \"Value\" : \"{{{id}}}\", \"DisplayValue\" : \"{{{buildNumber}}} \" }"
}
Let me know if there are any way to get this.
I afraid you will need to implement your own code methods to bind latest value to the latest buildid.
For example in Publish build artifacts task. The latest build id is retrieved according to the selected value(ie. latest) of field buildVersionToDownload: See below:
buildVersionToDownload has three options.
{
"name": "buildVersionToDownload",
"type": "pickList",
"label": "Build version to download",
"defaultValue": "latest",
"visibleRule": "buildType == specific",
"required": true,
"options": {
"latest": "Latest",
"latestFromBranch": "Latest from specific branch and specified Build Tags",
"specific": "Specific version"
}
}
If the buildVersionToDownload value is selected as specific. Then it is required to select the build id in the Build picklist.
{
"name": "buildId",
"type": "pickList",
"label": "Build",
"defaultValue": "",
"required": true,
"visibleRule": "buildType == specific && buildVersionToDownload == specific",
"properties": {
"EditableOptions": "True",
"DisableManageLink": "True"
},
"helpMarkDown": "The build from which to download the artifacts"
}
buildVersionToDownload is not bound to dataSource, only buildId is bound:
{
"endpointId": "tfs:teamfoundation",
"target": "buildId",
"endpointUrl": "{{endpoint.url}}/{{project}}/_apis/build/builds?definitions={{definition}}&resultFilter=succeeded,partiallySucceeded&$top=200",
"resultSelector": "jsonpath:$.value[*]",
"parameters": {
"project": "$(project)",
"definition": "$(definition)"
},
"resultTemplate": "{ \"Value\" : \"{{{id}}}\", \"DisplayValue\" : \"{{{buildNumber}}}\" }"
}
See here for more information.
If buildVersionToDownload is selected as latest. Then the latest build id is retrieved via code. See code example here
So that in your custom task. When the Default Version is latest. You can get the latest build id in your code methods. And when Default Version is not latest. Then make the Artifact visible to select a build id from the picklist.

Azure DevOps set MultiLine Row parameter

Is it possible to set the multiLine row parameter in the task.json file for a custom task?
{
"name": "Include",
"type": "multiLine",
"label": "Include",
"defaultValue": "#(\"*.sln\")",
"required": false,
"helpMarkDown": "example help"
}
I recocnize, that the only difference between a multiLine textbox and inline textbox (PowerShell Task) is the amount of rows:
The default amount of rows of a multiLine textbox is 2.
So it would be create to define the amount a rows in a multiLine control.
Is it possible to set the multiLine row parameter in the task.json
file for a custom task?
It's possible. Use .net core task have a textbox Path to projects:
Corresponding html see here:
Which is default rows of MultiLine type.
So I compared the source of Use .net core task and PS task here and found:
{
"name": "projects",
"type": "multiLine",
"label": "Path to project(s)",
"defaultValue": "",
"visibleRule": "command = build || command = restore || command = run || command = test || command = custom || publishWebProjects = false",
"required": false,
"helpMarkDown": "The path to the csproj file(s) to use. You can use wildcards (e.g. **/*.csproj for all .csproj files in all subfolders)."
}
and this:
{
"name": "script",
"type": "multiLine",
"label": "Script",
"visibleRule": "targetType = inline",
"required": true,
"defaultValue": "# Write your PowerShell commands here.\n\nWrite-Host \"Hello World\"\n",
"properties": {
"resizable": "true",
"rows": "10",
"maxLength": "20000"
},
"helpMarkDown": ""
}
The first script comes from dotnet task and second one comes from PS task. They both use MultiLine type.
According to the difference between these two scripts I think you can get what you want by setting the rows element in properties element. Something like this:
"properties": {
...,
"rows": "xxx",
...
}
Hope it helps and if I misunderstand anything, feel free to correct me :)

Populate picklist dynamically based on filepath type in Azure Devops?

In Azure Devops, I am building one extension with build task where input picklist field is dependent on the another input filepath field.
When the user provides an input filepath, I wanted to dynamically read and populate the picklist with new items.
I tried to search and implement but couldn't find a way.
Any help or guidance will be appreciated. I found the below example to help me out for static values.
{
"name": "action",
"type": "pickList",
"label": "Action",
"defaultValue": "Publish",
"required": true,
"helpMarkDown": "Select the Action to perform",
"options": {
"Publish": "Publish Changes",
"Script": "Script Changes",
"DeployReport": "Generate Deployment Report"
}
}
The out-of-box tasks are open source, so you can take a look through the repository to get some ideas of the patterns they use. Here is an example from the Download Build Artifacts task. The build definition is dependent on the project that is defined.
{
"name": "project",
"type": "pickList",
"label": "Project",
"defaultValue": "",
"required": true,
"visibleRule": "buildType == specific",
"properties": {
"EditableOptions": "True",
"DisableManageLink": "True"
},
"helpMarkDown": "The project from which to download the build artifacts"
},
{
"name": "definition",
"aliases": [
"pipeline"
],
"type": "pickList",
"label": "Build pipeline",
"defaultValue": "",
"required": true,
"visibleRule": "buildType == specific",
"properties": {
"EditableOptions": "True",
"DisableManageLink": "True",
"IsSearchable": "True"
},
"helpMarkDown": "Select the build pipeline name"
},
Then in the dataSourceBindings section believe it uses the target from the previous selection as a parameter input.
"dataSourceBindings": [
{
"endpointId": "tfs:teamfoundation",
"target": "project",
"endpointUrl": "{{endpoint.url}}/_apis/projects?$skip={{skip}}&$top=1000",
"resultSelector": "jsonpath:$.value[?(#.state=='wellFormed')]",
"resultTemplate": "{ \"Value\" : \"{{{id}}}\", \"DisplayValue\" : \"{{{name}}}\" }",
"callbackContextTemplate": "{\"skip\": \"{{add skip 1000}}\"}",
"callbackRequiredTemplate": "{{isEqualNumber result.count 1000}}",
"initialContextTemplate": "{\"skip\": \"0\"}"
},
{
"endpointId": "tfs:teamfoundation",
"target": "definition",
"endpointUrl": "{{endpoint.url}}/{{project}}/_apis/build/definitions?api-version=3.0-preview&$top=500&continuationToken={{{continuationToken}}}&name=*{{name}}*&queryOrder=2",
"resultSelector": "jsonpath:$.value[?(#.quality=='definition')]",
"parameters": {
"project": "$(project)",
"name": "$(name)"
},
"resultTemplate": "{ \"Value\" : \"{{{id}}}\", \"DisplayValue\" : \"{{{name}}}\" }",
"callbackContextTemplate": "{\"continuationToken\" : \"{{{headers.x-ms-continuationtoken}}}\"}",
"callbackRequiredTemplate": "{{{#headers.x-ms-continuationtoken}}}true{{{/headers.x-ms-continuationtoken}}}",
"initialContextTemplate": "{\"continuationToken\" : \"{{{system.utcNow}}}\"}"
},

Task.Json triggers validation on invisible only fields

I am trying to create a Azure DevOps Pipelines Custom extension. I have a task.json where fields are visible on certain conditions.
For example:
{
"name": "actions",
"type": "picklist",
"label": "Actions",
"defaultValue": "Select",
"required": true,
"helpMarkDown": "Select an Action from the dropdown as per your requirement.",
"options": {
"New": "Add",
"Delete": "Delete"
}
},
{
"name": "backEndIPAddress",
"type": "string",
"label": "IP Address",
"required": true,
"defaultValue": "",
"helpMarkDown": "",
"visibleRule": "actions = New",
"validation": {
"expression": "isMatch(value,'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?){0,15}$','IgnoreCase')",
"message": "Invalid IP Address. Please try again!"
}
}
The issue when the IPAddress field is hidden, the validation is still tried and it fails. How to ensure that the fields are validated only if they are visible?
A few options:
Set a default value for the input
Update the regex to include ^$| at the start to allow empty values ('require':true will take care of the requiredness)
Remember that there should be a default isIpV4Address(value: string) function so you don't have to specify the regex.
From the docs it looks like there is an upcoming when clause which will dictate when the set of rules should trigger, I suppose this may be causing the currently unwanted behavior.
See also:
https://github.com/Microsoft/vsts-tasks/blob/1d75fa8f66aa1cf7a9cb62946939f30f087b2969/docs/taskinputvalidation.md

TFS 2015.3 custom build step not sending variables to the script

I've followed closely the design guidance found here, here and here, but I keep getting this PowerShell error:
Cannot process command because of one or more missing mandatory parameters: SourcePath FilePattern BuildRegex.
The relevant config data is below.
I've checked and double-checked to make sure that the variables are present in my task.json file. I've also looked at the config for other working tasks (e.g. VSBuild) and there's no significant difference in the variable declaration and PowerShell execution syntax.
What could be going wrong here? This is a very simple architecture—there's not much to break. But clearly something has done just that.
From task.json:
"inputs": [
{
"name": "SourcePath",
"type": "filePath",
"label": "Source path",
"defaultValue": "",
"required": true,
"helpMarkDown": "Path in which to search for version files (like AssemblyInfo.* files). NOTE: this is case sensitive for non-Windows systems."
},
{
"name": "FilePattern",
"type": "string",
"label": "File pattern",
"defaultValue": "AssemblyInfo.*",
"required": true,
"helpMarkDown": "File filter to replace version info. The version number pattern should exist somewhere in the file(s). Supports minimatch. NOTE: this is casese sensitive for non-Windows systems."
},
{
"name": "BuildRegEx",
"type": "string",
"label": "Build RegEx pattern",
"defaultValue": "\\d+\\.\\d+\\.\\d+\\.\\d+",
"required": true,
"helpMarkDown": "Regular Expression to extract version from build number. This is also the default replace RegEx (unless otherwise specified in Advanced settings)."
},
{
"name": "BuildRegExIndex",
"type": "string",
"label": "Build RegEx group index",
"defaultValue": "0",
"required": false,
"helpMarkDown": "Index of the group in the Build RegEx that you want to use as the version number. Leave as 0 if you have no groups.",
"groupName": "advanced"
},
{
"name": "ReplaceRegEx",
"type": "string",
"label": "RegEx replace pattern",
"defaultValue": "",
"required": false,
"helpMarkDown": "RegEx to replace with in files. Leave blank to use the Build RegEx Pattern.",
"groupName": "advanced"
},
{
"name": "ReplacePrefix",
"type": "string",
"label": "Prefix for replacements",
"defaultValue": "",
"required": false,
"helpMarkDown": "Prefix for the RegEx result string.",
"groupName": "advanced"
},
{
"name": "ReplaceSuffix",
"type": "string",
"label": "Suffix for replacements",
"defaultValue": "",
"required": false,
"helpMarkDown": "Suffix for the RegEx result string.",
"groupName": "advanced"
},
{
"name": "FailIfNoMatchFound",
"type": "boolean",
"label": "Fail if no target match found",
"defaultValue": "false",
"required": false,
"helpMarkDown": "Fail the build if no match is found for the replace RegEx in the target file(s).",
"groupName": "advanced"
}
],
"execution": {
"PowerShell3": {
"target": "VersionAssembly.ps1"
}
}
From VersionAssembly.ps1:
[CmdletBinding()]
param(
[string][Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()] $SourcePath,
[string][Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()] $FilePattern,
[string][Parameter(Mandatory=$True)][ValidateNotNullOrEmpty()] $BuildRegex,
[string]$BuildRegexIndex,
[string]$ReplaceRegex,
[string]$ReplacePrefix,
[string]$ReplaceSuffix,
[string]$FailIfNoMatchFound,
[string]$BuildNumber = $ENV:BUILD_BUILDNUMBER
)
Apparently I wasn't following closely enough... I missed the warning on this page:
Words of warning
Tasks can be versioned, use this to your advantage. All build definitions use the latest available version of a specific task, you can’t change this behavior from the web interface, so always assume the latest version is being used.
If you don’t change the version number of your task when updating it, the build agents that have previously used your task will not download the newer version because the version number is still the same. This means that if you change the behavior of your task, you should always update the version number!
Once I got that all straightened out, everything worked fine.
Possibly the examples accepting inputs in the param section are out of date. It appears that you now need to use the Vsts-task-lib commands from your PowerShell script to get the input parameters.
[CmdletBinding()]
param()
$myParam = Get-VstsInput -Name myParam -Require