Deploy a database with dacpac from a VSO build using a custom task

While using the new scriptable build system in VSO, there have been countless times I wanted to create my own custom build/release task.  That day has finally arrived.  The ability to upload custom build and release tasks to VSO has now been made public by Microsoft.  It is still in the “pre-release” stage so you might run into some problems when using the task uploader tool. However, it seems to work pretty well and I will now walk you through creating and uploading your own VSO build/release task.

Step1: Installing tfx-cli

The task uploader tool is called TFS Extensions Command Line Utility or tfx-cli for short.  To install tfx-cli, you first need to make sure you have npm installed.  The easiest way to get npm is to install node.  Once node is installed, open a command window and type

npm

You should see the following:

Now, to install tfx-cli, type:

npm install –g tfx-cli

After tfx-cli is installed, from the command line, type:

tfx

You should see the following:

Step 2: Login to VSO with tfx-cli

You will need to use tfx-cli to log into VSO.  In this example, I used a personal access token to log into TFS.  If you have a personal access token, you can skip the next step.

Creating your personal access token

1. Log into your VSO with your browser

2. Edit your profile by clicking on your name and select My Profile

3. From your profile page, click on Security

4. This will bring up the the security page.  Click on Personal access token , then click Add

5. this will bring up the Create a personal access token page.  Fill in description, select an expires, select an accounts, chose All scopes and click Create Token

6. This will create your personal access token.  Copy this down somewhere safe as TFS will not save the actual token for you and you will not be able to get this tokens value again.

Logging into VSO with tfx-cli

1. From a command window type tfx login

2. Enter your collection url

3. Enter in your personal access token

Step 3: Create your task

For this example, I want to create a build task that will let me deploy a database via a dacpac.  In other words, I want a task which can use sqlpackage.exe to deploy the schema of a database.

Create a VSO build/deployment task stub

1. Create a directory for the new task and ce into that directory

2.  type tfx build tasks create

3. Enter a short name, a friendly name, a description and the author fields

This creates the stub for your custom task.  tfx-cli added the following into your dirctory:

Copy sqlpackage.exe files to your stub folder

When tfx-cli packages up your custom tasks and imports it into VSO, it will grab everything in the dacpacDeployTask directory.  Since I want to use sqlpackage.exe in my custom task, I copied all the binaries for sqlpackage.exe and  copy them into this folder.  I created a folder called SqlPackage in this directory, and then copied G:\Programs\VS2015\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\120 into that folder.  G:\Programs\VS2015 is where I installed VS2015 with SSDT (Sql Server Data Tools) and G:\Programs\VS2015\Common7\IDE\Extensions\Microsoft\SQLDB\DAC\120 is where all the binaries for sqlpackatge.exe sit.   After copying these files over, my folder structure now looks like this:

Create powershell script which calls sqlpackage.exe to deploy a database schema using a dacpac

Next, I created a power shell script which will use sqlpackage.exe to deploy a database using a dacpac and saved it into my dacpacDeployTask folder.  Here is the powershell script:

PowerShell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
param (
    [string]$cwd,
    [string]$connectionString,
    [string]$dacpacPath,
    [string]$blockOnPossibleDataLoss
)
 
Write-Output ‘Entering deployDacPac.ps1’
Write-Output “Parameters:”
Write-Output ”    cwd = $cwd”
Write-Output ”    connectionString = $connectionString”
Write-Output ”    dacpacPath = $dacpacPath”
Write-Output ”    blockOnPossibleDataLoss = $blockOnPossibleDataLoss”
 
# set the working dirctory
if(!$cwd)
{
    throw (Get-LocalizedString -Key “Working directory parameter is not set”)
}
 
if(!(Test-Path $cwd -PathType Container))
{
    throw (“$cwd does not exist”);
}
 
Write-Output “Creating task directory”
$taskDirectory = $cwd + “\tasks\dacpacDeployTask\0.1.0”
Write-Output ”    taskDirectory = $taskDirectory”
 
Write-Output “Setting working directory to $taskDirctory”
Set-Location $taskDirectory
 
Write-Output “calculating block value”
if ($blockOnPossibleDataLoss -eq “true”)
{
    $blockVar = $true
}
else {
    $blockVar = $false
}
Write-Output ”    blockVar = $blockVar”
 
