Getting Data from Multiple IIS Servers

Published on 29 September 2019

If you have a group of IIS webservers sitting behind a load balancer, you might want to find out the actual IP addresses that requests are coming from. With IIS you can add X-Forwarded-For as a custom IIS field, and that will allow the original IP to be logged. If you are using something like Akamai you will get the IP address of the original source, and the Akamai address. You can use this to your advantage though in pulling the information back.

So in the case of IIS where it will log the real IP, followed by a "+" and then the CDN address, you can use a regex to identify them.

    $regex = '((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\,\+))'

The above regex will look for an IPv4 address, followed by a ",+". This is what seperates the two addresses in the log file, and will also ensure you are not picking up the addresses of your webservers from the log.

You can then use get-childitem to get the log file from the server, and then

$logfiles = Get-ChildItem c:\inetpub\logs\LogFiles\W3SVC4\*.log -file | sort-object -pro lastwritetime | select-object -last 1
foreach ($logfile in $logfiles) {
    $Content = get-content $logfile
    $hits = $Content | Select-String $regex -AllMatches | Foreach-Object {$_.Matches} | Foreach-Object {$_.Groups[0].Value}
    $IPs = $hits.trimend(",+")
}

We also trim the seperator off the end of the matched string too, as we don't need those. Not that we are looking in a specific folder here (W3SVC4) as that is the folder for my particular application, and additionally, we are only finding the most recent logfile. Your milage might vary with those.

So once we have the IP addresses, we want to find the most common IP address. Luckily you don't need to start writing code to find a list of unique values, put then into an array, and then couunt the occurances of each in the original array, you can use the "group-object" command. We can then sort the list, and select the biggest 10 (the last 10 on the list):

$TopIPs = $ips | group-object | select-object Count, Name | sort-object Count | select-object -Last 10

Once you have it running on a single server, you can wrap it up with "invoke-command" and run it against all of your servers.

You can then do stuff, like compare it against WhoIs information, and out put it.

So this is how I put it all together.

Function get-MostIISRequests {
    <#
    .SYNOPSIS Gets the IP addresses with the most requests from IIS.
    .DESCRIPTION This function will connect to the servers and search the log file
    for external IP addresses. the X-Forwarded-For needs to be enabled on the 
    IIS server.
    .NOTES Author: Greg Heywood
    .PARAMETER Servers
    An array of servers to check, either names or IP addresses.
    .PARAMETER Credential
    The credentials used to connect to the servers.
    .PARAMETER date
    To check for logs with a write time of a specified date. Defaults to today.
    .EXAMPLE
    PS> Get-MostIISRequests -Servers $servers -Credential $cred -Date "19/9/2019"
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]

    param(
    [array]$Servers,
    $Credential,
    $date)


    $list = @()
    $ips = @()

    $scriptblock = {
    $regex = '((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\,\+))'
    $logfiles = Get-ChildItem c:\inetpub\logs\LogFiles\W3SVC4\*.log -file | sort-object -pro lastwritetime | select-object -last 1
    foreach ($logfile in $logfiles) {
        #Get all of the IP addresses
        $Content = get-content $logfile
        $hits = $Content | Select-String $regex -AllMatches | Foreach-Object {$_.Matches} | Foreach-Object {$_.Groups[0].Value}
        $IPs = $hits.trimend(",+")
    }
    Return $IPs
    }

    $scriptblock2 = {
    $regex = '((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\,\+))'
    $logfiles = Get-ChildItem c:\inetpub\logs\LogFiles\W3SVC4\*.log -file | where-object {$_.lastwritetime.date -eq $using:date}
    foreach ($logfile in $logfiles) {
        #Get all of the IP addresses
        $Content = get-content $logfile
        $hits = $Content | Select-String $regex -AllMatches | Foreach-Object {$_.Matches} | Foreach-Object {$_.Groups[0].Value}
        $IPs = $hits.trimend(",+")
    }
    Return $IPs
    }

    #Run the command on the servers
    If (!$date) {
    $IPs = (invoke-command -computer $servers -ScriptBlock $scriptblock -Credential $cred )
    } else {
    $date = [datetime]::parse($date)
    $IPs = (invoke-command -computer $servers -ScriptBlock $scriptblock2 -Credential $cred )
    }


    #Sort the data
    $TopIPs = $ips | group-object | select-object Count, Name | sort-object Count | select-object -Last 10

    #Check WHOIS and build object
    $list = @()
    foreach ($ip in $TopIPs) {
        $WhoIs = 'http://whois.arin.net/rest'
        $url = "$WHoIs/ip/$($ip.name)"
        $r = Invoke-RestMethod  $url
        [object]$Rec = New-object System.Object
        $Rec | Add-Member -membertype NoteProperty -Name "IP" -value $IP.Name
        $Rec | Add-Member -MemberType NoteProperty -Name "Count" -value $IP.Count
        $Rec | Add-Member -MemberType NoteProperty -Name "WhoIS" -value $r.net.name
        $list += $Rec
    }

    $list
}

You should get an output like this:

    IP              Count WhoIS
    --              ----- -----
    127.250.125.216  4456 DO-13
    104.228.46.28    4477 DO-13
    167.71.41.43     4480 DO-13
    17.53.150.247    4816 APPLE-WWNET
    127.0.0.1        4948 SPECIAL-IPV4-LOOPBACK-IANA-RESERVED
    63.229.29.159    4960 GOOGLE
    19.74.105.135    5065 109-RIPE
    ...

Hope that helps someone!

comments powered by Disqus