Automating ASP.NET Site Deployment with Web Deploy: A survivor’s tale

Share Button

I recently finished a spike into MS Deploy. I wanted to find an easy way to deploy an ASP.NET website to a test, QA/demo, and Staging/Production environments and in the spirit of Continuous Delivery I wanted the whole process to be automated. It wasn’t long until I found a wonderful tease of an article titled “Automating Deployment with Microsoft Web Deploy” by Scott Guthrie. I call it a tease because as much information as it contains, it still glosses over a lot of details I think are essential to getting a Web Deploy implementation production-ready. Scott was a member of the team who developed the Web Deploy technology for release with Visual Studio 2010 and IIS 7 in Windows 7 & Windows Server 2008.

I quickly discovered that configuring both your ASP.NET project and the destination server is no picnic. I hope that some of research I did to get my own Web Deploy implementation working will be of help to others who decide to go down a similar path.

Default MSBUILD target used by MSDeploy, “Any CPU” vs “AnyCPU”

* EDIT 24-7-12, Kudo’s to Chris Surfleet for spotting this subtle bug in my deployment.

Part of my CI build is to build a web deployment package that I can re-use for all my deployments. When I first attempted to do this from the command line I received the following error.

c:\windows\Microsoft.NET\Framework\v4.0.30319\Microsoft.Common.targets(483,9): error : The OutputPath property is not set for project 'My Project.csproj'.  Please check to make sure that you have specified a valid combination of Configuration and Platform for this project.  Configuration='Debug'  Platform='Any CPU'.  You may be seeing this message because you are trying to build a project without a solution file, and have specified a non-default Configuration or Platform that doesn't exist for this project. [C:\CCNetWD\My Project\My Project.csproj]

I’m no MSBuild expert, but my project file appeared to have the appropriate PropertyGroup defined for my Debug and AnyCpu build configuration, an OutputPath was defined. After a tip from Chris Surfleet I discovered that when using MSBUILD to create my deployment package it was defaulting to the platform configuration of “Any CPU”. However, when you create a new project with Visual Studio 2010 it defines the any cpu platform configuration with the name “AnyCPU”, note that there is no space. After a small adjustment to the MSBUILD call used to create the deployment package I was back in business without having to hack the platform configuration in all my project files.

MSBUILD /T:Package /p:Platform=AnyCPU;Configuration=Debug;PackageLocation=my_web_package.zip

Using IIS Manager Users to Sandbox Deployment Permissions

On internal infrastructure it’s not (usually) critical to lock down destination boxes with deployed solutions because they’re only usually accessible by individuals directly responsible for developing, testing, or demonstrating the project. In these cases you may be able to get away with using Integrated Security (the default MS Deploy security package) on your corporate domain network. If the identity doing the deploying has administrator priveleges on the remote machine then Web Deploy will work marvelously without error. However, if you are not deploying to a machine on your corporate network and your destination server is on a remote machine on a network you may not have control of (i.e. a colocated machine accessible over the Internet) then you’ll likely have requirements to lock down access to the box. In this case you have the option of either using a less priveleged local machine account or an IIS Manager User (a special user created and administered by IIS for the sole purpose of Deployment).