Write-Output “Creating argument list”
$argumentList = “/Action:Publish /SourceFile:`”$dacpacPath`” /TargetConnectionString:`”$connectionString`” /p:BlockOnPossibleDataLoss=$blockVar”
Write-Output ”    argumentList = $argumentList”
 
Write-Output “Deploying database”
$sqlPackage = $cwd + “\tasks\dacpacDeployTask\0.1.0\SqlPackage\sqlpackage.exe”
Write-Output ”    sqlPackage = $sqlPackage”
$processInfo = New-Object System.Diagnostics.ProcessStartInfo
$processInfo.CreateNoWindow = $true
$processInfo.UseShellExecute = $false
$processInfo.useshellexecute = $false
$processInfo.RedirectStandardOutput = $true
$processInfo.RedirectStandardError = $true
$processInfo.filename = “$cwd\tasks\dacpacDeployTask\0.1.0\SqlPackage\sqlpackage.exe”
$processInfo.arguments = $argumentList
 
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $processInfo
[void] $process.Start()
$output = $process.StandardOutput.ReadToEnd()
$errorOutput = $process.StandardError.ReadToEnd()
$process.WaitForExit()
 
write-output ” stdout: $output”
 
if ($process.ExitCode -ne 0) {
write-error $errorOutput
exit $process.ExitCode
}

I then deleted the sample.js and sample.ps1 files from the dacpacDeployTask.  This is what my folder now looks like:

Modify build/release task manifest file

Next, I modified the task.json file so that  it has inputs for the working directory, connection string to the target database, path to the dacpac and whether to block the deployment when possible data loss can occur.  I also modified the file to call my powershell script.  This is the modified task.json file:

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
{
  “id”: “8710e1b0-7060-11e5-ad80-ef82c85bf7a6”,
  “name”: “dacpacDeployTask”,
  “friendlyName”: “DacPac Deploy Task”,
  “description”: “This task deploys the schema of a database using a dacpac and sqlpackage.exe”,
  “author”: “AbelSquidHead”,
  “helpMarkDown”: “Replace with markdown to show in help”,
  “category”: “Utility”,
  “visibility”: [
    “Build”,
    “Release”
  ],
  “demands”: [],
  “version”: {
    “Major”: “0”,
    “Minor”: “1”,
    “Patch”: “0”
  },
  “minimumAgentVersion”: “1.83.0”,
  “instanceNameFormat”: “dacpacDeployTask $(message)”,
  “inputs”: [
    {
      “name”: “cwd”,
      “type”: “filePath”,
      “label”: “Working Directory”,
      “defaultValue”: “$(Agent.HomeDirectory)”,
      “required”: false,
      “helpMarkDown”: “Current working directory when dacpacDeployTask is run.”
    },
    {
      “name”: “connectionString”,
      “type”: “string”,
      “label”: “Connection String”,
      “defaultValue”: “”,
      “required”: true,
      “helpMarkDown”: “Connection string to the target database”
    },
    {
      “name”: “dacpacPath”,
      “type”: “string”,
      “label”: “Dacpac Path”,
      “defaultValue”: “”,
      “required”: true,
      “helpMarkDown”: “Path to the Dacpac to deploy”
    },
    {
      “name”: “blockOnPossibleDataLoss”,
      “type”: “boolean”,
      “label”: “Block On Possible Data Loss”,
      “defaultValue”: “true”,
      “required”: true,
      “helpMarkDown”: “Block database deployment if possible data loss scenario”
    }
  ],
  “execution”: {
    “PowerShell”: {
      “target”: “$(currentDirectory)\\deployDacPac.ps1”,
      “argumentFormat”: “”,
      “workingDirectory”: “$(currentDirectory)”
    }
  }
}

 

Upload task to VSO

Using tfx-cli, I uploaded the build task to VSO with the following command:

tfx build tasks upload C:\Users\abel\MyNewTask\dacpacDeployTask

Use your new deploy task

After deploying my new build task, I can now use it.  From a build, I add a new build step

Since I added this build in the Utility category (see the manifest file), click on Utility and select the DacPac Deploy task and click Add

Now I add the connection string to the database I want to deploy to along with the path to dacpac file (which is an artifact of this build since I am building a visual studio solution with a database project).  I also uncheck the Block On Possible Data Loss field because in this example, I don’t care if data loss occurs. I then click save to save my new build.

 

And hooray for this.  I have now created a build which calls my custom build task, which deploys a database via a dacpac.