A Practical Guide to MS Web Deploy

When I started working on a .NET project 6 months ago, I was quite surprised to find out that it is fairly easy to remotely publish a web app on an IIS server, using MSBuild, Batch, or PowerShell. This article provides a recipe for installing, configuring, and using WebDeploy 3.0 for IIS 7.0 on Windows 2008 Server R2. There are a few gotchas along the way, which I have highlighted as Notes.

Installing Web Deploy 3.0

All tips in this article use WebDeploy version 3.0. You will then first need to install it. You can find the MSI here. A word of caution with two things. First you need to make sure Web Deploy 2.0 is not already on the machine. Go to your list of programs and check Web Deploy 2.0 is not present. If it is then uninstall it.

Screen Shot 2013-05-13 at 11.39.00 AM

Web Deploy 3.0 in the Windows Programs list

Note: If Web Deploy 3.0 is already installed, or if you install it from scratch, you need to make sure that all components are installed, particularly the IIS Deployment Handler, and the Remote Agent Server as on the screenshot below. If you notice that when running the Web Deploy 3.0 installer, those features do not appear, then you need to read this post and run the dism commands provided by hefeiwess, before installing/changing Web Deploy 3.0.

Install all components of Web Deploy 3.0

Install all components of Web Deploy 3.0

Web Deploy Post Installation Checklist

Once you have installed Web Deploy 3.0 with all its components, you should be able to start deploying straight away, unless you do not want to use a local administrator account, in which case read the chapter Using a non admin user below.

But before trying it out, it is worth checking a few things:

1. The Web Management & Web Deployment Agent Windows Services should be up and running.

Note: Both services can be used for remote deployment, but in this article I only detail how to use the Web Management Service (WMSVC). The Web Management Service is only available in Web Deploy 3.0, and can be used for deployment as an admin, or as a specifically configured deployment user. For details on how Web Deploy works, and the difference between the two services please read this.

Windows Services required for remote IIS deployment

Windows Services required for remote IIS deployment

2. Check that IIS is properly configured for remote deployment. Go to the IIS Manager, double click on the root server node you would like to deploy to, and go to the Management Service page in the Management Section. You should have the ‘Enable remote connections‘ checkbox selected, and the service accepting connections on port 8172. Make also sure that no IP addresses are assigned as below (unless you want to limit the IP range of clients that can deploy remotely).

Screen Shot 2013-05-13 at 1.23.35 PM

IIS Management Service options

Note: If you browse the web for Management Service configuration, you may see pages or posts talking about Feature Delegation or Management Service Delegation. Delegation allows the IIS Manager to configure the Management Service ACLs at the operation level. In our case, no need to configure any Feature Delegation rules as the required rules are created by default when installing Web Deploy 3.0.

3. Check that you can hit the Management Service handler URL. Open your favourite web browser and hit https://iis_server:8172/MsDeploy.axd. This is the URL of the web service that Web Deploy will hit for each deployment. If all is fine you may have an HTTPS security alert, and you should have a login screen as below.

Login to MsDeploy in Firefox

Login to MsDeploy in Firefox

Publish With Visual Studio

Before trying to publish from a command line or a build tool, you might want to check that you can publish directly from Visual Studio. To do that, select your Web Project within your VS Solution, and go to the menu BUILD > Publish Selection. You will then be taken through a wizard to configure the Web Deploy publish.

1. Profile: A publish profile is an XML file that contains details of your publish task. You can select an existing profile, or create a new one.

Screen Shot 2013-05-13 at 2.21.16 PM

Select Publish profile in VS2012

Publish profiles will be stored with a .pubxml extension, at the root of your Web project under Properties\Publish Profiles. This is an example of what it may contain. Usually one can specify the Site/App to deploy to, the User name, and deployment type. In my case I have reduced it to its minimum, as I prefer to specify most parameters at runtime, when I deploy (see chapter Publishing with MSBuild). Note the MSDeployPublishMethod value here set to WMSVC. The alternative value would be RemoteAgent.

<?xml version="1.0" encoding="utf-8"?>
 <!--
 This file is used by the publish/package process of your Web project. You can customize the behavior of this process
 by editing this MSBuild file. In order to learn more about this please visit http://go.microsoft.com/fwlink/?LinkID=208121.
 -->
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 <PropertyGroup>
 <WebPublishMethod>MSDeploy</WebPublishMethod>
 <SiteUrlToLaunchAfterPublish />
 <MSDeployServiceURL>http://$(DeployIisHost):8172/MsDeploy.axd</MSDeployServiceURL>
 <RemoteSitePhysicalPath />
 <SkipExtraFilesOnServer>True</SkipExtraFilesOnServer>
 <MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
 <_SavePWD>True</_SavePWD>
 <PublishDatabaseSettings>
 <Objects xmlns="" />
 </PublishDatabaseSettings>
 </PropertyGroup>
 </Project>

