Using web.config transforms and Release Manager – TFS 2017/Team Services edition

This is a follow-up to an old post on Using Web.Config transforms and Release Manager that I did some time ago. That post was done on TFS 2015 and using the now “old” architecture of Release Management.

This post will follow the same idea of deploying an application to Azure (App Service), but this time using Visual Studio Team Services and its Build/Release system. This can be easily translated to TFS 2017 RTM, the latest TFS version at the time of this writing.

I’ll be using an old MVC app (TailspinToys) as an example and will be focusing on the web.config changes only. I’ll check if I can post the source code for this later, but this is an old app that I used to expedite the work on writing this, so you might want to use a newer framework.

First, from a developers perspective, I want to make sure my app runs on my laptop and uses my LocalDB as database server. This way, I can have a full test bed for my work before I deploy it to Azure. My web.config, by default, points to my LocalDB:

Web.Config pointing to LocalDB

The debug transform doesn’t need any changes:

Web.Debug.Config with no transforms

Running the application locally, you should get:

TailspinToys running locally on developer's machine

TailspinToys running locally on developer’s machine

If you don’t have the infrastructure setup on Azure yet, this is a good time to do it. I won’t go into too much detail on how to create a web app and Azure SQL database, but there is good documentation on the web on how to do it.

In this example, I’ll create two web app/sql pairs: one for QA, another for Prod. In prod, we’ll have two slots, just for fun.

Azure environments

Azure environments

We can now create a web.config transform to “tokenize” the connection string. This token will then be replaced at deployment time.

In the example below, we created in Visual Studio a configuration called “Azure” and created the corresponding transform (by right-clicking on the web.config and selecting “add web config transform”):

Web config for Azure with tokenized connection string

Web config for Azure with tokenized connection string

Notice that I used “__” as token delimiter. It’s not a requirement anymore, but I kept the same syntax used in the previous Release Management versions.

Code is committed and pushed to VSTS. Now we have to setup a build for the QA environment:

Build variables specifying "Azure" configuration to build

Build variables specifying “Azure” configuration to build


Repository setting to pull code from a specific branch

Repository setting to pull code from a specific branch


Notice that I also used some MSBuild arguments to make sure we get the properly packaged output. In this case I used /p:DeployOnBuild=True

In the copy artifacts steps, I specify all the files I need for deployment:

Artifacts to be published

Artifacts to be published

Once the build is successfully executed, we can see the artifacts published and ready to be used:

Build artifacts created after successful build

Build artifacts created after successful build

Let’s download the folder “Package” with all the web deploy files and examine the SetParameters file:

Parameters.xml file

Parameters.xml file

Notice that our tokenized connection string is created for us here. If you’d like to add more parameters, even if not in web.config take a look at this article.

Next step is for us to create a release definition that deploys the web app and database to the QA and Production environments we created earlier. The definition will replace the tokenized config set in the parameters with the correct connection string to the corresponding database.

The steps to deploy to QA should be:





A couple of points here:

  • I used one of the replace token tasks available on the VSTS marketplace. You can use any other, just make sure you know how to configure them to identify the tokens
  • Notice that I specify the token delimiters as “__”. In this task you can use anything. I kept the “old” setup to make it simple.
  • I specify the parameters file directly (full path) but you don’t have to. Same for the package zip

The variables are set at QA environment level. Notice the variable with the same name as the token, without the “__”:


The production environment will be very similar, but this time we want to accomplish a couple more things:

  • Deploy only if QA is successful
  • Deploy to staging slot first then wait for manual confirmation
  • Swap staging and prod slots for final deployment

The production deployment steps will then look something like this:

release-prod-manual-interv release-prod-swap-slot


  • Variables are the same as in QA, but with production values (server names, etc)
  • Manual intervention step is created. Here you should consider whether you really need manual intervention or even better: automate smoke tests to run against the staging environment and proceed with slot swap only if those tests pass
  • There’s no swap for the prod DB, which is deployed regardless. It may be better to consider creating a staging DB to be used or use a refactoring pattern for DB changes that allows for deployments that don’t break the existing application running in production
  • The swap slot step has an inline PowerShell script. You can choose to check that in as code and have a task call that script instead.

Running the release we get the app deployed and running in the QA environment:


If we go to the Kudu console for the web app in QA, we see that our web.config transformation happened without issues:


Now, in production is the same pattern. The only difference is that the app is deployed first to the staging slot.

Running in staging slot:


But not yet in production slot:


Once the deployment happens to staging, approval is needed for the manual intervention:


Then the slot is switched and the app is now running in production:


We can also verify that the web.config correctly points to the production database in Kudu:



There you have it. Now on to another post soon.

Comments (4)

  1. Cash Foley says:

    Hey Marcelo, I think I worked with you a few years back at MedAssets doing a Rally migration.

    What are your thoughts on using Transforms with Tokens vs. App Settings in an ARM Template?


    1. Hi Cash! Good to hear from you!
      I believe both approaches work fine. The only thing about using the ARM is that it will only work for PaaS (Azure App Service).
      You can also have the ARM template store the connection strings as tokens and replace them during Release, in a similar way to what I did in this article.
      One example I’m working on this week involves deploying an Azure VM Scale Set, which requires an admin password. If you create that using VS 2017, it will not include it in the parameters.json so it will ask you if you deploy from VS 2017.
      However, I needed to deploy from VSTS and needed to inject the admin password there, so I updated the parameters.json to be:

      "$schema": "",
      "contentVersion": "",
      "parameters": {
      "vmssName": {
      "value": "ARMDemo"
      "instanceCount": {
      "value": 2
      "adminUsername": {
      "value": "marcelos"
      "adminPassword": {
      "value": "__AdminPassword__"

      Now, I can do the replace token task before I deploy the ARM template to Azure.

      Hope that helps

  2. M says:

    What would you suggest I do if I don’t want to store .config files in source control? Where can I store a template .config file in Team Services that would be built together with my “remote repo” code base and perhaps further transformed as described in this article?

    1. I’m not aware of a good way to store .config files elsewhere in VSTS but the source control.
      You might be able to “inject” a web.config at release time by using a Inline PowerShell release task which creates the web.config file with content specified in the script, or pull the file from another source using PowerShell.
      Once it’s either created or pulled in from another source, you can transform it by using the replace token steps described here.

      Any particular reason why you would not want to store web.config in source control?

Skip to main content