As promised, here are my demo notes from my first TechEd Australia talk. If you were able to attend, thanks for coming. If you didn’t make it or just aren’t lucky enough to live in OZ, I think some of this may still be helpful for you, especially if you are just getting started with IIS7.
Adding and Removing Modules
In this short demo, I showed how to pull IIS7 modules out of an IIS worker process by removing their configuration from the <globalModules> and <modules> sections of applicationHost.config (located in %systemdrive%\Windows\System32\inetsrv\config\ ).
I used this Windows Powershell script to display what modules were currently loaded into the worker process:
gps -name w3wp | select -expand Modules | where {$_.Filename -like '*\inetsrv*'} | ft
I showed this example of the minimum module configuration for just serving static content:
<globalModules>
<
add name="DefaultDocumentModule" image="%windir%\System32\inetsrv\defdoc.dll" /><add name="StaticFileModule" image="%windir%\System32\inetsrv\static.dll" />
<
add name="AnonymousAuthenticationModule" image="%windir%\System32\inetsrv\authanon.dll" /></globalModules>
<
modules>
<
add name="DefaultDocumentModule" lockItem="true" />
<
add name="StaticFileModule" lockItem="true" /><add name="AnonymousAuthenticationModule" lockItem="true" />
</
modules>
This is the profile of the worker process under this configuration:

For more on IIS7 Modules, check out IIS7 Modules Overview. For more on Windows Powershell, check out the PowerShell Blog on MSDN
Migrating an ASP.NET site to IIS7
In this next part, I showed how easy it is to get your ASP.NET site up and running on IIS7. I first created a new Web site in IIS manager and pointed it at my site’s directory. When I hit the site in IE for the first time, I got this error:

The reason I get this error is because there are settings in my site’s Web.config file that do not apply in Integrated managed pipeline mode. By default, when I create a new site, IIS7 will create a new Application Pool for it, which by default, will run in Integrated managed pipeline mode. If my existing ASP.NET site use <httpModules>, <httpHandlers> or <identity impersonate = true>, I will get this error when I first run it in Integrated mode.
No worries though, there is a simple command you can run to migrate these sections. Even better, the error you see is one of IIS7’s new detailed errors, so the remedy is provided right in the error page (don’t worry, this great level of detail is only available locally, your customers definitely won’t be reading detailed errors that reveal configuration only you should be privy to) . I’ve marked the command in the error above, but here it is again:
%SystemRoot%\system32\inetsrv\appcmd migrate config "<Your Site Name>/".
After running this command in an elevated command prompt, IIS7 migrates the <httpModules> and <httpHandlers> sections that my site was using.

Here is the configuration that IIS7 adds to my Web.config for me:
<system.webServer>
<
modules>
<
add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
</
modules><handlers>
<
add name="ScriptResource.axd_GET,HEAD" path="ScriptResource.axd" verb="GET,HEAD" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv2.0" /><add name="*_AppService.axd_*" path="*_AppService.axd" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv2.0" />
<
add name="*.asmx_*" path="*.asmx" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv2.0" /></handlers>
<
validation validateIntegratedModeConfiguration="false" />
</
system.webServer>
Notice that IIS7 is using its new <system.webServer> section to store IIS configuration right alongside ASP.NET configuration in <system.web>. Also notice that the configuration for the module this site is adding, there is a setting “precondition=”managedHandler” that ensures the module is only used for ASP.NET requests. By default, when IIS7 migrates configuration for integrated mode, IIS will make sure that managed modules continue to run for only ASP.NET requests. This way there is no unexpected behavior when you migrate a site from IIS6.
For more on running ASP.NET on IIS7, take a look at ASP.NET Integration with IIS7
Applying Managed Modules to Non-Managed Content
Once I have my site up and running in Integrated managed pipeline mode, I can start to take advantage of this new request processing feature by applying managed modules, like ASP.NET’s Forms Authentication module, to non-managed content, like a pdf file, without having to map any extra extensions to ASP.NET
In my talk, this demo showed the power of the integrated pipeline, but it also illustrated just how deeply we integrated ASP.NET into our management stack. A full ASP.NET roles-based membership solution can be configured quickly from one place in one tool. Then, by leveraging the integrated pipeline, we can apply this solution to any non-ASP.NET content found in the site.
To set up this membership solution, I completed the following steps in IIS Manager:
1. Enable Forms Authentication (don’t forget to add login.aspx to the root of your site’s directory)
2. Enable Role Manager and create a role
3. Add new users to the membership store and the new role as appropriate
4. For only the URLs that require restricted access, configure Authorization rules (Deny Anonymous Users, Allow <Your Role>)
I’ve marked where these steps must be completed in the next two screen shots.