I chose to setup an IIS Manager User. This is a straightforward task and is documented extensively on the MSDN page “Configurating the Web Deployment Handler“. However, as I learned to expect during this whole process there are some subtle details to take into account to get Web Deploy working nicely with IIS Manager Users.

  1. Use basic authentication when calling MS Deploy at the Command line. Windows Authentication security packages (NTLM or Kerberos) won’t cut it because IIS Manager Users are not Windows accounts. The only way you will be able to use IIS Manager users is to explicitly specify basic authentication at the command line like in the following example.
    msdeploy.exe -source:package="_My_Project_webdeploy.zip" -dest:auto,computerName="https://MyDestinationServer:8172/MSDeploy.axd?site=My Project",userName="autobuild",password="shhhh",authtype="basic",includeAcls="False" -verb:sync -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension -setParamFile:"_My_Project_webdeploy.SetParameters.xml" -allowUntrusted -verbose -debug

    Keep in mind that when you using basic authentication you are sending your username and password in clear text to the destination server. This is one reason why the Web Deployment Handler is setup by default to only be accessible via HTTPS.

  2. Create the web site and its file system root directory before calling web deploy. Before you run your MSDeploy command in a less priveleged mode it’s necessary to do some initial setup tasks. Create a directory on the destination server where your web site will be deployed to. Keep in mind that the identity the Web Deployment Handler uses (Local Service by default) must have write access to this folder in order to deploy the package.Create an empty web site in IIS and point it to the root directory you just created.
  3. Specify the IIS site name in your destination server name query string. This is another subtle detail I can’t fully explain. I can’t find any documentation on this query string parameter so I’m not certain why it’s required. When this parameter is omitted I get a 401 error when attempting to deploy with the IIS Manager User. My guess is that when the site is not specified, the web deploy path will attempt to interrogate IIS at the server level (where your IIS Management User likely will not have access to via IIS Feature Delegation) and fail. See the bold section in the following MSDeploy system call for an example.
    msdeploy.exe -source:package="_My_Project_webdeploy.zip" -dest:auto,computerName="https://MyDestinationServer:8172/MSDeploy.axd?site=My Project",userName="autobuild",password="shhhh",authtype="basic",includeAcls="False" -verb:sync -disableLink:AppPoolExtension -disableLink:ContentExtension -disableLink:CertificateExtension -setParamFile:"_My_Project_webdeploy.SetParameters.xml" -allowUntrusted -verbose -debug

    This and a lot of other sage advice can be found in this StackOverflow answer.

  4. Ignore setting ACL’s on the destination machine during deployment. This should only be applicable if you’re deploying to a destination machine without Integrated Security and with a sandboxed IIS user. MSDeploy is clever and will attempt to mirror the NTFS ACL’s you have defined on your source machine. In certain cases the identities in your source machine ACL’s are not available on your destination machine nor will your IIS Manager Users have the capability to set such ACL’s if they were available. The following is a snippet of my web project file. Persisting ACL’s can be disabled by adding the IncludeSetAclProviderOnDestination node and setting it to False.
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
        <DebugSymbols>true</DebugSymbols>
        <DebugType>full</DebugType>
        <Optimize>false</Optimize>
        <OutputPath>bin\</OutputPath>
        <DefineConstants>DEBUG;TRACE</DefineConstants>
        <ErrorReport>prompt</ErrorReport>
        <WarningLevel>4</WarningLevel>
        <DeployIisAppPath>Default Web Site/</DeployIisAppPath>
        <IncludeIisSettings>false</IncludeIisSettings>
        <IncludeAppPool>false</IncludeAppPool>
        <PublishDatabases>false</PublishDatabases>
        <IncludeSetAclProviderOnDestination>False</IncludeSetAclProviderOnDestination>
      </PropertyGroup>
    

    You can also define this as a parameter for MSBuild at the command line if you’re so inclined.

    msbuild.exe "My Project.csproj" /p:IncludeSetAclProviderOnDestination=False

    The following article titled “Prevent Visual Studio 2010 and Web Deploy from Changing ACLs” by Rick Barber tipped me off to this solution.

Iterative Database Deployment with Web Deploy?

* EDIT 25-7-12: My comments about the dbFullSql provider were not exactly true. It can support iterative updates after a fashion.

One feature I find lacking in MSDeploy is a simple way to perform iterative updates to a database. Most websites have a backend relational component so I’m perplexed as to why this hasn’t been addressed in MSDeploy. MSDeploy does have a SQL provider (dbFullSql) and it _can_ be used to execute an arbitrary SQL script on deployment. When I first wrote this post I thought the dbFullSql provider was not capable of this, but I was corrected in a StackOverflow question I created about Automating database deployment with MSDeploy. An alternative to using the dbFullSql provider in this manner is to roll your own iterative database updates and launch it via the MSDeploy runCommand provider or bypass MSDeploy entirely and work directly with SQL Server.

* UPDATE 25-7-12: Sayed Ibrahim, an MS employee who works on MSDeploy hints future support for code first DB migrations in Web Deploy.

Again, thanks to Christ Surfleet for passing along this post by Sayed which mentions support for code first migrations for Azure deployments.

I hope to spike research on a solution for this problem in the near future. I’ll document my findings.

Conclusion

Microsoft Deploy is a fantastic system for deploying pretty much anything to Windows environments. It’s unfortunate it’s not documented more thoroughly. However, I still believe it is the best solution available at this time.

Clearly some of the solutions I discuss in this post have not been researched exhaustively. To be honest, I was just happy to get this whole process working in one form of another in the time allotted to the task. I plan on iteratively refining my deployment pipeline as time allows.

