Joining strings in pipeline

In PowerShell, I’ve been joining string arrays for quite some time with the join operator which concatenates a set of strings into a single string. Today however, I found myself in need to join strings that are coming from the pipeline, without previously saving the result into a variable. Guess what, there is nothing ready, out of the box, that will do the trick.
In case you would like to join the strings with new line character as a delimiter (or with no delimiter at all), you can always use Out-String cmdlet, eventually with it’s -NoNewline parameter. However, as far as I know, there are no cmdlets available that will perform this simple operation.

This pushed me to write a simple one that should do the trick.

function Join-String
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)][string]$String,
        [Parameter(Position = 1)][string]$Delimiter = ""
    )
    BEGIN {$items = @() }
    PROCESS { $items += $String }
    END { return ($items -join $Delimiter) }
}

Invoking this cmdlet is quite trivial, however, for completeness here is an example.

$commaSepareted = @("one","two","three") | Join-String ","

That’s it folks!

Is there a more elegant or out of the box way to achieve the same?

Automate setting ‘Retain indefinitely’ flag on your releases

Overview

As a common practice, after a successful release to production, often there is a need to retain the involved artifact and relevant release information for a certain amount of time. In order to avoid that a retention policy removes this information, you will mark that release with a Retain indefinitely flag, by choosing that option from the VSTS UI.

As this is a manual process that I would like to automate, I will use the Retain indefinitely current release task available in VSTS Marketplace.

After installing the extension, in the list of available tasks, check the Utility category and you’ll find the above-mentioned task.

Only a single parameter is presented by the task in a form of a checkbox labelled Mark the current release to be retained indefinitely. By default is set to true. If checked, it will mark the current release with Retain indefinitely flag. Otherwise, it will take the Retain indefinitely flag off the current release.

I will add it only to my production environment process and I will place it as the last task. E.g.

This is because I do not want this task to execute if any of the previous tasks in the process do fail. It should execute only if I managed to deploy my application in production. That is the criteria for retaining this release for a longer time.
The only requirement for this task to run is that the account on which the build agent is running has sufficient privileges to set Retain indefinitely flag.

That’s all. Now you do not need to remember to set the Retain indefinitely flag after every successful production release.

UPDATE:

After extensive testing, I noticed that it’s mostly the case that additional rights do need to be granted for the build agent identity in order for this task to succeed.
In case the permissions are missing, a similar error will be visible in the log:

##[error]VS402904: Access denied: User Project Collection Build Service (mummy) does not have manage releases permission. Contact your release manager.

Edit the security settings for that particular release or for all the releases and set the following to the needed account.

Make sure ‘Manage Releases’ permission is granted for the indicated user.

Another note is that also the cross-platform agents are supported starting from the version 2.x of the extension. If you check on GitHub you’ll see that the task has been re-written and it is now based on node provider with the code written in TypeScript.

Uploading build/release tasks to VSTS

Let’s start with why? Why would someone upload a task directly to TFS/VSTS? You can just install or update the extension that added those tasks, no?!?! So why?
Obviously there are many reasons, and aside the development of the tasks themselves, often is a case when you find and fix a bug in a task that you need to get in production ASAP. Notifying third party and waiting for a new version of the extension is often not acceptable.
But there is already a tool that Microsoft made for handling tasks! Does TFS-CLI tells you nothing? Sure, tool that works very well and which I mentioned already in several occasions on my blog. Still, getting it requires Java Runtime Environment, NodeJs, tool itself, configuration. These are often not available out of the box and if you are doing this things occasionally, a better way may be a simple script.

Following is a script I do use:

param(
   [Parameter(Mandatory=$true)][string]$TaskPath,
   [Parameter(Mandatory=$true)][string]$VstsUrl,
   [Parameter(Mandatory=$true)][string]$Pat
)

# Load task definition from the JSON file
$taskDefinition = (Get-Content $taskPath\task.json) -join "`n" | ConvertFrom-Json
$taskFolder = Get-Item $TaskPath

# Zip the task content
Write-Output "Zipping task content"
$taskZip = ("{0}\..\{1}.zip" -f $taskFolder, $taskDefinition.id)
if (Test-Path $taskZip) { Remove-Item $taskZip }

Add-Type -AssemblyName "System.IO.Compression.FileSystem"
[IO.Compression.ZipFile]::CreateFromDirectory($taskFolder, $taskZip)

# Prepare to upload the task
Write-Output "Uploading task content to $VstsUrl"

$taskZipItem = Get-Item $taskZip
$encodedCredentials = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$Pat"))

$headers = @{ Authorization = "Basic $encodedCredentials"; "Content-Range" = "bytes 0-$($taskZipItem.Length - 1)/$($taskZipItem.Length)"}

$url = "{0}/_apis/distributedtask/tasks/{1}?api-version=2.0-preview" -f $VstsUrl, $taskDefinition.id

try
{
	Invoke-RestMethod -Uri $url -Headers $headers -ContentType application/octet-stream -Method Put -InFile $taskZipItem
	Write-Host "Task uploaded successfully" -ForegroundColor Green
}
finally
{
    Remove-Item -LiteralPath $taskZip -Force
}

To invoke this, it is sufficient to provide the path to the folder containing the task, url towards your VSTS account (in case of TFS, path to the collection) and your personal access token. E.g.

.\TaskUploader.ps1 .\task\ https://myaccount.visualstudio.com jx3sa4h3cb56ehftgrjitj6kctei3pp7usuyxkoim5vpeqcgn7eq

That’s all.