I saw a question on Twitter about using the new Release Management functionality in VSTS to deploy virtual applications to Azure websites. After some research I found the Azure Web App Deployment task uses the Publish-AzureWebSiteProject PowerShell cmdlet which does not support deployment of virtual applications. This is also confirmed by this issue on the vso-agent-tasks GitHub repo where phatcher provides an alternative PowerShell script to deploy a virtual app to Azure.

So I decided this would be a good opporturnity to learn how to write a custom release task using the new extensibility features in VSTS. In the end I have to say this was a lot easier than I expected it to be.

Generic MSDeploy.exe Task Overview

Before I set out on building my own task I did review the 0pen source tasks from Microsoft and others but did not find one to meet my needs. There were several that used MSDeploy but they were limited to a specific deployment scenario. For example, deploying to an Azure site or deploying to a named machine. I’m sure these are helpful to most users but they fall down as soon as you want to use the more advanced features of MSDeploy.

So this task may not be as easy to use as some of the others but I’m hoping it is generic enough to deploy an MSDeploy package any way you would like. I think it will be especially helpful to those who already have experience with MSDeploy from the commandline. The basic steps within the task are:

Find the package file based on a pattern

Find the MSDeploy executable

Setup the command arguments to deploy a package to a destination

Run the MSDeploy command

To enable this it defines 8 task inputs:

Notice the “Destination Computer” input is actually an MSDeploy web service endpoint from Azure. This is the type of complexity that may turn away some users but to the advanced MSDeploy user it may be preferred. At the very least this task should help fill in some gaps where the built in tasks fall short.

I also include an “Additional Arguments” input so the user can add whatever other MSDeploy arguments they want.

Deploying a Virtual App

To deploy the virtual app all you have to do is set the IIS app name either via the MSBuild process (as decribed in this post) OR you can use the setParam flag it in the “Additional Arguments” field:

Here is a snippet from the build showing a successful virtual app deployment:

2016-03-21T22:02:26.0643128Z Deploying package to https://virtualappdeploy.scm.azurewebsites.net:443/msdeploy.axd?site=virtualappdeploy 2016-03-21T22:02:26.0701471Z &quot;C:Program FilesIISMicrosoft Web Deploy V3msdeploy.exe&quot; -verb:sync -source:package=&#039;C:a1sRootSiteVApp1objreleasePackageVApp1.zip&#039; -dest:auto,computerName=&#039;https://virtualappdeploy.scm.azurewebsites.net:443/msdeploy.axd?site=virtualappdeploy&#039;,userName=&#039;$virtualAppDeploy&#039;,password=&#039;********&#039;,authType=&#039;basic&#039;,includeAcls=&#039;False&#039; -allowUntrusted -verbose -setParam:&#039;IIS Web Application Name&#039;=virtualappdeployVApp1 2016-03-21T22:02:31.6171338Z Info: Using ID &#039;c4953408-c32f-4fe6-a629-1aeef20dd2c1&#039; for connections to the remote server. 2016-03-21T22:02:31.6177480Z 2016-03-21T22:02:31.6184161Z Verbose: Pre-authenticating to remote agent URL &#039;https://virtualappdeploy.scm.azurewebsites.net:443/msdeploy.axd?site=virtualappdeploy&#039; as &#039;$virtualAppDeploy&#039;. 2016-03-21T22:02:31.6184161Z 2016-03-21T22:02:31.6254324Z Verbose: Source createApp (C:a1sRootSiteVApp1objreleasePackagePackageTmp) does not match destination (virtualappdeployVApp1) differing in attributes (isDest[&#039;False&#039;,&#039;True&#039;]). Update pending. 2016-03-21T22:02:31.6264321Z 2016-03-21T22:02:31.6274337Z Info: Adding virtual path (virtualappdeployVApp1)

Creating a custom Release task

The open source vso-agent-tasks from Microsoft are a great place to learn how the tasks work and to “borrow” code. For example, I used file search pattern functionality from the AzureWebPowerShellDeployment task to support the same file pattern matching logic as the built-in tasks.

I also found calling MSDeploy from PowerShell is REALLY hard! Most of my difficulty and time in building this task was related to this issue. In general, calling batch commands from PowerShell can be challenging especially when the command has an odd/extensive arguments with spaces/quotes. I think most batch commands aren’t that bad. MSDeploy.exe, however, seems to be the exception with all the knobs and switches you can adjust via arguments. For example, the following MSDeploy.exe call is what we need to mimic to deploy a package:

msdeploy.exe -verb:sync -source:package=&#039;$packageFile&#039; -dest:$DestinationProvider,computerName=&#039;$DestinationComputer&#039;,userName=&#039;$UserName&#039;,password=&#039;$Password&#039;,authType=&#039;$AuthType&#039;,includeAcls=&#039;False&#039; -allowUntrusted

I tried several ways to setup the arguments and call MSDeploy.exe from PowerShell but they all failed until I found the IISWebAppDeployment task had an example of calling MSDeploy.exe using the cmd command.

The moral of this story is use the open source tasks from Microsoft as a learning tool!

Couple of Custom Task Gotchas

I did struggle for a bit with an error where the task was failing looking for a PowerShell parameter:

The required ConnectedServiceName parameter was not found by the AzurePowerShellRunner.

I couldn’t find any references to this in my code so I was at a loss as to the source of the problem. Eventually I found I used the wrong execution type. I had AzurePowerShell but should have used just PowerShell .

&quot;execution&quot;: { &quot;PowerShell&quot;: { &quot;target&quot;: &quot;$(currentDirectory)\MSDeployPackageSync.ps1&quot;, &quot;argumentFormat&quot;: &quot;&quot;, &quot;workingDirectory&quot;: &quot;$(currentDirectory)&quot; }

The second gotcha is related to importing PowerShell task modules on my local machine. The initial error was it couldn’t find the module. Donovan Brown blogged about how to enable the modules locally by copying them to your local module folder and installing some DLLs to the into the GAC. I couldn’t get it to work until I found that you can’t place the modules in a subfolder unless the subfolder was named the same as the module. I had placed all the VSO agent modules into a “vsoAgents” folder under the PowerShell “modules” folder. Once I moved the module folders to the “modules” folder everything worked like a champ.

Deploying a custom task

You can use Jeff Bramwell’s excellent blog post on using the tfx command to extend a VSTS team project. After logging in its a simple upload call –

tfx build tasks upload -task.path ./MSDeployPackageSync

The Source

The task source is shared on GitHub – https://github.com/rschiefer/vso-agent-tasks/blob/master/Tasks/MSDeployPackageSync

Feel free to use it, share it and/or fork it.

Please let me know if you plan on using this task and/or open issues on the GitHub repo to get some help.