For my first attempt at automating an Azure deployment process, I had some MsBuild wrap the calls to the MsDeploy.exe. This lets me have the build server (eg. Team City) run the MsBuild script and call a simple “DeployX” target for the relevant Dev/Test/Production deployment. The MsBuild script is responsible for supplying MsDeploy with the correct settings. Problem is, the MsDeploy call passes Azure credentials (for the web app, not a user) as clear-text command-line parameters.
If we have those publish settings stored in the MsBuild script, we have to copy those parameter values from the Azure publish profile into a script (the build script) that lives in source control. Not so secure, and it involves some tedious copy-and-pasting.
While your application should know “how to deploy itself”, a big problem with locking your publishing credentials into your deployment packages is what happens if those settings should change. If you have a continuous delivery pipeline (you should), and if code takes a few days to travel through the pipeline from check-in to deployment, any code in the pipeline with outdated publish settings is hard to deploy (you may need to just grab the deployment packages and modify them). It would be better if the credentials lived outside of the build pipeline. You could have your build server pass these in via parameters, but that only works if you’re deploying one or two apps (I’m currently working on a project that deploys a set of eight apps together, whether that’s the correct long-term strategy is another question).
So a first step would be, rather than storing the Azure credentials in a script, just include the publish profile files and read them at deployment time. Then you can move them out of your code (source control and deployment packages) and store them on disk or in a different repository – anywhere that every developer or build server agent machine can access – and read the appropriate profile at deployment time.
You could do this with a custom application, script or MsBuild target that reads and parses the publish profile Xml file. Or just use some Powershell, it has some built-in XML hacking cpabilities. Instead of knowing the publish settings for the MsDeploy call, our MsBuild script can run a Powershell script instead.
Note that this isn’t the only way to deploy with Powershell. In fact, there’s probably better ways to use Powershell to deploy directly as a trusted Azure user, rather than all the hassle of downloading publish profiles. All we’re doing is wrapping the MsDeploy calls that actually ship our code.
A Powershell Script That Deploys Webapps
So create a Powershell script like this (yes the funny quotes to escape characters are weird but necesssary):
#Usage: powershell .\deployWebApp.ps1 [pathToWebAppPackageFolder] [webAppPackageZipFile] [pathToPublishSettingsFile] [configuration] param([string]$packageFolderPath,[string]$packageZip,[string]$publishProfilePath,[string]$configuration) #MsDeploy.exe location (if not in path) [string]$msDeployExe="C:\Program Files (x86)\IIS\Microsoft Web Deploy V3\msdeploy.exe" #Get publish settings from file [xml]$xml=Get-Content($publishProfilePath) [string]$azureSite=$xml.publishData.publishProfile.msDeploySite.get(0) [string]$azureUrl=$xml.publishData.publishProfile.publishUrl.get(0) [string]$azureUsername=$xml.publishData.publishProfile.userName.get(0) [string]$azurePassword=$xml.publishData.publishProfile.userPWD.get(0) #MsDeploy "white label" webapp package (additional configs to follow) $msDeployPackageCommand = "`"$msDeployExe`" -source:package=`"$packageFolderPath\$packageZip`" -dest:auto,ComputerName=`"https://$azureUrl/msdeploy.axd?site=$azureSite`",UserName=`"$azureUsername`",Password=`"$azurePassword`",IncludeAcls=`"False`",AuthType=`"Basic`" -verb:sync -setParam:name=`"IIS Web Application Name`",value=`"$azureSite`" -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension -retryAttempts=2 -verbose -userAgent=`"VS12.0:PublishDialog:WTE12.3.51016.0`""; Write-Output ("MsDeploy Command: " + $msDeployPackageCommand); cmd.exe /c "$msDeployPackageCommand"; #MsDeploy config file "web.configuration.config" eg. "web.TEST.config" $msDeployConfigCommand = "`"$msDeployExe`" -source:contentPath=`"$packageFolderPath\Configs\Web.$configuration.config`" -dest:contentPath=`"$azureSite\web.config`",ComputerName=`"https://$azureUrl/msdeploy.axd?site=$azureSite`",UserName=`"$azureUsername`",Password=`"$azurePassword`",IncludeAcls=`"False`",AuthType=`"Basic`" -verb:sync -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension -retryAttempts=2 -verbose -userAgent=`"VS12.0:PublishDialog:WTE12.3.51016.0`""; Write-Output ("MsDeploy Command: " + $msDeployConfigCommand); cmd.exe /c "$msDeployConfigCommand";
For the configs, I run web.config transforms on all my configs during the CI/packaging build stages, and ship all the possible configs with the deployment package. While the above example shows shipping full web packages and individual files, a better approach is to use parameter files for configuration, adding an argument like:
OK, try running the script with:
powershell powershellScripts\deployWebApp.ps1 BuildArtifacts\DeployableWebApp DeployableWebApp.zip C:\Development\AzurePublishProfiles\myappname.azurewebsites.net.PublishSettings Debug
This works, so we go and add this to our MsBuild script with this target:
<Target Name="PsDeployToDev"> <Exec Command="$(PowerShellExe) powershellScripts\deployWebApp.ps1 $(PackageDeploymentPath)$(WebApp) $(WebApp).zip $(AzurePublishProfilesPath)\myappname.azurewebsites.net.PublishSettings Debug"/> </Target>
Run it with:
msbuild WebDeploy.msbuild /t:PsDeployToDev
…and it fails, because of an execution policy issue. See http://go.microsoft.com/fwlink/?LinkId=135170
Yes, within this command prompt, I have the rights to run a powershell script, but not to run a script that runs my powershell script. Go Microsoft! Let’s add a few parameters to our MsBuild Powershell call to handle the execution policy:
<Target Name="PsDeployToDev"> <Exec Command="$(PowerShellExe) -NonInteractive -ExecutionPolicy Unrestricted -File powershellScripts\deployWebApp.ps1 $(PackageDeploymentPath)$(WebApp) $(WebApp).zip $(AzurePublishProfilesPath)\myappname.azurewebsites.net.PublishSettings Debug"/> </Target>
Success! You can test this by editing, re-building and re-deploying your web app.
I have this running as a Team City build step (MsBuild runner calls the appropriate target in my MsBuild script).
This is a starting point – a basic “ship a white-label package and a config file”, you’ll need to adapt it for deploying your applications.
Some next steps:
- You’ll probably want to turn off verbose logging and outputting of the actual deployment command (with the publishing credentials arguments) once you’re happy things are working
- You might need the “msdeploy.exe -useCheckSum” argument to prevent files getting redeployed after your build server does a clean checkout / rebuild