*Note: The stars on this screenshot indicate how I found the exact resource in my site’s directory using IIS Manager’s Content View, and then I switched to the Feature View for this particular URL and configured URL Authorization rules just for this resource. This membership solution will have no impact on the rest of my site since the rest of the site permits Anonymous users.
Now, this membership solution is fully implemented for ASP.NET but will not work for non-ASP.NET content, like static pdf files, because managed modules the solution requires, are not configured to process any request to static files. Thus when I try to browse to this URL, I will get a 401.2 Unauthorized because Forms Authentication is not being loaded (because it’s a managed module) and the site needs Forms Authentication to acquire credentials required by the URL Authorization rules we set. URL Authorization is still enforced because it is a native IIS module and always works for all requests regardless of content type.
To enable the membership solution for all static content, three managed modules must be configured to run for non-managed content: Forms Authentication, Default Authentication and Role Manager. This step can be completed in the Modules feature area of IIS Manager or just by editing the <modules> section in Web.config. Here is the result in Web.config:
<modules>
<
remove name="DefaultAuthentication" /><remove name="FormsAuthentication" />
<
remove name="RoleManager" /><add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
<
add name="RoleManager" type="System.Web.Security.RoleManagerModule" preCondition="" /><add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" preCondition="" />
<
add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" preCondition="" />
</
modules>
Note that the precondition attribute has been cleared for the RoleManager, FormsAuthentication and DefaultAuthentication modules. This is the setting that allows them to take advantage of request processing integration and apply to all content types.
For more on the Integrated managed pipeline mode, check out How to Take Advantage of the IIS7 Integrated Pipeline. For more on using Forms Authentication for all request types, check out Forms Authentication in IIS7
Configuring PHP on IIS7
In this short demo, I deployed a new PHP site and pointed out two configurations that are mandatory for hosting a PHP site on IIS7. The first was adding index.php to the Default Document list which can be done easily in Web.config or the IIS Manager tool. This step is required because by default, IIS will not include the popular default document for PHP.
The second important configuration was to add a handler (what we used call a script map) that tells IIS to processes files of the extension *.php with the CGI module or more preferably, the new FastCGI module. This second step is rather easy to complete by selecting “Add Module Mapping” from Actions List in the Handlers feature page of IIS Manager. The result is a Web.config file that can be deployed with the PHP site to preconfigure the site for any IIS7 server. Here are its contents:
<configuration>
<
system.webServer>
<
defaultDocument>
<
files>
<
add value="index.php" />
</
files></defaultDocument>
<
handlers>
<
add name="FastCGI_PHP" path="*.php" verb="*" modules="FastCgiModule" scriptProcessor="E:\php\php-cgi.exe" resourceType="Unspecified" />
</
handlers></system.webServer>
</
configuration>
For more on PHP on IIS7, check out Using FastCGI to host PHP applications on IIS7
Output Caching for Faster Dynamic Content
IIS7 introduces Output Caching for all types of dynamic content. I demonstrated just how much performance can be improved by enabling output caching for the PHP application, QDIG, which I deployed in the previous demo.
As you can see in the screenshot below, Output Caching is simple to set up from the Output Caching feature page in IIS Manager.

