Updating web.config for Azure Releases via Team Service Releases
Problem
I want to be able to modify various parts of a web.config file of a azure app service when I release to certain live environments.
Team services already provisions features such as slots that allow you to have specific appconfig and connection strings specific to certain slots.
If you want to modify other slots Team Service also has a specific type of task that can tokenize and transform a web.config:

However I did not feel this was an elegant or succint solutions. Instead I wanted a task that would ensure that my stage/live and any other environments have the correct settings.
To do this I used Powershell or a Powershell task in TeamServices:

If you want to modify other slots Team Service also has a specific type of task that can tokenize and transform a web.config:
However I did not feel this was an elegant or succint solutions. Instead I wanted a task that would ensure that my stage/live and any other environments have the correct settings.
To do this I used Powershell or a Powershell task in TeamServices:
Solution
Instead I use a powershell which runs against a live release in Team Services. This powershell script will switch slots (effectively putting the code live) then will ensure the stage and live web.config have the correct settings.
After that I run an integration test that ensures my web.config transformations are correct.
The powershell is broken up into 4 tasks:
- Perform a swap of slots
- Download the web.config for an environment via FTP to a build client temp directory
- Perform web.config transform
- Re-upload the modified file via FTP
Below is the script:
#
# SwapSlots.ps1
#
param (
[string] $AzureWebsiteName,
[string] $From,
[string] $To
)
Switch-AzureWebsiteSlot -Name $AzureWebsiteName -Slot1 $From -Slot2 $To -Force -Verbose
function DownloadFile ($sourceuri,$targetpath,$username,$password){
# Create a FTPWebRequest object to handle the connection to the ftp server
$ftprequest = [System.Net.FtpWebRequest]::create($sourceuri)
# set the request's network credentials for an authenticated connection
$ftprequest.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
$ftprequest.UseBinary = $true
$ftprequest.KeepAlive = $false
# send the ftp request to the server
$ftpresponse = $ftprequest.GetResponse()
# get a download stream from the server response
$responsestream = $ftpresponse.GetResponseStream()
# create the target file on the local system and the download buffer
try
{
$targetfile = New-Object IO.FileStream ($targetpath,[IO.FileMode]::Create)
[byte[]]$readbuffer = New-Object byte[] 1024
# loop through the download stream and send the data to the target file
do{
$readlength = $responsestream.Read($readbuffer,0,1024)
$targetfile.Write($readbuffer,0,$readlength)
}
while ($readlength -ne 0)
$targetfile.close()
}
catch
{
$_|fl * -Force
}
}
function UploadFile ($sourceuri,$targetpath,$username,$password){
# Create a FTPWebRequest object to handle the connection to the ftp server
$ftprequest = [System.Net.FtpWebRequest]::create($sourceuri)
# set the request's network credentials for an authenticated connection
$ftprequest.Credentials = New-Object System.Net.NetworkCredential($username,$password)
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::UploadFile
$ftprequest.UseBinary = $true
$ftprequest.KeepAlive = $false
# Read the File for Upload
$FileContent = gc -en byte $targetpath
$ftprequest.ContentLength = $FileContent.Length
# Get Stream Request by bytes
$Run = $ftprequest.GetRequestStream()
$Run.Write($FileContent, 0, $FileContent.Length)
# Cleanup
$Run.Close()
$Run.Dispose()
}
function ModifyWebConfigEndPoint ($file, $oldValue, $newValue) {
#Mod File for production
$webConfig = $file
$doc = (Get-Content $webConfig) -as [Xml]
$root = $doc.get_DocumentElement();
$newSet = $root.'system.serviceModel'.client.endpoint.address.Replace($oldValue,$newValue);
$root.'system.serviceModel'.client.endpoint.address = $newSet
$doc.Save($webConfig)
}
# Global Settings
$Password = "<FTPPassword>"
$RemoteFile = "ftp://ftpURL/web.config"
# Download and modify Config for production (master)
$LocalFile = $env:Temp + "\web.config"
Write-Host "TempDirectoryIs"$LocalFile
$Username = "ftpusername"
#Remove-Item $LocalFile
DownloadFile $RemoteFile $LocalFile $Username $Password
ModifyWebConfigEndPoint $LocalFile 'val1' 'val2'
UploadFile $RemoteFile $LocalFile $Username $Password
Write-Host "Modified web config for "
# Download and modify Config for production (staging)
$LocalFile = $env:Temp + "\web.config"
Write-Host "TempDirectoryIs"$LocalFile
$Username = "ftpusername"
#Remove-Item $LocalFile
DownloadFile $RemoteFile $LocalFile $Username $Password
ModifyWebConfigEndPoint $LocalFile 'val1' 'val2'
UploadFile $RemoteFile $LocalFile $Username $Password
Write-Host "Modified web config for (staging)"
Its worth noting that the function that does the changing of the web.config is specific to in my case service endpoints but you can create specific or generic functions to update any part of the web.config file as it is just xml.
Its worth noting that the function that does the changing of the web.config is specific to in my case service endpoints but you can create specific or generic functions to update any part of the web.config file as it is just xml.
function ModifyWebConfigEndPoint ($file, $oldValue, $newValue) {
#Mod File for production
$webConfig = $file
$doc = (Get-Content $webConfig) -as [Xml]
$root = $doc.get_DocumentElement();
$newSet = $root.'system.serviceModel'.client.endpoint.address.Replace($oldValue,$newValue);
$root.'system.serviceModel'.client.endpoint.address = $newSet
$doc.Save($webConfig)
}