PowerShell IIS Log Parser: Analyze Web Traffic Patterns

PowerShell IIS Log Parser: Analyze Web Traffic Patterns

PowerShell Tips Editor 5 min read
PowerShell IIS Log Parser: Analyze Web Traffic Patterns

Ad-Hoc IIS Log Analysis with PowerShell

Log Parser Studio is powerful but requires a GUI, a separate installation, and SQL-like query syntax that not every sysadmin has memorized. PowerShell’s Import-Csv, Group-Object, and Sort-Object cmdlets can parse IIS W3C log files with custom groupings, aggregate slow requests, and export findings in under 30 lines of interactive code. When you need a quick answer about what happened on a production IIS server last Tuesday, a PowerShell one-liner against the log file is often faster than opening any dedicated tool.

Quick Answer

Strip comment lines starting with # from the log file, import it with Import-Csv -Delimiter ' ' using the fields line as a header, then pipe through Group-Object, Sort-Object, and Select-Object to answer any traffic question in seconds.

Parsing W3C Log Format with Import-Csv and Custom Delimiters

IIS W3C logs use space-delimited columns with a #Fields: comment line that lists the column names. Import-Csv cannot use those headers directly because the # prefix and the inline location confuse the parser. The correct approach is to extract the header line, strip the prefix, then feed the cleaned header as the -Header parameter.

$logFile = 'C:\inetpub\logs\LogFiles\W3SVC1\u_ex250501.log'

# Read all lines
$lines = Get-Content -Path $logFile

# Extract the column names from the #Fields: line
$headerLine = ($lines | Where-Object { $_ -match '^#Fields:' }) -replace '^#Fields:\s*', ''
$headers    = $headerLine -split ' '

# Filter out ALL comment lines, then parse
$dataLines  = $lines | Where-Object { $_ -notmatch '^#' }
$logData    = $dataLines | ConvertFrom-Csv -Delimiter ' ' -Header $headers

Write-Host "Loaded $($logData.Count) log entries"
Loaded 284631 log entries

Using ConvertFrom-Csv on the already-filtered string array is more reliable than piping Import-Csv directly against the file when comment lines are present.

Stripping Comment Lines Starting with #

IIS log files contain multiple comment blocks — a file header at the top plus an additional comment block whenever the IIS worker process recycles during the log period. Both must be stripped. The Where-Object { $_ -notmatch '^#' } filter handles all of them in a single pass regardless of how many recycles occurred.

Top 10 Most Requested URLs with Group-Object

Once the log is parsed into objects, answering traffic questions is pipeline composition. The cs-uri-stem field contains the URL path without the query string — perfect for grouping pages by hit count.

$top10Urls = $logData |
    Group-Object 'cs-uri-stem' |
    Sort-Object Count -Descending |
    Select-Object -First 10 |
    Select-Object @{N='URL'; E={$_.Name}}, Count

$top10Urls | Format-Table -AutoSize
URL                              Count
---                              -----
/api/health                      48210
/assets/app.js                   31045
/assets/styles.css               28990
/login                           19884
/dashboard                       15221
/api/users                       12003
/favicon.ico                      9811
/api/sessions                     8734
/logout                           4322
/admin                            1044

HTTP Status Code Distribution with Group-Object

The sc-status field holds the HTTP response code. Grouping on it reveals the ratio of successful requests to client errors (4xx) and server errors (5xx) — a quick proxy for application health at any point in time.

$statusDist = $logData |
    Group-Object 'sc-status' |
    Sort-Object Count -Descending |
    Select-Object @{N='Status'; E={$_.Name}},
                  Count,
                  @{N='Percent'; E={ '{0:P1}' -f ($_.Count / $logData.Count) }}

$statusDist | Format-Table -AutoSize

Finding Slowest Requests with Sort-Object on time-taken

The time-taken field records request duration. On some IIS versions it is in milliseconds; on others it is in seconds. Check a few values against known-slow endpoints to confirm the scale before setting thresholds. Cast to [int] for numeric sorting.

$slowRequests = $logData |
    Where-Object { [int]$_.'time-taken' -gt 5000 } |
    Sort-Object { [int]$_.'time-taken' } -Descending |
    Select-Object -First 20 |
    Select-Object 'date', 'time', 'cs-uri-stem', 'cs-uri-query',
                  @{N='TimeTakenMs'; E={[int]$_.'time-taken'}},
                  'sc-status', 'c-ip'

$slowRequests | Format-Table -AutoSize

Exporting Summary Report to CSV and HTML

For sharing findings or archiving analysis, export the summary objects to CSV and HTML. Combine the top-URL and status-distribution tables into a single HTML report using multiple ConvertTo-Html fragments joined together.

$top10Urls    | Export-Csv -Path 'C:\Reports\iis-top-urls.csv'    -NoTypeInformation
$statusDist   | Export-Csv -Path 'C:\Reports\iis-status-dist.csv' -NoTypeInformation

$htmlBody  = $top10Urls  | ConvertTo-Html -Fragment -PreContent '<h2>Top 10 URLs</h2>'
$htmlBody += $statusDist | ConvertTo-Html -Fragment -PreContent '<h2>Status Distribution</h2>'
$htmlBody += $slowRequests | ConvertTo-Html -Fragment -PreContent '<h2>Top 20 Slow Requests</h2>'

ConvertTo-Html -Head '<style>body{font-family:Arial}table{border-collapse:collapse}td,th{border:1px solid #ccc;padding:4px 8px}</style>' `
               -Body $htmlBody -Title 'IIS Traffic Analysis' |
    Out-File -FilePath 'C:\Reports\iis-analysis.html' -Encoding UTF8

Common Errors

  • Column headers in W3C logs have a # prefix — strip it before Import-Csv. The #Fields: line itself starts with a hash, and the field names are space-separated after the colon. Failing to strip the prefix results in the first column being named #Fields:date instead of date, which silently breaks all downstream references.
  • time-taken is in milliseconds on some IIS versions and seconds on others. IIS 7.0 and earlier logged in seconds. IIS 7.5+ logs in milliseconds. If your slow-request threshold seems wrong, check a request you know took several seconds and verify the logged value matches expectations before setting production thresholds.

Related Cmdlets / See Also

Wrapping Up

Parsing IIS W3C logs with ConvertFrom-Csv, Group-Object, and Sort-Object gives you on-demand traffic analysis without any additional tooling. Once the comment-stripping and header-extraction boilerplate is in place, answering any traffic question is a matter of adding one more pipeline stage.

Send-Item -To