2. Connection: The next pane of the wizard requires details to connect to the remote IIS site and deployment handler. Service URL is the URL to the Management Service Handler. Site/Application is the IIS Site and Virtual Application you want to deploy to. In my case the Site is InternetOrder AU and the Application is estorebeta. User name and Password are those you want to use to authenticate to IIS and to the Management Service Handler. Here, I use a local administrator, which will work by default with Web Deploy 3.0. Finally you can hit the Validate Connection button to check that the handshake is working.

Screen Shot 2013-05-13 at 2.28.12 PM

Configure Web Deploy job details

3. Configuration: It is used to build your project before deploying the binaries. I recommend mapping Web Deploy publish profiles to MSBuild configuration. It will make the management of environment-specific files easier on the long run. For instance, in one of my project I have the following publish profiles and Web config transforms.

Screen Shot 2013-05-13 at 2.35.53 PM

Typical Publish Profile and Web Config arrangement for multiple environments

Publishing with MSBuild

If you use MSBuild as your build DSL, you can call the WebPublish target to implement your custom target and perform a remote web deploy. The simplest way to explain it is to take your through an example. The parameters of my WebBuildAndPublish target below are:

  • $(ProjectFile)     Path to your VS .csproj Web Project file (e.g. Corsamore.TestProject\TestProject.csproj).
  • Configuration    The Build configuration (e.g. Debug, Release, etc..)
  • PublishProfile    The name of the publish profile to use (if you keep a one-to-one mapping with Configuration use the same name)
  • DeployIisHost     The name of the IIS Server to deploy to (only needed if not specified in the Publish Profile).
  • DeployIisPath     The IIS path to the Site application to deploy to (e.g. corsamore/blog)
  • UserName           The user name to use for Web Deploy (only needed if not specified in the Publish Profile).
  • Password            The user password to use for Web Deploy.
<Target Name="WebBuildAndPublish">
 <MSBuild Projects="$(ProjectFile)" 
         Targets="Clean;Build;WebPublish" 
         Properties="
           VisualStudioVersion=11.0;
           Configuration=$(PublishProfile);
           PublishProfile=$(PublishProfile);
           AllowUntrustedCertificate=True;
           UserName=$(DeploymentUser);
           Password=$(DeploymentPassword);"/>
</Target>

Typical Errors

The most common errors I have encountered when running a Web Deploy command and how I fixed them are listed here. All errors codes and details can be found here.

  • ERROR_DESTINATION_NOT_REACHABLE is a connectivity issue with your IIS, or the Web Management Service is not up.
  • ERROR_SITE_DOES_NOT_EXIST make sure that the site has been manually created before hand, even an empty one if necessary.
  • ERROR_USER_UNAUTHORIZED is usually when you are either mistyping the Admin user name or password, or trying to use a non-admin user (see next chapter for setup).
  • error: An error occurred when the request was processed on the remote computer followed by error : Unable to perform the operation. Please contact your server administrator to check authorization and delegation settings. usually happens when the first handshake with the Web Management Service was successful, but the files could not be copied on the remote server physical location. To troubleshoot that I recommend looking at the Microsoft Web Deploy logs in the Event Viewer on the IIS server.
Microsoft Web Deploy log in the 2008 R2 Event Viewer

Microsoft Web Deploy log in the 2008 R2 Event Viewer

Using a non admin user

If you are in the situation where you cannot use an admin user account (e.g. you deploy to production), the great thing with Web Deploy 3.0 is that you can setup a non-admin user with specific permissions just for the site/app you have to publish to. Again, the web is full of conflicting info as to how to do that. I tell you here how I have done it for a client in 3 simple steps. Also, I provide here a parameterised PowerShell script that will setup such a user for you.

Step 1 : Create a new local user.

The CreateLocalUser PowerShell function below will create a local user on the local machine.

function CreateLocalUser ([string]$user, $password) 
{
 Write-Host "Will create local user $user with password $password"
 $computerObj = [ADSI]"WinNT://localhost"
 $userObj = $computerObj.Create("User", $user)
 $userObj.setpassword($password)
 $userObj.SetInfo()
 $userObj.description = "Remote Web Deploy User"
 $userObj.SetInfo()
 Write-Host "User $user created"
}

Step 2 : Grant the new user access to the physical directory where WebDeploy will copy files to, i.e. the directory that is mapped to the IIS Web App to deploy to.

The GrantUserFullControl function below will add a new ACL for the given $directory for the newly created $user, so that WebDeploy can copy files to that directory on its behalf.

function GrantUserFullControl ([string]$user, [string]$directory)
{
 if(Test-Path "$directory")
 {
 Write-Host "Will create $user permissions $permissions for directory $directory."
$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit"
 $PropagationFlag = [System.Security.AccessControl.PropagationFlags]"None"
 $objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
 ($user, "FullControl", $InheritanceFlag, "None", "Allow") 
 $objACL = Get-ACL "$directory"
 $objACL.AddAccessRule($objACE) 
 Set-ACL "$directory" $objACL
Write-Host "Permissions for user $user set."
 }
}

