
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.