For others in the same situation as my own, I hope that some of the advice in this post helped you with your work. In addition to the links I’ve already mentioned I would like to close with some other online resources I found invaluable while working on my Web Deploy implementation.

Share Button
  • http://shcherbak.me/ Alexey Shcherbak

    Hi there. If you have database in form of dbprojsqlproj projects – you can automate increment deployment:
    Both project types could be called to “publish” data to DB, but in case of dbproj you could specify connection string data

    • http://randonom.com/ Sean Glover

       Thanks Alexey!  I have yet to play with SQL Server Data Tools and SQL Projects.  I’ve done a little reading and I see how it can be used to push incremental updates.  When you do a publish with MSBuild how exactly does it connect to the destination SQL Server?  I’m assuming it doesn’t use MSDeploy endpoints and connects directly with SQL Server?

      I’ll be downloading SSDT soon and attempt to run through the following walkthrough: http://sqlproj.com/index.php/2012/03/headless-msbuild-support-for-ssdt-sqlproj-projects/

      • http://shcherbak.me/ Alexey Shcherbak

        You are right, SQL publishing going through direct connection, not using msdeploy.
        I also read that article, when I built-up our CI. And there is a very nice comment with link at the end of it (…sadly it wasn’t there when I did setup, it could save some huge time). 

        Check this http://huddledmasses.org/adventures-getting-msbuild-tfs-and-sql-server-data-tools-to-work-together/ for some advanced msbuild ninja tricks – connection string generation with ParameterizeTransformXml. It’s looks a bit messy (I think it also could be simplified there), but sould give you some directions if you are stuck.

  • Chris Surfleet

    Hi Sean – good post

    The issue you were having with the configurations is because for some (insane) reason, your projects will default to AnyCPU and the build will look for “Any CPU”, usually you can modify your build script to add the space, or specify AnyCPU in your commandline command.
    Take a look at the new Web Publish stuff thats just been released (http://sedodream.com/2012/06/15/VisualStudio2010WebPublishUpdates.aspx) – it includes bits for database migrations too, I haven’t had a try yet though!

    • http://shcherbak.me/ Alexey Shcherbak

      Chris, the main issue with Web Publish stuff ( and all other great VStudio built-in stuff) is that it’s kinda “Work on my machine” mindset. CI systems require slightly different approach and usually lead to some more initial work to set it up properly.

      It’s always good to show how cool you can publish to azure from your laptop. But when you ever will need to repeat everything EXACTLY as it was 1-2 months ago to track a bug – you cant do it on your machine, because your environment usually already altered with new SDKs, patches, tools etc. Dedicated build machines and CI system saves you. So Msbuild (or any other build script) and CI are “musthave” for any serious production environments. IMO.

      • Chris Surfleet

        Definately. We’re still deploying to quite a few IIS6 environments, and getting everything setup through CI was a bit of a nightmare! I ended up creating a custom workflow using MsBuild internally to publish the apps, then xcopying, it shouldn’t be so difficult really. I’m hopeful that the new web publish stuff will really help with this stuff, I’ve just not had time to install it on the TFS box and play around yet!

        • http://shcherbak.me/ Alexey Shcherbak

          Xcopy for internal environment
          actually ftw =). We did the same and I enjoying full control of build process. I’m going to look on any publishbuild features only if it supplied with msbuild targets. Otherwise – GUI chrome is useless for any CI.

    • http://randonom.com/ Sean Glover

      Chris, thanks for reading. And thanks again for the suggestion! At a glance that indeed does look like the problem *facepalm*. When I have a moment to test my build process I’ll try it out and update this post accordingly.

      EF CodeFirst Migrations look very promising. It’s a long time coming for this sort of functionality. One only has to look as far as ruby’s migration framework for inspiration.. I suppose that’s the usual story for ASP.NET “innovation” :). Code first migrations have been supported for awhile now as part of EF 4.3, but what you’re getting at is it looks as though it will be supported by the next Web Deploy? After a cursory search I can’t find much info on this other than the link you provided.

      EF 4.3 CodeFirst based migrations walkthrough
      http://blogs.msdn.com/b/adonet/archive/2012/02/09/ef-4-3-code-based-migrations-walkthrough.aspx

    • Biergarten

      Thanks to both. I was having this problem. And it did solved it. But I can’t understand why it was working form the default debug/release configurations. In the .csproj file you could see (‘Dev’ is my custom configuration:

      ….