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”.
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.
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.
This approach allows for any of the above local techniques to be used as a bootstrap.
WinRM is the underlying technology behind most remote execution, and the WinRM command line utility can be leveraged for this purpose.
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.
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