Using IIS7 And Application Request Routing as Load Balancer – Part 2
In part 1 I explained why I needed a load balancer that was capable of doing smart balancing decisions using more than just IPs and why I decided to use IIS7 and Application Request Routing (ARR). In this part I will give a detailed explanation of how to setup ARR on Windows Server 2008 R2 Web Server Core.
Server Core provide the following benefits over traditional installations according to Microsoft:
- Reduced maintenance
- Reduced attack surface
- Reduced management
- Less disk space required
I concluded that the above points is quite important for a load balancer, as you don’t want it to spend its resources on anything else than load balancing.
In this guide I will walk you through the steps needed to install Windows Server 2008 Web Server Core. Secondly I will show you how to create the necessary scripts to automate about 99% of the installation and configuration of ARR.
Before we dig deep, here’s a short description of the setup I used for my ARR environment:
Two Web Servers with:
- 3 websites running both SSL and normal HTTP
- Each website needed its own IP
- Each website needed its own SSL certificate
ARR Server:
- In order to avoid the “clients NAT’ed behind one IP” problem, I needed to configure ARR Server Affinity to UseCookie.
- As for the Load Balancing Algorithm I used Weighted Round Robin (you should look into the other alternatives that suite your needs)
- The ARR server is responsible for distributing traffic to two web servers mentioned above
All of the above can of course be changed in the scripts that I’ll be showing later to suite your configuration needs. Below is an illustration of the infrastructure:
Installing Windows Server 2008 R2 Web Server Core:
- Install Windows Server 2008 R2 Web – Server Core
- Set password for admin (when prompted)
- Rename server:
NETDOM renamecomputer %computername% /newname:[nameOfServer] - Restart:
shutdown /r /t 0 - Set static IP:
netsh interface ipv4 set address "Local Area Connection" static [IP] [Subnet] [Gateway] - Set Primary DNS server:
netsh interface ipv4 add dnsserver "Local Area Connection" [IP] - Set Secondary DNS server:
netsh interface ipv4 add dnsserver "Local Area Connection" [IP] index=2 - If you want the server to join a domain:
netdom join %computername% /domain:[domainname] /userd:[username] /passwordd:* - Restart:
shutdown /r /t 0 - If you want to enable Remote Desktop:
For Vista/Win7/2008 clients:
cscript %windir%\system32\SCRegEdit.wsf /ar 0
For earlier clients:
cscript %windir%\system32\SCRegEdit.wsf /cs 0
For both new and earlier clients run both of the above - If you want to allow remote MMC management (e.g. to simplify managing users etc):
netsh advfirewall firewall set rule group="Remote Administration" new enable=yes - Activate Windows:
start /w slmgr.vbs –ipk [key]
start /w slmgr.vbs –ato
Now the server is ready for ARR installation and configuration, but since the OS is a server core, creating some scripts to automate this procedure is very helpful. Next I will show you the scripts I created and used, which hopefully will make this task easier for you than for me :-) I’ve made all the files and scripts available in this zip file.
Creating script for installing SSL certificates (do this on some client computer, not the ARR Server):
Note: You can skip this section (except step 1 and 2) if you’re not using SSL.
- Create a folder called ARRInstall
- In the ARRInstall folder, create a new folder called Utils
- Download capicom_dc_sdk.msi and place MSI in the Utils folder
- Download winhttpcertcfg.msi and install with default options on a client computer
- Browse to C:\Program Files (x86)\Windows Resource Kits\Tools\ and copy winhttpcertcfg.exe into your Utils folder
- Create a folder called Certificates in your ARRInstall folder
- The above folder must contain all your certificates (both with and without private keys). For my setup this looks like this:
ARRInstall
|– Certificates
|– site1.torresdal.net.cer
|– site1.torresdal.net.pfx
|– site2.torresdal.net.cer
|– site2.torresdal.net.pfx
|– site3.torresdal.net.cer
|– site3.torresdal.net.pfx - Copy the script below, read comments in script and modify accordingly. Then save the script as _SetupCertificates.cmd in the ARRInstall folder:
@echo off
echo Installing CAPICOM...
echo.
msiexec /qn /i "%~dp0Utils\capicom_dc_sdk.msi"echo Removing existing certificate from store...
REM Example:
REM certutil -delstore Root "blog.torresdal.net"certutil -delstore Root "[certificate name]"echo Removing SSL certificate from IIS...
netsh http delete sslcert ipport=0.0.0.0:443
echo Installing certificates...
echo.
REM Example:
REM certutil -f -addstore Root "%~dp0Certificates\blog.torresdal.net.cer"certutil -f -addstore Root "%~dp0Certificates\[certificate name].cer"IF EXIST "%PROGRAMFILES%\Microsoft CAPICOM 2.1.0.2 SDK" (SET capicompath="%PROGRAMFILES%\Microsoft CAPICOM 2.1.0.2 SDK\Samples\vbs\cstore.vbs"SET cscript=%windir%\system32\cscript.exe
)
IF EXIST "%PROGRAMFILES(x86)%\Microsoft CAPICOM 2.1.0.2 SDK" (SET capicompath="%PROGRAMFILES(x86)%\Microsoft CAPICOM 2.1.0.2 SDK\Samples\vbs\cstore.vbs"SET cscript=%windir%\syswow64\cscript.exe
ECHO Setting up CAPICOM for 64 bits environment...
copy /y "%PROGRAMFILES(x86)%\Microsoft CAPICOM 2.1.0.2 SDK\Lib\X86\capicom.dll" %windir%\syswow64%windir%\syswow64\regsvr32.exe /s %windir%\syswow64\capicom.dll
)
REM Example:
REM %cscript% /nologo %capicompath% delete -l LM -subject "blog.torresdal.net" -noprompt%cscript% /nologo %capicompath% delete -l LM -subject "[certificate name]" -nopromptREM Example:
REM %cscript% /nologo %capicompath% import -l LM "%~dp0Certificates\blog.torresdal.net.pfx" "somePassword"
%cscript% /nologo %capicompath% import -l LM "%~dp0Certificates\[certificate name].pfx" "[password]"
REM Example:
REM "%~dp0Utils\winhttpcertcfg.exe" -g -c LOCAL_MACHINE\My -s blog.torresdal.net -a "IIS_IUSRS"
"%~dp0Utils\winhttpcertcfg.exe" -g -c LOCAL_MACHINE\My -s [certificate name] -a "IIS_IUSRS"
echo.
echo ============================
echo Certificates Setup finished!
echo ============================
REM pause
:end
- Your folder structure should now look like this:
ARRInstall
|– Certificates
|– site1.torresdal.net.cer
|– site1.torresdal.net.pfx
|– site2.torresdal.net.cer
|– site2.torresdal.net.pfx
|– site3.torresdal.net.cer
|– site3.torresdal.net.pfx|– Utils
|– capicom_dc_sdk.msi
|– winhttpcertcfg.exe
|– _SetupCertificates.cmd
Create script for installing and configuring ARR:
- Download the x64 installer for ARR from the ARR website: http://www.iis.net/expand/ApplicationRequestRouting
- Copy the downloaded file (ARRv2_setup_x64.EXE) into the Utils folder
- Download IIS7Util.exe which I’ve made available for you on my site. (The source code can be found at the foaf-ssl-dotnet project. Just browse code and search for “iis7util”, to make it clear where I got it from :-))
- Copy the IIS7Util.exe into the Utils folder
- Copy the script below, read comments in script and modify accordingly. Then save the script as InstallARR.cmd in the ARRInstall folder. You should then have this file structure:
ARRInstall
|– Certificates
|– site1.torresdal.net.cer
|– site1.torresdal.net.pfx
|– site2.torresdal.net.cer
|– site2.torresdal.net.pfx
|– site3.torresdal.net.cer
|– site3.torresdal.net.pfx|—Utils
|– ARRv2_setup_x64.EXE
|– capicom_dc_sdk.msi
|– IIS7Util.exe
|– winhttpcertcfg.exe
|– _SetupCertificates.cmd
|– InstallARR.cmd
Note: If you’re not using SSL, comment out this line in your script:
call %~dp0_SetupCertificates.cmd@echo off
REM =====================
REM CONFIGURATION
REM =====================
REM =============================================
REM IP's for the different sites on the LB Server
REM =============================================
SET Site1IPNLB=10.0.0.51
SET Site2IPNLB=10.0.0.52
SET Site3IPNLB=10.0.0.53
REM =============================
REM IP's for back end web servers
REM =============================
SET Site1IPWeb01=10.137.13.61
SET Site2IPWeb01=10.137.13.62
SET Site3IPWeb01=10.137.13.63
SET Site1IPWeb02=10.137.13.71
SET Site2IPWeb02=10.137.13.72
SET Site3IPWeb02=10.137.13.73
REM ==============================
REM Host names for different sites
REM ==============================
SET Site1HostName=site1.torresdal.net
SET Site2HostName=site2.torresdal.net
SET Site3HostName=site3.torresdal.net
REM ==================================
REM Name of Server Farms for each site
REM ==================================
SET ARRServerFarm1=Site1
SET ARRServerFarm2=Site2
SET ARRServerFarm3=Site3
REM ============================
REM File locations for web sites
REM ============================
SET Site1FileLoc=C:\%ARRServerFarm1%
SET Site2FileLoc=C:\%ARRServerFarm2%
SET Site3FileLoc=C:\%ARRServerFarm3%
REM ==========
REM AppCmd.exe
REM ==========
SET AppCMD=%windir%\system32\inetsrv\appcmd.exe
REM =========================
REM INSTALLATION
REM =========================
Echo Installing IIS...
start /w pkgmgr /l:log.etw /iu:IIS-WebServerRole;WAS-WindowsActivationService;WAS-ProcessModel;WAS-NetFxEnvironment;WAS-ConfigurationAPI;NetFx2-ServerCore;NetFx2-ServerCore-WOW64;IIS-ManagementService
Echo Enabling remote management of IIS...
reg add HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebManagement\Server /v EnableRemoteManagement /t REG_DWORD /d 1 /f
sc config WMSVC start= auto
Echo Installing Application Request Routing (ARR)...
net stop was /y
net stop wmsvc /y
"%~dp0utils\ARRv2_setup_x64.EXE" /Qnet start was
net start wmsvc
Echo Disable Idle Timeout on Application Pool...
%AppCMD% set apppool "DefaultAppPool" -processModel.idleTimeout:"00:00:00" /commit:apphost
%AppCMD% set config -section:system.applicationHost/applicationPools /[name='DefaultAppPool'].recycling.periodicRestart.time:"00:00:00" /commit:apphostEcho Installing certificates...
call %~dp0_SetupCertificates.cmd
Echo Adding aditional IPs for web sites...
netsh interface ipv4 add address "Local Area Connection" %Site1IPNLB% 255.255.255.0netsh interface ipv4 add address "Local Area Connection" %Site2IPNLB% 255.255.255.0netsh interface ipv4 add address "Local Area Connection" %Site3IPNLB% 255.255.255.0Echo Creating folders for web sites...
md %Site1FileLoc%
md %Site2FileLoc%
md %Site3FileLoc%
Echo Creating web sites in IIS...
%AppCMD% add site /name:%site1HostName% /bindings:"http/%Site1IPNLB%:80:,https/%Site1IPNLB%:443:" /physicalPath:"%Site1FileLoc%"
%AppCMD% add site /name:%site2HostName% /bindings:"http/%Site2IPNLB%:80:,https/%Site2IPNLB%:443:" /physicalPath:"%Site2FileLoc%"
%AppCMD% add site /name:%site3HostName% /bindings:"http/%Site3IPNLB%:80:,https/%Site3IPNLB%:443:" /physicalPath:"%Site3FileLoc%"
Echo Adding farms and servers to ARR...
%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm1%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm1%'].[address='%Site1IPWeb01%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm1%'].[address='%Site1IPWeb02%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm2%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm2%'].[address='%Site2IPWeb01%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm2%'].[address='%Site2IPWeb02%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm3%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm3%'].[address='%Site3IPWeb01%']" /commit:apphost%AppCMD% set config -section:webFarms /+"[name='%ARRServerFarm3%'].[address='%Site3IPWeb02%']" /commit:apphostREM ==============================================================================
REM ALERT!: REMEMBER THAT SSL RULES FOR EACH SITE MUST BE BEFORE THE HTTP RULES!!!
REM E.g. 1) Rules for Site1 SSL
REM 2) Rules for Site1 HTTP
REM 3) Rules for Site2 SSL
REM 4) Rules for Site2 HTTP
REM 5) etc.
REM
REM If we had switched 1 and 2, it would match on HTTP first, and would
REM have redirected to a non SSL site, resulting in SSL offloading.
REM
REM If SSL offloading is the wanted behaviour, only the HTTP rule is
REM nessesary.
REM ==============================================================================
echo Adding rewriting rules to ARR...
%AppCMD% clear config -section:system.webServer/rewrite/globalRules
REM Site1 SSL
%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm1%_SSL', patternSyntax='Wildcard',stopProcessing='True']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm1%_SSL',patternSyntax='Wildcard',stopProcessing='True'].match.url:"*" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm1%_SSL',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTPS}',pattern='on']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm1%_SSL',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTP_HOST}',pattern='*%Site1HostName%*']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm1%_SSL',patternSyntax='Wildcard',stopProcessing='True'].action.type:"Rewrite" /[name='%ARRServerFarm1%_SSL',patternSyntax='Wildcard',stopProcessing='True'].action.url:"https://%ARRServerFarm1%/{R:0}" /commit:apphost
REM Site1 HTTP
%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm1%', patternSyntax='Wildcard',stopProcessing='True']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm1%',patternSyntax='Wildcard',stopProcessing='True'].match.url:"*" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm1%',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTP_HOST}',pattern='*%Site1HostName%*']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm1%',patternSyntax='Wildcard',stopProcessing='True'].action.type:"Rewrite" /[name='%ARRServerFarm1%',patternSyntax='Wildcard',stopProcessing='True'].action.url:"http://%ARRServerFarm1%/{R:0}" /commit:apphost
REM Site2 SSL
%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm2%_SSL', patternSyntax='Wildcard',stopProcessing='True']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm2%_SSL',patternSyntax='Wildcard',stopProcessing='True'].match.url:"*" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm2%_SSL',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTPS}',pattern='on']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm2%_SSL',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTP_HOST}',pattern='*%Site2HostName%*']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm2%_SSL',patternSyntax='Wildcard',stopProcessing='True'].action.type:"Rewrite" /[name='%ARRServerFarm2%_SSL',patternSyntax='Wildcard',stopProcessing='True'].action.url:"https://%ARRServerFarm2%/{R:0}" /commit:apphost
REM Site2 HTTP
%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm2%', patternSyntax='Wildcard',stopProcessing='True']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm2%',patternSyntax='Wildcard',stopProcessing='True'].match.url:"*" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm2%',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTP_HOST}',pattern='*%Site2HostName%*']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm2%',patternSyntax='Wildcard',stopProcessing='True'].action.type:"Rewrite" /[name='%ARRServerFarm2%',patternSyntax='Wildcard',stopProcessing='True'].action.url:"http://%ARRServerFarm2%/{R:0}" /commit:apphost
REM Site3 SSL
%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm3%_SSL', patternSyntax='Wildcard',stopProcessing='True']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm3%_SSL',patternSyntax='Wildcard',stopProcessing='True'].match.url:"*" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm3%_SSL',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTPS}',pattern='on']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm3%_SSL',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTP_HOST}',pattern='*%Site3HostName%*']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm3%_SSL',patternSyntax='Wildcard',stopProcessing='True'].action.type:"Rewrite" /[name='%ARRServerFarm3%_SSL',patternSyntax='Wildcard',stopProcessing='True'].action.url:"https://%ARRServerFarm3%/{R:0}" /commit:apphost
REM Site3 HTTP
%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm3%', patternSyntax='Wildcard',stopProcessing='True']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm3%',patternSyntax='Wildcard',stopProcessing='True'].match.url:"*" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /+"[name='%ARRServerFarm3%',patternSyntax='Wildcard',stopProcessing='True'].conditions.[input='{HTTP_HOST}',pattern='*%Site3HostName%*']" /commit:apphost%AppCMD% set config -section:system.webServer/rewrite/globalRules /[name='%ARRServerFarm3%',patternSyntax='Wildcard',stopProcessing='True'].action.type:"Rewrite" /[name='%ARRServerFarm3%',patternSyntax='Wildcard',stopProcessing='True'].action.url:"http://%ARRServerFarm3%/{R:0}" /commit:apphost
REM ===============
REM Server Affinity
REM ===============
Echo Adding Server Affinity...
%AppCMD% set config -section:webFarms /[name='%ARRServerFarm1%'].applicationRequestRouting.affinity.useCookie:"True" /commit:apphost%AppCMD% set config -section:webFarms /[name='%ARRServerFarm2%'].applicationRequestRouting.affinity.useCookie:"True" /commit:apphost%AppCMD% set config -section:webFarms /[name='%ARRServerFarm3%'].applicationRequestRouting.affinity.useCookie:"True" /commit:apphostREM ========================
REM Load Balancing Algorithm
REM ========================
Echo Adding Load Balancing Algorithm...
%AppCMD% set config -section:webFarms /[name='%ARRServerFarm1%'].applicationRequestRouting.loadBalancing.algorithm:"WeightedRoundRobin" /commit:apphost%AppCMD% set config -section:webFarms /[name='%ARRServerFarm2%'].applicationRequestRouting.loadBalancing.algorithm:"WeightedRoundRobin" /commit:apphost%AppCMD% set config -section:webFarms /[name='%ARRServerFarm3%'].applicationRequestRouting.loadBalancing.algorithm:"WeightedRoundRobin" /commit:apphost
Installing Application Request Routing:
- From the ARR server connect to your client containing the script files:
net use [some drive letter]: \\clientComputerName\[drive on client]$ /user:domain\user
Example:net use r: \\myClient\c$ /user:torresdal\jon
- Make a directory on the server to copy the files to:
E.g.: md c:\temp\arr - Copy the ARRInstall folder from your client to C:\temp\arr on your server
- Run InstallARR.bat, watch for errors and wait for it to finish
- Install Remote IIS Manger on a PC that will administrate IIS and connect to the ARR server: http://www.iis.net/expand/IISManager
- In Internet Information Services Manager connected to ARR do:
- Select Sites->Site1
- Click Bindings (upper right corner)
- Click https –> Edit
- Select proper certificate
- Repeat for all other sites
- Under Sites:
- For each site that require SSL:
- Click SSL Settings
- Check Require SSL
- For each site that require SSL:
- Start IIS on ARR server:
iisreset /start - Using Internet Information Services Manager:
Stop “Default Web Site” - You’re done!
I hope this helped in making the installation and configuration of the ARR server easier, and that my scenario is applicable to others except me :-) Let me know if I can help with questions or if you find any errors.
Next time I will talk about how to avoid the ARR server being the Single Point of Failure.