Step 3 : Grant IIS Manager permissions for the new user to edit any Web App under the target Site.

The GrantUserIisAccess function below will configure the correct level of access for the new $user to be able to WebDeploy to any Web App within the IIS $site.

function GrantUserIisAccess ([string]$user, [string]$site)
{
	Write-Host "Will grant $user permission to access IIS site $site."

	$hostName = [System.Net.Dns]::GetHostName()
	[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Management")

	$authorisedUsers = [Microsoft.Web.Management.Server.ManagementAuthorization]::GetAuthorizedUsers("$site", $true, 0, -1)
	$isAuthorised = $false
	foreach ($authorisedUser in $authorisedUsers)
	{
		if($authorisedUser.Name -eq "$hostName\$user") 
		{
			$isAuthorised = $true
		}
	}
	if(!$isAuthorised)
	{
		[Microsoft.Web.Management.Server.ManagementAuthorization]::Grant("$hostName\$user", "$site", $FALSE)
		Write-Host "Access to $site for user $user granted." 
	}
	else {
		Write-Host "Access to $site for user $user was already granted."
	}
}

Recap : Here is a script that puts it all together. Just replace mysite with your IIS Site, myapp with your Web App you want to deploy to, and mydir with the physical directory the Web App is mapped to in IIS.

create_webdeploy_user.cmd

@echo off
set user=%~1
set password=%~2
powershell -ExecutionPolicy RemoteSigned -File "%~dp0CreateWebDeployUser.ps1" -user %user% -password %password% -site "" -application  -dir ""

CreateWebDeployUser.ps1

param($user, $password, $site, $application, $dir)

function LocalUserExists([string]$user) 
{
	$computer = [ADSI]("WinNT://localhost")
	$users = $computer.psbase.children |where{$_.psbase.schemaclassname -eq "User"}
	foreach ($member in $Users.psbase.syncroot)
	{
		if( $member.name -eq $user) {
			return $true
		}
	}
	return $false
}

function CreateLocalUser ([string]$user, $password) 
{
	Write-Host "Will create local user $user with password $password"
	$computerObj = [ADSI]"WinNT://localhost"
	$userObj = $computerObj.Create("User", $user)
	$userObj.setpassword($password)
	$userObj.SetInfo()
	$userObj.description = "Remote Web Deploy User"
	$userObj.SetInfo()
	Write-Host "User $user created"
}

function GrantUserFullControl ([string]$user, [string]$directory)
{
	if(Test-Path "$directory")
	{
		Write-Host "Will create $user permissions $permissions for directory $directory."

		$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]"ContainerInherit, ObjectInherit"
		$PropagationFlag = [System.Security.AccessControl.PropagationFlags]"None"
		$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule `
			($user, "FullControl", $InheritanceFlag, "None", "Allow") 
		$objACL = Get-ACL "$directory"
		$objACL.AddAccessRule($objACE) 
		Set-ACL "$directory" $objACL

		Write-Host "Permissions for user $user set."
	}
}

function GrantUserIisAccess ([string]$user, [string]$site)
{
	Write-Host "Will grant $user permission to access IIS site $site."

	$hostName = [System.Net.Dns]::GetHostName()
	[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Management")

	$authorisedUsers = [Microsoft.Web.Management.Server.ManagementAuthorization]::GetAuthorizedUsers("$site", $true, 0, -1)
	$isAuthorised = $false
	foreach ($authorisedUser in $authorisedUsers)
	{
		if($authorisedUser.Name -eq "$hostName\$user") 
		{
			$isAuthorised = $true
		}
	}
	if(!$isAuthorised)
	{
		[Microsoft.Web.Management.Server.ManagementAuthorization]::Grant("$hostName\$user", "$site", $FALSE)
		Write-Host "Access to $site for user $user granted." 
	}
	else {
		Write-Host "Access to $site for user $user was already granted."
	}
}

if(!$user -or !$password -or !$site -or !$application -or !$html5dir)
{
   $(Throw 'Values for $user, $password, $site and $application and $html5dir')
}

if((LocalUserExists -user $user) -eq $false)
{
	CreateLocalUser -user $user -password $password
}
else {
	Write-Host "User $user already exists. Will do nothing."
}

GrantUserFullControl -user $user -directory "$dir"
GrantUserIisAccess -user $user -site "$site"

Write-Host "User $user setup completed."

Conclusion

I started this article stating that it is fairly easy to deploy to IIS using WebDeploy 3.0. It actually is, despite the length of this article. What makes it difficult, as usual, is the floraison of often hard to understand or conflicting information about it. I hope this article gives a concise recipe that will help you successfully use Web Deploy.