PowerShell, TFS/VSTS Build and Release – There is more than meets the eye

Planning, Scheduling and Executing – A Deeper Look [Part 1]
December 25, 2017

PowerShell, TFS/VSTS Build and Release – There is more than meets the eye

Recently I was involved in an effort to create automated builds and releases using the capabilities of Microsoft’s Team Foundation Server [TFS] and Visual Studio Team Services [VSTS]. Since the existing approach included using Remote Desktop to access the target machines and then running Scripts, it initially appeared that this aspect would be a near trivial exercise. After all, the tooling already has a “Run PowerShell on Target” task to remotely execute scripts which exist on remote machines.

It was quickly discovered that using this task had a number of significant differences in the experience compared to running the scripts within a PowerShell ISE environment while local on the machine.  While a comprehensive list of all of the considerations would take far more than a blog post, being aware of some of the key considerations has value.

Nearly all of the differences are related to the handling of the various streams [Write-Host, Write-Output, Write-Verbose, Write-Warning, Write-Error]. The differences in Write-Output specifically impact the use of “return codes”.

Local PowerShell Execution Options

PowerShell ISE vs. PowerShell.exe 

For frequent PowerShell developers, this comes as no surprise. However it may also be a surprise to many. The root cause is that many of the behaviors of the PowerShell environment, especially those related to the various streams, delegate the implementation to the host.  Clearly when doing any automated work, ISE is not going to be the selected host; this means that testing (from a very early stage) using powershell.exe is an important step.

Running PowerShell locally within Build/Release

The most common way to run local PowerShell within a Task is to use the “PowerShell” task provided with TFS/VSTS. When this is done, the Task internally makes use of Invoke-Command to run the designated script within a controlled environment. This invocation occurs inside of a PowerShell context that is created by the Task and therefore has some subtle differences compared to invoking the same script via PowerShell.exe.

Alternatively, one can use the “Run Command” to directly execute PowerShell.exe and pass the desired script or a script block which invokes the desired script. The differences between these two can be identified by swapping between the two tasks and examining both the live logging windows and the resultant log files.

Remote PowerShell Execution Options

Locally utilize Invoke-Command and specify target Machine

This approach allows for any of the above local techniques to be used as a bootstrap.

Run PowerShell.exe on the Remote Machine

WinRM is the underlying technology behind most remote execution, and the WinRM command line utility can be leveraged for this purpose.

Use the “PowerShell on Target Task”

This approach is significantly different than either of the above remote approaches. Network connectivity issues between the agent machine and the machine executing the script will cause both “Invoke-Command” and “WinRS” implementations to fail, as they require a constant connection. For many environments, this can be problematic as the script may continue running, but the agent will be incapable of retrieving the output or determining if the script rant to a successful completion.

For this reason, the TFS/VSTS task establishes an environment that can continue to execute independent of network connectivity, and post back the results when the script has completed (presuming the network issue was intermittent and has been re-established). As the saying goes: “There ain’t no such thing as a free lunch” [TANSTAAFL] and this decision has a number of effects that may be surprising or problematic.

  1. Streams are cached, and do not appear “in real time”
  2. Write-Output messages to not get processed in the event of an error or exception (any information in the error stream). The general recommendation here is to use “Write-Verbose”
  3. Certain cmdlet [notably some related to IIS] impact the verbose stream. Any writes to the stream after invocation of the cmdlet will not appear
  4. Streams are deserialized sequentially. Therefore the ordering of writes to various streams is known relative to the writes to other streams. Based on observation, the Error stream gets deserialized first, then the Verbose Stream, and finally – if there were no errors – the Output stream.


If caught unaware, these elements can quickly become distractions or problems. Once one is aware of the differences, the Builds/Releases along with the scripts can be designed to be effectively used in the most appropriate invocation context for a given situation








Leave a Reply

Your email address will not be published. Required fields are marked *