I’ve pointed out my specific configurations with the red boxes. My cache rule is for the file extension *.php and cached responses are set to be refreshed every 30 seconds. I selected User-mode caching so I can cache multiple versions of the same file and under “Advanced Settings,” I’ve told the Output Caching module that the responses will vary based on four Query String parameters (Qwd, Qif, Qiv and Qis). Here is the exact configuration from the site’s Web.config file:
<caching>
<
profiles>
<
add extension=".php" policy="CacheForTimePeriod" kernelCachePolicy="DontCache" duration="00:00:30" varyByQueryString="Qwd, Qif, Qiv, Qis" />
</
profiles>
</
caching>
For more on Output Caching, check out IIS7 Output Caching
Testing the Performance of FastCGI and Output Caching with WCAT
In the previous two demos, I used a great utility called WCAT (Web Capacity Analysis Tool) that allowed me to quickly test the performance of the two enhancements I made to the PHP site, switching to FastCGI over CGI to process PHP and enabling Output Caching of the dynamic responses of the Qdig site.
If you hadn’t heard of WCAT, for you, this may have been the most valuable part of my whole talk. WCAT is essential for testing not just your Web applications on IIS but your entire architecture of Web servers, databases, load balancers and other infrastructure that sits in front and behind an IIS-hosted site. Used locally, it can help isolate any performance issues that are rooted entirely in your application’s code. This is really helpful when you’re trying something new and you’d like to get some idea of how it will affect site performance before it’s rolled out into production.

