Test VSTS build tasks written in PowerShell with PowerShell Tools for Visual Studio 2015

Below is the folder structure of an extension I am working on.  I want to test all four tasks in a single project using the PowerShell Tools for Visual Studio extension.

Dot Sourcing files

When I use the PowerShell Tools for Visual Studio, I link the files to my test project so as to not disrupt the task folder structure.

By simply linking to the file, it is not copied to the test project folder.  Because the files are not in the test project folder, this can cause issues when you try to dot source a file to load additional functions.

If you want to dot source a file the standard way,

. .\utility.ps1

it will only work if you copy the files to the project folder.  If you were to run the following command in a linked file,

(Get-Item -Path ".\" -Verbose).FullName

you will see that the working folder is not the folder of the file. It is the working folder of Visual Studio.

C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE

Because of this . .\ will not find the desired file.  You can overcome this by using the PSCmdlet.MyInvocation property of PowerShell like this.

. (Join-Path ($MyInvocation.MyCommand.Path.Replace($MyInvocation.MyCommand.Name,"")) utility.ps1)

This form will work anywhere regardless of if the file is linked or not.  This line is getting the Path to the currently executing script. This will include the filename as well. I use the Replace function to remove the filename. Finally, the Join-Path takes the resulting path and adds the name of the desired file which I know to be in the same directory.

Importing Distributed Task modules

Many custom tasks written for VSTS use PowerShell modules provided by the agent to perform actions in our task. 

# Import the Task.Common dll that has all the cmdlets we need for Build

import-module "Microsoft.TeamFoundation.DistributedTask.Task.Common"

If you were to simply run this command, you will get the following error.

For this command to work, you must have the modules on your development machine.  You can harvest these files from any machine currently running an agent.  Simply copy the contents of the [agentInstallfolder]\agent\worker\modules folder to C:\Program Files\WindowsPowerShell\Modules on your development machine.

You will also need to install a few assemblies into the Global Assembly Cache (GAC) depending on the modules you import.  For example, Microsoft.TeamFoundation.DistributedTask.Task.Common requires Microsoft.TeamFoundation.DistributedTask.Agent.Interfaces.dll. 

These DLLs can be found in the Worker folder of an agent.  Copy the required files to your development machine and start a Developer Command prompt as Administrator. Then, using gacutil, install the required DLLs into the GAC.

Make sure you restart Visual Studio after installing files into the GAC.  Now when you test your PowerShell, the module will successfully import.

Test with Pester

At this point, you are ready to start testing your task with Pester in Visual Studio.  There is already a lot of great information on Pester, so I will not go in to great detail in this post.

The test files are in the test project folder but the files we are testing are not.  At the top of each test file, you need to dot source the linked file from its real location.  Below is an example of how I brought in my utility script from one of my tasks.

$here = Split-Path -Parent $MyInvocation.MyCommand.Path

$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'

. "$here\..\DockerRunTask\$sut"

Notice on the line where I dot source the file I have to move up an over a directory into DockerRunTask to locate the file I want to test.

Mocking

There are certain aspects of your task that are going to rely on VSTS functionality that will not be present on your development machine.  You can use Pester Mocking to fake those portions for testing.  Just remember that Pester can only mock what it can find.  If you attempt to mock Get-ServiceEndpoint, which you will need to do if your task uses service endpoints, you might get the following error.

You can simply define an empty function with the same name above your test or create a script full of fake functions and dot source the file into your test project.  How many functions I need signatures for and how often I will need them determines if they are defined above my test or in a separate file.

File vs Functions

Normally, when using Pester, you dot source the script that defines lots of functions that you would like to test. Then you simply call the methods in your tests.  But what if your script does not define a named function?  You simply dot source your file in your test. That way, it is not loaded too early and you can pass in all the parameters you need, like below.

$here = Split-Path -Parent $MyInvocation.MyCommand.Path

$dir = $sut = $MyInvocation.MyCommand.Path -replace 'dockerrun.Tests.ps1', ''

$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'

 

function Get-ServiceEndpoint { }

 

Describe "dockerrun" {

Context "Exists" {

      # This is used to Get-Docker

      Mock Get-Item { return @{FullName = "c:\temp"} } -Verifiable

     

      # The path does not really exist but I need this to return

      # true anyway.

      Mock Test-Path { return $true } -Verifiable

 

      # This is a call that returns the Service Endpoint

      Mock Get-ServiceEndpoint { return @{"Url"="test"; "Authorization" = @{"Parameters" = @{"key"="key"; "username"="username"; "password"="password"} } } }

 

It "Runs" {

         . "$here\..\DockerRunTask\$sut" -connectedServiceName "d314d0f0-4295-4537-84d5-897b8ef6bc4c" -cwd "c:\temp" -imageName peopletracker -authOptions tls -ports 8080:8080 -removeConflictingContainers "False" -verbose

}

}

}

Notice how I dot source file in the call to It.

I hope this helps you speed up the development time for VSTS tasks.

Comments (11) -

  • Brandon Martinez

    1/1/2016 1:22:53 PM | Reply

    Were you able to use the TFS modules with a basic Import-Module on VSTS? I had to reference the path directly after I deployed: www.brandonmartinez.com/.../

    • Donovan

      1/1/2016 4:24:27 PM | Reply

      I was able to use with basic Import-Module.  I just copied them from the agent to my dev machine in the correct folder.  Once I added the dependencies to the GAC it all worked.  No need for direct reference to module path needed.

  • Alex Sarafian

    5/21/2016 11:13:37 PM | Reply

    Hi,
    I've been following your efforts on promoting vsts automation. I've recently engaged automation on vsts where my main working assets are PowerShell. I need to build modules or run scripts.

    Add the main building block for PowerShell is modules, i'm having a lot of difficulties with hosted agents.

    How to install on the hosted agent a module from the gallery? How to publish to the gallery? In both cases you need PowershellGet module and that is not available. You can do dinner tricks like download the files and import ththem or even include the entire module as isuggesteded for pester unit testing.

    Any feedback?

    You can take a look in my efforts at https://sarafian.github.io/

    • Donovan

      5/30/2016 3:03:03 PM | Reply

      Alex,

      Modules can be a challenge.  They are when working with DSC as well.  But as part of my deployment I have to make sure the needed files are on the machine.  I download or add to source if I have too.

  • Paul

    3/6/2017 10:44:18 PM | Reply

    Hi Donavan,

    Do you know if it is possible to import custom PowerShell modules for use in a VSTS Release Management task?

    Thanks!

    • Donovan

      3/9/2017 12:40:52 AM | Reply

      With a private agent yes. You might try the install-module cmdlet with the Powershell task. What module do you need so I can try some testing?

  • Xtian Glaindo

    3/16/2017 8:49:36 PM | Reply

    Trying to import the Microsoft.TeamFoundation.DistributedTask.Task.Common.dll.  but in the VSOagent (version 1.95.3 ) that dll is not present.

    How can I get the module mention there? Is it in other dll?

  • Denis Garon

    10/3/2017 5:38:06 PM | Reply

    Hi,

    Maybe you can answer, i see you know well VSTS. Im a admin and I just try to get information from our portal in VSTS.  So the portail is like this URL :  https://corp.visualstudio.com/_admin/_users

    I try to get information using POWERSELL, a list of all users  with there ACCESS LEVEL in VSTS portal.

    So far i didnt find any PowerShell module to access information on the VSTS portal.  I need to automate this list to process this information for the CORP need, then send by email the information.

    Its like click on "EXPORT USERS" on top on the window of the menu /_admin/_users of the portal.

    To you know if MS VSTS have made a PowerShell module to get information from there portal, like they do with MSOL  (Azure and Off365) ?

    • Donovan

      10/5/2017 6:42:03 PM | Reply

      Here is an open source module I started. I don't have users there yet but you can see how we wrapped the other REST API and issue a pull request.

      https://github.com/DarqueWarrior/vsteam

  • Sarah Sloan

    8/9/2018 7:14:54 AM | Reply

    Hi Donovan,

    I'm writing an extension where some of my tasks need to share common code. I don't really want to have to install my own powershell module on all our agents, so is there a way of packaging up shared modules within an extension such that any task that needs them will download them with the file for that task please?

    I have tried a directory structure like the following and just importing MyModule.psm1 from MyBuildTask.ps1 but the psm1 file does not get downloaded to the agent.

    My Extension
    |- MyBuildTask
    |   |- MyBuildTask.ps1
    |   |- task.json
    |- MyReleaseTask
    |   |- MyReleaseTask.ps1
    |   |- task.json
    |- Common
    |   |- MyModule.psm1
    |-vss-extension.json

    Thanks,
    Sarah

    • Harvard

      3/12/2021 5:01:07 AM | Reply

      I am interested in the same approach.  Any updates on this?

Add comment

Loading