VSTS Release: Custom Deploy Task to Deploy Virtual Applications to Azure

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.

image

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:

image

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:

image

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 "C:Program FilesIISMicrosoft Web Deploy V3msdeploy.exe" -verb:sync -source:package='C:a1sRootSiteVApp1objreleasePackageVApp1.zip' -dest:auto,computerName='https://virtualappdeploy.scm.azurewebsites.net:443/msdeploy.axd?site=virtualappdeploy',userName='$virtualAppDeploy',password='********',authType='basic',includeAcls='False' -allowUntrusted -verbose -setParam:'IIS Web Application Name'=virtualappdeployVApp1
2016-03-21T22:02:31.6171338Z Info: Using ID 'c4953408-c32f-4fe6-a629-1aeef20dd2c1' 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 'https://virtualappdeploy.scm.azurewebsites.net:443/msdeploy.axd?site=virtualappdeploy' as '$virtualAppDeploy'.
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['False','True']). 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='$packageFile' -dest:$DestinationProvider,computerName='$DestinationComputer',userName='$UserName',password='$Password',authType='$AuthType',includeAcls='False' -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.

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

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.

18 thoughts on “VSTS Release: Custom Deploy Task to Deploy Virtual Applications to Azure”

  1. hi i am trying to deploy virtual app to azure using msdeploy 3.6, i am using following command: >msdeploy.exe -source:package=”E:PackageTmpAnuWebApp1.zip” -dest:auto,ComputerName=”https://anubhavapp1.scm.azurewebsites.net/msdeploy.axd?site=anubhavapp1″,UserName=”$anubhavapp1″,Password=”####”,AuthType=’Basic’ -verb:sync -enableRule:DoNotDeleteRule -enableRule:AppOffline -setParam:name=”IIS Web Application Name”,value=”anubhavapp1/myVApp1″ .
    i am getting an error: Error: Source does not support parameter called ‘IIS Web Application Name’. Must be one of ().
    Error count: 1.

    1. If you created the source package with WebDeploy there should also be a sample parameters file in the same folder with the package. Open that to check the parameter name or existence.

  2. Pingback: MSDeployAllTheThings VSTS/TFS Extension is Public! – dotnet Catch

  3. Hi Robb – I’m having an issue with your task. When it runs in VSTS I get the following error:
    > ##[error]’C:Program’ is not recognized as an internal or external command,

    Truncated output of Write-Host $fullCommand is shown here:

    > “C:Program FilesIISMicrosoft Web Deploy V3msdeploy.exe” -verb:sync

    I have tried running the pertinent parts of your script locally pointing to an msdeploy.exe path which has spaces in it and everything works fine. Any ideas?

  4. Hi Robb – I had uploaded the task manually into our VSTS instance (I hadn’t yet noticed that it was available via the marketplace). Anyway, I finally managed to work out what was wrong. My additional arguments were specified as follows:

    -setParam:name=”IIS Web Application Name”,value=”Default Web Site/Test” -setParam:name=”Environment”,value=”Test”

    As soon as I changed this from double to single quotes, your task worked fine

    -setParam:name=’IIS Web Application Name’,value=’Default Web Site/Test’ -setParam:name=’Environment’,value=’Test’

    I had basically resorted to making changes to your script, re-uploading the task via tfx then running the release again and seeing what broke. After several attempts, I managed to isolate the problem to the $AdditionalArguments variable. Thanks for creating the task – it seems like the type of thing VSTS should have out of the box.

  5. Hi Robb – I have left a review in the marketplace. Thanks again for taking the time to develop the task.

  6. Thank you so much for building this! It seems surprising that this wouldn’t be supported out-of-the-box.

    So I’m a noob to Azure, so I don’t know much about the authentication/authorization necessary to call /msdeploy.axd. The standard “Azure Web Site Deployment” task seems to handle it automagically. Any tips for configuring the task to authenticate the same way as the “Azure Web Site Deployment” task? It didn’t require me to set up any user accounts on my target server. Perhaps it uses the service endpoint/Azure subscription?

    This is all very new to me, so I’m still putting together the “big picture.” Thanks for any advice!

    1. Glad it was helpful! The “Azure Web Site Deployment” uses the PowerShell MSDeploy cmdlets which supports automatic authentication. My task uses raw WebDeploy functionality which requires a login. I believe they are working on a more generic MSDeploy task.

  7. Thanks for the AzurePowerShell vs PowerShell gotcha.

    In another extension of mine I used AzurePowerShell and had to add an input named ConnectedServiceName which is binded to azure subscription selector.

    In the extension I’m developing now I don’t need this so I changed it to execute PowerShell.

  8. I was having some problems due to special chars in the password and the single quotes this package uses.
    So I just created a new task in the pipeline, a “Powershell” task.
    Step by step:

    1. Copy the script from @CHIEF7 and modify it as you want (or any other powershell script)
    2. Create a repository called “BuildTools” (or something else)
    3. Create a build pipeline to publish the script to artifactory
    3.1. Inside that pipeline create a task “Publish Build Artifact”
    3.2. Modify “Path to publish” and “Artifact name”
    3.3. Save and queue the build
    4. In the release pipeline create a new artifact from the build you created for “BuildTools” repo, this artifact will have the name from the step 3.2.
    5. In the release pipeline use a new task “Powershell”
    5.1. Change script path to use the artifact you selected in the step 4.
    5.2. Use the arguments you need for the script to run

    And you will have a custom task that you can change by pushing code to your script inside your repository “BuildTools”

Leave a Reply to Paul Schofield Cancel reply