Above is the results of my test of the Output Caching enabled Qdig site. How did I configure WCAT to get me all this performance data? It’s pretty easy to set up actually. There are two parts of WCAT to configure, the controller and the client. Think of the controller as the piece that does all the counting and the client as the piece that is hitting your application with all the simulated requests.
In my talk, I conveniently used two batch files to quickly execute the controller and the client in separate command prompts. Here’s what these two files contain:
php_controller.bat
wcctl -c config.cfg -s script.cfg -d distribution.cfg -a qdig
This is where the real work happens. I tell the controller (wcctl) what configuration, script and distribution files to use for the test (config.cfg, script.cfg, distribution.cfg), as well as what site to monitor (qdig).
Here is my configuration file for the tests, config,cfg. You can tweak these settings as much as you like, for me Duration is the most important setting, it’s how long I’m making the audience wait for the test to finish. J
Warmuptime 0s
Duration 20s
CooldownTime 0s
NumClientMachines 1
NumClientThreads 20
Here is my script file, script.cfg, which tells WCAT client what requests to make against the server. I only listed the first two entries because I think you can get the point. Main thing is to use a new classId as you’re cutting and pasting in new transactions.
NEW TRANSACTION
classId = 1
NEW REQUEST HTTP
Verb = "GET"
URL = "http://qdig/index.php?Qwd=./PHOTOS&Qiv=none&Qis=M"
NEW TRANSACTION
classId = 2
NEW REQUEST HTTP
Verb = "GET"
URL = "http://qdig/index.php?Qwd=./PHOTOS&Qif=DougWhistler.JPG&Qiv=none&Qis=M
…
Here is my distribution file, distribution.cfg, which tells WCAT client what the distribution of the different requests listed in the script file should be used. Each pair of numbers is a classId, followed by a weighting. (Note: despite what is shown, the total of all the numbers listed second does not have to add to 100 percent)
1 9
2 9
3 9
4 9
5 9
6 9
7 9
8 9
9 9
10 9
11 10
php_client.bat
wcclient.exe qdig
The client batch file includes no fancy configurations. I just need to tell wcclient.exe which site to hit and the controller configurations will specify everything else.
For more on WCAT, check out WCAT: Easy, Magical Stress Testing for IIS Web Applications
Building a Managed Module
In the last demo, I showed a small extension for IIS7 written in .NET, that adds a little footer to the bottom of a Web page and I showed it working for both an ASP.NET site and a PHP site. I quickly showed the code for this module ( < 150 lines!) and then told everyone to come back the next day and see my two talks on IIS7 Extensibility talk which go much deeper .
In similar fashion, this blog post includes just the configuration for adding this module and the code from FooterModule.cs. My next two posts covering my demos from the two extensibility talks will explain IIS7 extensibility in far greater detail.
Config:
<modules>
<
add name="FooterModule" type="FooterModule.FooterModule" />
</
modules>
Code:
using
System;
using
System.IO;
using
System.Collections.Generic;
using
System.Text;
using
System.Text.RegularExpressions;
using
System.Web;namespace FooterModule
{
/// <summary>
/// The module class.
/// </summary>
public class FooterModule : IHttpModule
{
#region IHttpModule Members
/// <summary>
/// Initializes the module, and registers for application events.
/// </summary>
/// <param name="application">
/// The System.Web.HttpApplication instance exposing application events.
/// </param>public void Init(HttpApplication application)
{
application.ReleaseRequestState +=
new EventHandler(this.AddFilter);
}
/// <summary>
/// Disposes of the resources (other than memory) used by the module.
/// </summary>public void Dispose()
{
}
#endregion
#region Module Event Handlers
public void AddFilter(object source, EventArgs e)
{
HttpApplication application1 = (HttpApplication)source;
HttpContext context1 = application1.Context;
string text1 = context1.Response.ContentType.ToLower();
if (context1.Response.ContentType == "text/html")context1.Response.Filter = new FooterFilter(context1.Response.Filter);
}
#endregion
}
public class FooterFilter : Stream
{
//public const string FOOTER_HTML = "<script src=\"/footer.js\"></script>";public const string FOOTER_HTML = "<div align=center><a href=\"http://www.iis.net/default.aspx?tabid=7\" title=\"IIS Home Page\"><img style=\"border:none\" src=\"http://www.iis.net/images/getIISgraphics/powered-by-iis7-1of2.png\" alt=\"Powered By IIS 7\" /></a></div>";
public const string BEFORE_REGEX = @"</body>\s*</html>";Stream responseStream;
long position;public FooterFilter(Stream inputStream)
{
responseStream = inputStream;
}
#region Filter overridespublic override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
responseStream.Flush();
}
public override long Length
{
get { return 0; }
}
public override long Position
{
get { return position; }set { position = value; }
}
public override long Seek(long offset, SeekOrigin origin)
{
return responseStream.Seek(offset, origin);
}
public override void SetLength(long length)
{
responseStream.SetLength(length);
}
public override int Read(byte[] buffer, int offset, int count)
{
return responseStream.Read(buffer, offset, count);
}
public override void Close()
{
responseStream.Close();
}
#endregion
public override void Write(byte[] buffer, int offset, int count)
{
string pageHTML = System.Text.UTF8Encoding.UTF8.GetString(buffer, 0, buffer.Length);Regex regex = new Regex(BEFORE_REGEX, RegexOptions.IgnoreCase);
Match match = regex.Match(pageHTML);
if ((match != null) && match.Success)
{
StringBuilder newBuffer = new StringBuilder(pageHTML.Length + FOOTER_HTML.Length);
newBuffer.Append(pageHTML.Substring(0, match.Index));
newBuffer.Append(FOOTER_HTML);
newBuffer.Append(pageHTML.Substring(match.Index));
ASCIIEncoding encoding = new ASCIIEncoding();byte[] finalBuffer = new byte[encoding.GetByteCount(newBuffer.ToString())];
finalBuffer = encoding.GetBytes(newBuffer.ToString());
responseStream.Write(finalBuffer, 0, finalBuffer.Length);
}
else
{
responseStream.Write(buffer, 0, buffer.Length);
}
}
}
}
Deploying this module to IIS7 is simple, just drop the .cs file in the App_Code directory (create one if you have to) and then add the config settings to the Web.config file (again, create one if you have to).
Building this module was actually not too bad. The real relevant part is where I hooked the module into the IIS pipeline so it can touch every request. The rest of the code in the module could have been built for ASP.NET on IIS6. Below is that crucial line from the Init() method.
application.ReleaseRequestState += new EventHandler(this.AddFilter);
Notice that I only need to tack on an EventHandler to the IIS pipeline event I want to hook onto, ReleaseRequestState. All the real processing is done by event handler methods, such as in this case, the method, AddFilter(). To put the footer on the page, AddFilter just adds a Response Filter to the response to this request. This filter only applies to text/html content types where it can find that closing <body/> tag. Response Filters require you to implement the Stream interface, which added the most lines to this module. The real work of injecting html into the response, occurs in the override of the Write() method.
For more on IIS7 Extensibility, check out my next two posts and this article, An End-to-End Extensibility Example for IIS7 Developers
Alright, so that’s everything I did in my talk. Thanks again for attending if you did. I’m having the time of my life down here in Australia. I love the scenery, the wildlife, the beaches and the activities but most of all, the warm hospitality all the Aussies have shown me.
Comments