Friday, January 31, 2014

Automated installation and configuration of IIS Advanced logging module using Powershell

Recently I had a chance to work on a web application which is hosted in the AWS environment. Throughout the development life cycle we have built an automated deployment process using Powershell. The automated build and deploy process takes care of spinning up new EC2 instances using Windows 2012 base AMI and installs both runtime requirements and application itself.

During this process, I had a chance to build few automation modules in Powershell. I am going to share some snippets of the modules in upcoming posts which I found not so straight forward to build. So that it would be useful to someone who is trying perform the same task.
In this post I am going to cover the automated installation and configuration of IIS Advanced logging module.

Why Advanced Logging module?
Since all of our web servers are behind Elastic Load Balancers (ELB), the remote host address is always set to the internal IP address of the load balancer. ELB puts the actual client IP address is in the X-Forwarded-For request header. The stock standard IIS HTTP logging module doesn't have access to these custom headers therefore we chose to use Advanced Logging module to capture and log additional customer headers.

We only need Advanced Logging module for IIS 7, IIS 7.5 and IIS 8.0. In IIS 8.5 (i.e.: In Windows 2012 R2) the HTTP logging has native support to capture custom fields as part of stock standard HTTP logging.

It was pretty straight forward to install and configure manually but not so easy through Powershell because installation and configuration not fully supported through Powershell alone I had to use appcmd.exe as well.

The Goal
  1. Install Advanced Logging module
  2. Create custom logging folder and assign permissions 
  3. Disable stock standard IIS logging
  4. Add additional headers like X-Forwarded-For, X-Forwarded-Proto to the available logging fields
  5. Disable server level Advanced logging
  6. Enable Advanced logging
  7. Configure custom log directory at server level and default site level 
  8. Configure Advanced Logging by site on the server
Lets dive into detail.....

1. Install Advanced Logging module
msiexec.exe /i C:\data\AdvancedLogging64.msi /passive /log C:\Data\advancedlogging.log
Start-Sleep -Seconds 10
2. Create custom logging folder and assign permissions
   
Give Read & Write access to IIS_IUSERS group (I found out about this in the hard way)
#grant permission to IIS_IUSERS group to the log directory
$Command = "icacls C:\logs /grant BUILTIN\IIS_IUSRS:(OI)(CI)RXMW"
cmd.exe /c $Command
3. Disable stock standard IIS logging
Import-Module WebAdministration 

# Disables http logging module
Set-WebConfigurationProperty -Filter system.webServer/httpLogging -PSPath machine/webroot/apphost -Name dontlog -Value true
4. Add additional headers like X-Forwarded-For, X-Forwarded-Proto to the available logging fields
# Adds AWS ELB aware X-Forwarded-For logging field
Add-WebConfiguration "system.webServer/advancedLogging/server/fields" -value @{id="X-Forwarded-For";sourceName="X-Forwarded-For";sourceType="RequestHeader";logHeaderName="X-Forwarded-For";category="Default";loggingDataType="TypeLPCSTR"}

# Adds AWS ELB aware X-Forwarded-Proto logging field
Add-WebConfiguration "system.webServer/advancedLogging/server/fields" -value @{id="X-Forwarded-Proto";sourceName="X-Forwarded-Proto";sourceType="RequestHeader";logHeaderName="X-Forwarded-Proto";category="Default";loggingDataType="TypeLPCSTR"} 
5. Disable default server level Advanced logging
# Disables the default advanced logging config
Set-WebConfigurationProperty -Filter "system.webServer/advancedLogging/server/logDefinitions/logDefinition[@baseFileName='%COMPUTERNAME%-Server']" -name enabled -value false
6. Enable Advanced logging
   
By default the Advanced Logging is disabled.
# Enable Advanced Logging
Set-WebConfigurationProperty -Filter system.webServer/advancedLogging/server -PSPath machine/webroot/apphost -Name enabled -Value true
7. Configure custom log directory at server level and default site level
# Set log directory at server level
Set-WebConfigurationProperty -Filter system.applicationHost/advancedLogging/serverLogs -PSPath machine/webroot/apphost -Name directory -Value $logDirectory

# Set log directory at site default level
Set-WebConfigurationProperty -Filter system.applicationHost/sites/siteDefaults/advancedLogging -PSPath machine/webroot/apphost -Name directory -Value $logDirectory
8. Configure Advanced Logging by site on the server
function AdvancedLogging-GenerateAppCmdScriptToConfigureAndRun()
{
    param([string] $site) 

    #Get current powershell execution folder
    $currentLocation = Get-Location

    #Create an empty bat which will be populated with appcmd instructions
    $stream = [System.IO.StreamWriter] "$currentLocation\$site.bat"

    #Create site specific log definition
    $stream.WriteLine("C:\windows\system32\inetsrv\appcmd.exe set config `"$site`" -section:system.webServer/advancedLogging/server /+`"logDefinitions.[baseFileName='$site',enabled='True',logRollOption='Schedule',schedule='Daily',publishLogEvent='False']`" /commit:apphost")

    #Get all available fields for logging
    $availableFields = Get-WebConfiguration "system.webServer/advancedLogging/server/fields"

    #Add appcmd instruction to add all the selected fields above to be logged as part of the logging
    #The below section can be extended to filter out any unwanted fields
    foreach ($item in $availableFields.Collection) 
    {
        $stream.WriteLine("C:\windows\system32\inetsrv\appcmd.exe set config `"$site`" -section:system.webServer/advancedLogging/server /+`"logDefinitions.[baseFileName='$site'].selectedFields.[id='$($item.id)',logHeaderName='$($item.logHeaderName)']`" /commit:apphost")
    }

    $stream.close()

    # execute the batch file create to configure the site specific Advanced Logging
    Start-Process -FilePath $currentLocation\$site.bat
    Start-Sleep -Seconds 10
}
#Call the above method by passing in the IIS site names
AdvancedLogging-GenerateAppCmdScriptToConfigureAndRun 'foo.com'

And that’s it :).

I found following two posts to be helpful in building the above.

http://forums.iis.net/t/1193633.aspx?Configure+Advanced+Logging+through+powershell
http://forums.iis.net/t/1161699.aspx?IIS+Advanced+Logging+1+0+Feedback

Comments and suggestions are welcome.

3 comments:

Anonymous said...

Excellent article. Really helped with configuring some roll out scripts! The only thing you might want to add is that you need intiate the WebAdministration module for this to work by doing:
import-module WebAdministration

Danny Hadley said...

Hi, I am working on adding some custom log fields to my app servers (we have them sitting behind a load balancer and need the original client IP address to come in as a http header). I was able to add the fields in the IIS manager GUI without installing the advanced logging module, but I am having a hard time finding the commands needed to automate this process. See image below:

http://i61.tinypic.com/2zppa1y.png

Antony Francis said...

This blog post is only relevant upto windows 2012. From windows 2012 r2 onwards, it has built in support for custom http headers as you mentioned. I still have to figure out how to automate for windows 2012 r2.