It seems that much of the open source community (including WordPress) works hard to avoid Microsoft Internet Information Server (IIS) available on Microsoft operating systems in favor of Apache – even if they are running on a Microsoft operating system.  I’ve been using Windows for a very long time and am comfortable with it and have a certain level of expertise with it, so rather than throw all that experience away and try to learn yet one more new thing (of all the new things I’ve learned so that I can use and enjoy WordPress) I have worked hard at making WordPress work on IIS.

One of the things that keeps cropping up, however, is the use of .htaccess files. These files describe how to rewrite URLs for a site to make them look the way the site administrator – or the software being used by the administrator – wants them to look.  IIS doesn’t have support for rewrite rules, but you can buy software from third parties that will do the job.  I stumbled across a free URL rewriter called Ionics Isapi Rewrite Filter, or IIRF, which does the job quite nicely for free.

Of course, free is a relative term, and free software usually has other costs – like time.  IIRF was no different for me.

Here were my goals for implementing rewrite rules for my sites (which as of this writing is still in the testing phase and therefore may not be visible yet on this site):

  • Get rid of the index.php in the URL required by IIS.  IIS requires this because it requires that addresses map directly to files in the home directory.
  • Map one permalink structure (e.g. year/month/day) to another (e.g. (year/month).
  • Be able to support WordPress sites configured as a virtual directory of another site (e.g. dpotter.net and dpotter.net/technical).

IIRF is extremely flexible, allowing you to use regular expressions to describe rules for rewriting URLs.

Here are some things to look out for when deploying IIRF.

  • IIRF listens for file system notifications to discover when the configuration file (an .INI file stored in the same directory as the DLL) has changed.  As a result:
    • Copy the IsapiRewrite4.dll file to a directory that is neither in your inetsrv directory nor in your Inetpub directory.
    • Specify a log file location that is not in or below the directory containing the IIRF DLL.
    • Since this directory will contain the rewrite rules configuration – which is user configuration – I prefer to place this somewhere I have full control of and can count on remembering to back up.  This excludes anywhere in or below the Windows or Program Files directories.
  • Test the filter using a separate app pool.  This allows you to take it down easily without affecting other sites (such as live sites you are testing at the moment).  Once you’re satisfied with your rewrite rules, you can deploy those sites in any app pool you choose.
  • Don’t forget to add IIRF as an allowed web service extension in IIS Manager.
  • The instructions say to take down the iisadmin service to install it.  I suspect that is not actually necessary.  You may be able to get started by simply recycling the app pool to which your web sites/virtual directories belong.
  • You can have multiple sets of rewrite rules by deploying multiple instances of the IIRF DLL.  Of course, you can’t deploy separate ISAPI DLLs for virtual directories – unfortunately.  Being able to do so would have solved some problems for me.

There are others using IIRF on their WordPress web sites, but I suspect none of them are using them for both root-level and virtual directory-level sites.  Here is the basic WordPress rewrite rule:

RewriteRule ^/(?!index.php)(?!wp-)(.*)$ /index.php/$1

This rule says:

  • If the URL doesn’t contain index.php at the beginning
    AND
  • If it doesn’t contain something that begins with wp- at the beginning (e.g. wp-content or wp-admin),
    THEN
  • Add /index.php at the beginning of the URL.

This will cause the following URLs to be mapped like this:

URL Becomes
/ /index.php
/2008/10/hello-world/ /index.php/2008/10/hello-world
/about/ /index.php/about/

This looks great!  Okay, what happens if we add a web site at a virtual directory called blog?

URL Becomes
/blog/ /index.php/blog/
/blog/2008/10/hello-world/ /index.php/blog/2008/10/hello-world
/blog/about/ /index.php/blog/about/

Hmm, clearly not what we want.  This requires the following modifications to our rewrite rules:

RewriteRule ^/blog/(?!index.php)(?!wp-)(.*)$ /blog/index.php/$1 [I,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^/(?!index.php)(?!wp-)(.*)$ /index.php/$1

This solves the problem, allowing the URLs beginning with /blog/ to be rewritten first before the root-level rewrite occurs.  The two RewriteCond lines prevent URLs that refer to a specific file or directory from being rewritten.  This allows URLs like /wp-login.php or /wp-admin/ to be passed right through to IIS.

The next problem I wanted to solve was to redirect all traffic that included the index.php in the URL to the URL without it.  Adding this line achieves that objective:

RewriteRule ^/blog/index.php(.*)$  /blog/$1  [I,R=301]

The final problem I wanted to solve was to redirect traffic that included the day number for blog posts to an URL without it.  This rewrite rule strips the day out and redirects the browser to the new style URL:

RewriteRule ^/blog/(index.php/)*([1-2][0-9]{3})/([0-1][0-9])/([0-3][0-9])(.*)$ /blog/$2/$3$5 [I,R=301]

Here is my final rewrite file (IsapiRewrite4.ini) with comments.  Notice that it supports two virtual directories called Technical and David:

# IsapiRewrit4.ini
#
# Rewrite rules for WordPress sites.
#
RewriteLog       C:\WebTools\LogFiles\IIRF
RewriteLogLevel  3

# Remove the day and (optionally) index.php from the URL.
# Old style --> /Technical/index.php/2008/10/01
# New style --> /Technical/2008/10
RewriteRule ^/(Technical|David)/(index.php/)*([1-2][0-9]{3})/([0-1][0-9])/([0-3][0-9])(.*)$ /$1/$3/$4$6 [I,R=301]

# Remove index.php and redirect to the URL without it.
# Old style --> /Technical/index.php/whatever
# New style --> /Technical/whatever
RewriteRule ^/(Technical|David)/index.php(.*)$  /$1/$2 [I,R=301]

# Add the index.php back in since IIS requires it, but don't show it to the user.
# Only rewrite if the following conditions are true:
#   --> There is no index.php right after the virtual directory name.
#   --> The address does not contain one of the wp-* names in it.
# Stop processing rules if this rule matches.
# That avoids having the next rule adding index.php before the virtual directory name.
RewriteRule ^/(Technical|David)/(?!index.php)(?!wp-)(.*)$ /$1/index.php/$2 [I,L]

# Rewrite URLs for the root that don't have index.php in them.
# Only rewrite them if they aren't for a file or directory that exists.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule                     ^/(?!index.php)(?!wp-)(.*)$     /index.php/$1

I’m still refining this and hope to simplify it even further.  Feel free to give me your feedback.