#IIS Hacks

Server & System Administration Archive · 2007–2013

An independent, non-commercial archive. Not affiliated with, endorsed by or connected to any vendor named in these notes.

Two adjacent server racks seen edge-on, one rail run lit in cool light and the other in shadow

Migrating websites from IIS6 to IIS7

Archived note. Originally published 7 October 2008 on iishacks.com. It describes moving a website from IIS 6 to IIS 7 using the Microsoft Web Deployment Tool, and is preserved here as a record. IIS 6 shipped with Windows Server 2003, and Windows Server 2003 and Windows Server 2008 are both long past end of support; the deployment tool named here has been through several generations since. Verify against current vendor documentation before applying any of it to a live system.

The original note

The steps below are what was required to move a website hosted on IIS 6 to IIS 7. IIS 7 replaces the aging metabase with a more convenient applicationHost.config, which stores the configuration in XML.

Two builds of MS Deploy, the program needed to migrate, were published at the time: 32-bit and x64. The original note linked to both downloads; those addresses are not preserved here.

Installing MS Deploy on the source IIS 6 server

The server must have .NET Framework 2.0 SP1 or 3.5 installed. Download the file and open it, then choose Setup Type: Typical. Use the x64 version on Windows Server 2003 x64. There is no entry in the Start Menu; the program is started from a command prompt:

C:\Program Files\Microsoft Web Deploy\msdeploy

followed by the command to be run.

Installing MS Deploy on the destination IIS 7 server

The same procedure, using the x64 version on Windows Server 2008 x64.

Create a backup of the IIS 7 configuration

Backing up the IIS 7 configuration before starting matters, because restoring IIS to its default configuration without one is unpleasant work.

IIS 7 ships appcmd.exe, a command line tool new in that version, which simplifies configuration backups considerably. It lives in %windir%\system32\inetsrv\, which is not in the path variables, so the command prompt has to be pointed at that folder first — type cd \ and then cd C:\Windows\system32\inetsrv if Windows sits in the default location. Then:

appcmd add backup "BackupPreMigrate"

To list previous backups, and to restore one:

appcmd list backup
appcmd restore backup "BackupPreMigrate"

Verify dependencies on the source IIS 6 server

A screenshot of the variables the migrate tool supported appeared here in the original note and is not preserved. The instructions assume a command prompt in the C:\Program Files\Microsoft Web Deploy\ directory.

msdeploy -verb:getDependencies -source:metakey=lm/w3svc/#siteidentifier

The list returned is fairly comprehensive as to which components are installed and available for use on that website. It does not, however, distinguish which of them are actually in use.

Installing required components on the destination IIS 7 server

The dependency list determines which roles have to be installed on the destination IIS 7 server. Any dependency listed in the XML file saved during the migration has to be present there or the migration will not complete. Dependencies can be removed beforehand, or removed from the XML file in the backup directory after the sync command.

Migrate the website

On the source IIS 6 server:

msdeploy -verb:sync -source:metakey=lm/w3svc/#siteidentifier -dest:archivedir=c:\backup_name

Move the backup folder (c:\backup_name) to the destination server — or, on a network, write it straight to its final location on the IIS 7 server or the SAN. Then, on the destination IIS 7 server:

msdeploy -verb:migrate -source:archivedir=c:\backup_name -dest:metakey=lm/w3svc/#siteidentifier

After migration

Some elements have to be re-configured once the migration completes. PHP, ASP.NET Ajax and others are configured independently of the IIS migration, to match the source server's settings.

From the metabase to applicationHost.config

A dedicated tool existed because the two versions do not hold configuration in the same shape. IIS 6 kept it in the metabase: a machine-wide store owned by the server, reached through the IIS Manager console or through ADSI and WMI scripts, with sites addressed by a metabase key — the lm/w3svc/#siteidentifier above, where the identifier is the numeric site ID, not the site name.

IIS 7 discards that. Server-level settings live in applicationHost.config under %windir%\system32\inetsrv\config\, and anything delegated lives in a web.config inside the site itself, in the XML schema ASP.NET already used. Most of a site's configuration therefore travels with its content, which the metabase never allowed — the reason moving a site between two IIS 6 servers meant re-entering settings by hand, and why moving to IIS 7 needed translation rather than a copy.

Integrated and classic pipelines

The second break caused more post-migration failures than the configuration format did. Under IIS 6, ASP.NET was an ISAPI extension: anything mapped to aspnet_isapi.dll went to the managed runtime, so managed modules saw ASP.NET requests only. IIS 7 introduces the integrated pipeline, in which managed modules participate in the server's own pipeline and can act on every request whatever handles it, and application pools default to it.

Applications configured the IIS 6 way declare modules and handlers in the <system.web> section of web.config. Integrated mode reads them from <system.webServer> instead and refuses to start an application whose settings contradict the mode it runs in, reporting that an ASP.NET setting has been detected that does not apply in integrated managed pipeline mode. The ways out were appcmd migrate config, which rewrites the declarations, or classic mode, which restores IIS 6 behaviour at the cost of the feature.

What the deployment tool did

MS Deploy — the Web Deployment Tool — is a synchronisation engine rather than an installer. Source and destination are each named as a provider: a metabase key, an archivedir on disk, a whole machine, a single application. -verb:sync makes the destination match the source, -verb:migrate does the same with the IIS 6 to IIS 7 translation applied, and -verb:getDependencies changes nothing. The archivedir route makes the job workable when the servers cannot reach each other, and the manifest it writes is XML that can be edited before the migrate step. Its stated limitation is the part that costs time: the dependency list reports what is installed and available rather than what the site uses, so installing everything it names leaves the destination with a larger attack surface than the server being replaced.

What usually failed: 32-bit code

The dependency that broke these migrations most often was native code that could not be carried across: 32-bit ISAPI filters and extensions, and COM components registered on the old server. Bitness is half of it. Windows Server 2008 x64 runs 64-bit worker processes by default, and a 64-bit process cannot load a 32-bit DLL, whether an ISAPI filter, an in-process COM server or a PHP binary. The remedy is always Enable 32-bit Applications on the application pool — the same constraint behind the pool steps in installing PHP ISAPI on Windows 2008 IIS 7 x64 and in the later PHP 5.3 FastCGI note.

Registration is the other half. A COM component is a machine-level registration, not a file in the site folder, so it must be installed and registered on the destination and the pool identity permitted to activate it. IIS 7 also replaced the IIS 6 Web Service Extensions allow-list with ISAPI and CGI Restrictions, so an extension permitted on the source is denied until allowed explicitly — a migration that looks successful, followed by a site that errors for one file type only.

What has changed since

IIS 6 and Windows Server 2003 are out of support, so this procedure has no live application; it is kept because the model it moves to is the one IIS still uses. Current documentation is at Microsoft's IIS documentation home and the Windows Server documentation, with supported versions in the product lifecycle pages. The rest of the web-server material sits under Internet Information Server, the interpreter-side notes under PHP, and the full index is the complete post archive.