Using Visual Studio Code to develop VSTS Build Tasks with PowerShell and Pester tests

Background

I am finding  myself writing a lot of PowerShell at present, mostly for VSTS build extensions. Here I hit a problem (or is it an opportunity for choice?) as to what development environment to use?

  • PowerShell ISE is the ‘best’ experience for debugging a script, but has no source control integration – and it is on all PCs
  • Visual Studio Code has good Git support, but you need to jump through some hoops to get debugging working.
  • Visual Studio PowerShell tools, are just too heavy weight, it is not even in the frame for me for this job.

So I have found myself getting the basic scripts working in the PowerShell ISE then moving to VS Code to package up the task/extensions as this means writing .JSON too – so awkward

This gets worse when I want to add Pester based unit tests, I needed a better way of working, and I chose to focus on VS Code

The PowerShell Extension for VS Code

Visual Studio Code now supports PowerShell. Once you have installed VS Code you can install the extension as follows

  1. Open the command pallet (Ctrl+Shift+P)
  2. Type “Extension”
  3. Select “Install Extensions”. 
  4. Once the extensions list loads, type PowerShell and press Enter.

Once this extension is installed you get Intellisense etc. as you would expect. So you have a good editor experience, but we still need a F5 debugging experience.

Setting up the F5 Debugging experience

Visual Studio Code can launch any tool to provide a debugging experience. The PowerShell extension provides the tools to get this running for PowerShell.

I found Keith Hill provided a nice walkthrough with screenshots of the setup, but here is my quick summary

  1. Open VS Code and load a folder structure, for me this usually this will be a Git repo
  2. Assuming the PowerShell extension is installed, goto the debug page in VS Code
  3. Press the cog at the top of the page and a .vscodelaunch.json file will be added to the root of the folder structure currently loaded i.e. the root of your Git repo
  4. As Keith points out the important line, the program, the file/task to run when you press F5 is empty – a strange empty default.

image

We need to edit this file to tell it what to run when we press F5. I have decided I have two options and it depends on what I am putting in my Git Repo as to which I use

  • If we want to run the PowerShell file we have in focus in VS Code (at the moment we press F5) then we need the line

              "program": "${file}"

  • However, I soon released this was not that useful as I wanted to run Pester based tests. I was usually editing a script file but wanted to run a test script. So this meant changing the file in focus prior to pressing F5. In this case I decided it was easier to hard code the program setting to run to a script that ran all the Pester tests in my folder structure

               "program": "${workspaceRoot}/Extensions/Tests/runtests.ps1"

    Where my script contained the single line to run the tests in the script’s folder and below

               Invoke-Pester $PSScriptRoot –Verbose

Note: I have seen some comments that if you edit the launch.json file you need to reload VS Code for it to be read the new value, but this has not been my experience

So now when I press F5 my Pester tests run, I can debug into them as I want, but that raises some new issues due to the requirements of VSTS build tasks

Changes to my build task to enable testing

A VSTS build task is basically a PowerShell script that has some parameters. The problem is I needed to load the .PS1 script to allow any Pester tests to execute functions in the script file. This is done using the form

# Load the script under test
. "$PSScriptRoot......versioningversiondacpactaskUpdate-DacPacVersionNumber.ps1"

Problem 1: If any of the parameters for the script are mandatory this include fails with errors over missing values. The fix is to make sure that any mandatory parameters are passed or they are not mandatory – I chose the latter as I can make any task parameter ‘required’ in the task.json file

Problem 2: When you include the script it is executed – not what I wanted at all. I had to put a guard if test at the top of the script to exit if the required parameters were not at least reasonable – I can’t think of a neater solution

# check if we are in test mode i.e.
If ($VersionNumber -eq "" -and $path -eq "") {Exit}
# the rest of my code …..

Once these changes were made I was able to run the Pester tests with an F5 as I wanted using mocks to help test program flow logic

# Load the script under test
. "$PSScriptRoot......versioningversiondacpactaskUpdate-DacPacVersionNumber.ps1"

Describe "Use SQL2012 ToolPath settings" {
    Mock Test-Path  {return $false} -ParameterFilter {
            $Path -eq "C:Program Files (x86)Microsoft Visual Studio 14.0Common7IDEExtensionsMicrosoftSQLDBDAC120Microsoft.SqlServer.Dac.Extensions.dll"
        }
    Mock Test-Path  {return $true} -ParameterFilter {
            $Path -eq "C:Program Files (x86)Microsoft Visual Studio 12.0Common7IDEExtensionsMicrosoftSQLDBDAC120Microsoft.SqlServer.Dac.Extensions.dll"
        }    
 
    It "Find DLLs" {
        $path = Get-Toolpath -ToolPath ""
        $path | Should be "C:Program Files (x86)Microsoft Visual Studio 12.0Common7IDEExtensionsMicrosoftSQLDBDAC120"
    }
}

Summary

So I think I now have a workable solution with a good IDE with a reasonable F5 debug experience. Ok the PowerShell console in VS Code is not as rich as that in the PowerShell ISE, but I think I can live with that given the quality of the rest of the debug tools.