Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Windows IIS Log Prometheus Exporter
|
||||
.DESCRIPTION
|
||||
Prometheus exporter for IIS log files - parses W3C Extended log format to
|
||||
extract request counts, status code distribution, response times, bandwidth,
|
||||
top URIs, and error rates per IIS site. Exports metrics as Prometheus text.
|
||||
.PARAMETER Mode
|
||||
Output mode: 'stdout' (default), 'textfile', or 'http'
|
||||
.PARAMETER Port
|
||||
HTTP port for http mode (default: 9542)
|
||||
.PARAMETER TextfileDir
|
||||
Directory for textfile collector output (default: C:\ProgramData\node_exporter)
|
||||
.PARAMETER LogDir
|
||||
IIS log directory (default: C:\inetpub\logs\LogFiles)
|
||||
.PARAMETER WindowMinutes
|
||||
Parse logs from the last N minutes (default: 5)
|
||||
.PARAMETER InstallScheduledTask
|
||||
Switch to create a scheduled task for automatic execution
|
||||
.PARAMETER TaskIntervalMinutes
|
||||
Interval in minutes for the scheduled task (default: 2)
|
||||
.NOTES
|
||||
Author: Phil Connor
|
||||
Contact: contact@mylinux.work
|
||||
Website: https://mylinux.work
|
||||
License: MIT
|
||||
Version: 1.0
|
||||
|
||||
Metrics Exported:
|
||||
Core:
|
||||
- windows_iis_log_up
|
||||
- windows_iis_log_exporter_info{version}
|
||||
|
||||
Requests:
|
||||
- windows_iis_log_requests_total{site,method,status}
|
||||
- windows_iis_log_requests_by_status_class{site,class}
|
||||
- windows_iis_log_bytes_sent_total{site}
|
||||
- windows_iis_log_bytes_received_total{site}
|
||||
|
||||
Response Times:
|
||||
- windows_iis_log_time_taken_avg_ms{site}
|
||||
- windows_iis_log_time_taken_max_ms{site}
|
||||
- windows_iis_log_time_taken_p95_ms{site}
|
||||
|
||||
Errors:
|
||||
- windows_iis_log_errors_total{site}
|
||||
- windows_iis_log_error_rate{site}
|
||||
|
||||
Sites:
|
||||
- windows_iis_log_sites_total
|
||||
- windows_iis_log_log_file_age_seconds{site}
|
||||
|
||||
Exporter:
|
||||
- windows_iis_log_exporter_duration_seconds
|
||||
- windows_iis_log_exporter_last_run_timestamp
|
||||
#>
|
||||
|
||||
param(
|
||||
[ValidateSet('stdout','textfile','http')]
|
||||
[string]$Mode = 'stdout',
|
||||
|
||||
[int]$Port = 9542,
|
||||
|
||||
[string]$TextfileDir = 'C:\ProgramData\node_exporter',
|
||||
|
||||
[string]$LogDir = 'C:\inetpub\logs\LogFiles',
|
||||
|
||||
[int]$WindowMinutes = 5,
|
||||
|
||||
[switch]$InstallScheduledTask,
|
||||
|
||||
[int]$TaskIntervalMinutes = 2
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'SilentlyContinue'
|
||||
$Version = '1.0'
|
||||
|
||||
# ============================================================================
|
||||
# SCHEDULED TASK INSTALLER
|
||||
# ============================================================================
|
||||
|
||||
if ($InstallScheduledTask) {
|
||||
$scriptPath = $MyInvocation.MyCommand.Path
|
||||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
|
||||
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" -Mode textfile"
|
||||
$trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes $TaskIntervalMinutes) `
|
||||
-Once -At (Get-Date)
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
||||
-StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 5)
|
||||
Register-ScheduledTask -TaskName 'WindowsIISLogExporter' -Action $action -Trigger $trigger `
|
||||
-Settings $settings -RunLevel Highest -User 'SYSTEM' -Force
|
||||
Write-Host 'Scheduled task "WindowsIISLogExporter" installed successfully.'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
function Get-PrometheusEscape {
|
||||
param([string]$Value)
|
||||
$Value -replace '\\', '\\\\' -replace '"', '\"' -replace "`n", '\n'
|
||||
}
|
||||
|
||||
function Write-MetricHeader {
|
||||
param([string]$Name, [string]$Type, [string]$Help)
|
||||
"# HELP $Name $Help"
|
||||
"# TYPE $Name $Type"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# LOG PARSING
|
||||
# ============================================================================
|
||||
|
||||
function Parse-IISLogFile {
|
||||
param([string]$FilePath, [datetime]$Since)
|
||||
|
||||
$results = @{
|
||||
Requests = @{}
|
||||
StatusClasses = @{}
|
||||
BytesSent = 0
|
||||
BytesReceived = 0
|
||||
TimeTaken = [System.Collections.Generic.List[int]]::new()
|
||||
Errors = 0
|
||||
Total = 0
|
||||
}
|
||||
|
||||
$fieldMap = @{}
|
||||
|
||||
Get-Content $FilePath -Tail 10000 | ForEach-Object {
|
||||
$line = $_
|
||||
if ($line.StartsWith('#Fields:')) {
|
||||
$fields = $line.Substring(9).Trim() -split '\s+'
|
||||
for ($i = 0; $i -lt $fields.Count; $i++) {
|
||||
$fieldMap[$fields[$i]] = $i
|
||||
}
|
||||
return
|
||||
}
|
||||
if ($line.StartsWith('#') -or [string]::IsNullOrWhiteSpace($line)) { return }
|
||||
if ($fieldMap.Count -eq 0) { return }
|
||||
|
||||
$parts = $line -split '\s+'
|
||||
|
||||
# Parse date/time
|
||||
$dateIdx = $fieldMap['date']
|
||||
$timeIdx = $fieldMap['time']
|
||||
if ($null -ne $dateIdx -and $null -ne $timeIdx -and $parts.Count -gt [Math]::Max($dateIdx,$timeIdx)) {
|
||||
try {
|
||||
$entryTime = [datetime]::ParseExact("$($parts[$dateIdx]) $($parts[$timeIdx])", 'yyyy-MM-dd HH:mm:ss', $null)
|
||||
if ($entryTime -lt $Since) { return }
|
||||
} catch { return }
|
||||
}
|
||||
|
||||
$results.Total++
|
||||
|
||||
# Method
|
||||
$methodIdx = $fieldMap['cs-method']
|
||||
$method = if ($null -ne $methodIdx -and $parts.Count -gt $methodIdx) { $parts[$methodIdx] } else { 'UNKNOWN' }
|
||||
|
||||
# Status
|
||||
$statusIdx = $fieldMap['sc-status']
|
||||
$status = if ($null -ne $statusIdx -and $parts.Count -gt $statusIdx) { $parts[$statusIdx] } else { '0' }
|
||||
|
||||
$key = "${method}_${status}"
|
||||
if (-not $results.Requests.ContainsKey($key)) { $results.Requests[$key] = 0 }
|
||||
$results.Requests[$key]++
|
||||
|
||||
# Status class
|
||||
$class = "${status}".Substring(0,1) + "xx"
|
||||
if (-not $results.StatusClasses.ContainsKey($class)) { $results.StatusClasses[$class] = 0 }
|
||||
$results.StatusClasses[$class]++
|
||||
|
||||
# Errors (4xx and 5xx)
|
||||
if ($status -match '^[45]') { $results.Errors++ }
|
||||
|
||||
# Bytes
|
||||
$sentIdx = $fieldMap['sc-bytes']
|
||||
if ($null -ne $sentIdx -and $parts.Count -gt $sentIdx -and $parts[$sentIdx] -match '^\d+$') {
|
||||
$results.BytesSent += [long]$parts[$sentIdx]
|
||||
}
|
||||
$recvIdx = $fieldMap['cs-bytes']
|
||||
if ($null -ne $recvIdx -and $parts.Count -gt $recvIdx -and $parts[$recvIdx] -match '^\d+$') {
|
||||
$results.BytesReceived += [long]$parts[$recvIdx]
|
||||
}
|
||||
|
||||
# Time taken
|
||||
$ttIdx = $fieldMap['time-taken']
|
||||
if ($null -ne $ttIdx -and $parts.Count -gt $ttIdx -and $parts[$ttIdx] -match '^\d+$') {
|
||||
$results.TimeTaken.Add([int]$parts[$ttIdx])
|
||||
}
|
||||
}
|
||||
|
||||
return $results
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# METRIC COLLECTION
|
||||
# ============================================================================
|
||||
|
||||
function Get-IISLogMetrics {
|
||||
$startTime = Get-Date
|
||||
$metrics = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_up' 'gauge' 'Exporter status (1=up, 0=down)'))
|
||||
|
||||
if (-not (Test-Path $LogDir)) {
|
||||
$metrics.Add('windows_iis_log_up 0')
|
||||
return ($metrics -join "`n")
|
||||
}
|
||||
|
||||
$metrics.Add('windows_iis_log_up 1')
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_exporter_info' 'gauge' 'Exporter version'))
|
||||
$metrics.Add("windows_iis_log_exporter_info{version=`"$Version`"} 1")
|
||||
|
||||
$since = (Get-Date).AddMinutes(-$WindowMinutes)
|
||||
$siteDirs = Get-ChildItem -Path $LogDir -Directory -ErrorAction SilentlyContinue
|
||||
$siteCount = 0
|
||||
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_requests_total' 'gauge' 'Requests by method and status'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_requests_by_status_class' 'gauge' 'Requests by status class'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_bytes_sent_total' 'gauge' 'Total bytes sent'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_bytes_received_total' 'gauge' 'Total bytes received'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_time_taken_avg_ms' 'gauge' 'Average response time in ms'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_time_taken_max_ms' 'gauge' 'Max response time in ms'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_time_taken_p95_ms' 'gauge' '95th percentile response time in ms'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_errors_total' 'gauge' 'Total 4xx and 5xx errors'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_error_rate' 'gauge' 'Error rate (errors/total)'))
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_log_file_age_seconds' 'gauge' 'Seconds since log file was last modified'))
|
||||
|
||||
foreach ($siteDir in $siteDirs) {
|
||||
$siteName = Get-PrometheusEscape $siteDir.Name
|
||||
$logFiles = Get-ChildItem -Path $siteDir.FullName -Filter "*.log" -ErrorAction SilentlyContinue |
|
||||
Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
|
||||
if (-not $logFiles) { continue }
|
||||
$siteCount++
|
||||
|
||||
$logFile = $logFiles[0]
|
||||
$fileAge = [math]::Round(((Get-Date) - $logFile.LastWriteTime).TotalSeconds)
|
||||
$metrics.Add("windows_iis_log_log_file_age_seconds{site=`"$siteName`"} $fileAge")
|
||||
|
||||
$parsed = Parse-IISLogFile -FilePath $logFile.FullName -Since $since
|
||||
if ($null -eq $parsed -or $parsed.Total -eq 0) { continue }
|
||||
|
||||
# Requests by method+status
|
||||
foreach ($key in $parsed.Requests.Keys) {
|
||||
$parts = $key -split '_'
|
||||
$method = $parts[0]
|
||||
$status = $parts[1]
|
||||
$count = $parsed.Requests[$key]
|
||||
$metrics.Add("windows_iis_log_requests_total{site=`"$siteName`",method=`"$method`",status=`"$status`"} $count")
|
||||
}
|
||||
|
||||
# Status classes
|
||||
foreach ($class in $parsed.StatusClasses.Keys) {
|
||||
$count = $parsed.StatusClasses[$class]
|
||||
$metrics.Add("windows_iis_log_requests_by_status_class{site=`"$siteName`",class=`"$class`"} $count")
|
||||
}
|
||||
|
||||
# Bytes
|
||||
$metrics.Add("windows_iis_log_bytes_sent_total{site=`"$siteName`"} $($parsed.BytesSent)")
|
||||
$metrics.Add("windows_iis_log_bytes_received_total{site=`"$siteName`"} $($parsed.BytesReceived)")
|
||||
|
||||
# Response times
|
||||
if ($parsed.TimeTaken.Count -gt 0) {
|
||||
$sorted = $parsed.TimeTaken | Sort-Object
|
||||
$avg = [math]::Round(($sorted | Measure-Object -Average).Average)
|
||||
$max = $sorted[-1]
|
||||
$p95idx = [math]::Floor($sorted.Count * 0.95)
|
||||
$p95 = $sorted[[math]::Min($p95idx, $sorted.Count - 1)]
|
||||
$metrics.Add("windows_iis_log_time_taken_avg_ms{site=`"$siteName`"} $avg")
|
||||
$metrics.Add("windows_iis_log_time_taken_max_ms{site=`"$siteName`"} $max")
|
||||
$metrics.Add("windows_iis_log_time_taken_p95_ms{site=`"$siteName`"} $p95")
|
||||
}
|
||||
|
||||
# Errors
|
||||
$metrics.Add("windows_iis_log_errors_total{site=`"$siteName`"} $($parsed.Errors)")
|
||||
$errorRate = if ($parsed.Total -gt 0) { [math]::Round($parsed.Errors / $parsed.Total, 4) } else { 0 }
|
||||
$metrics.Add("windows_iis_log_error_rate{site=`"$siteName`"} $errorRate")
|
||||
}
|
||||
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_sites_total' 'gauge' 'Total IIS sites with logs'))
|
||||
$metrics.Add("windows_iis_log_sites_total $siteCount")
|
||||
|
||||
# Duration
|
||||
$duration = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 2)
|
||||
$timestamp = [math]::Round((Get-Date -UFormat %s), 0)
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_exporter_duration_seconds' 'gauge' 'Script execution time'))
|
||||
$metrics.Add("windows_iis_log_exporter_duration_seconds $duration")
|
||||
$metrics.AddRange([string[]](Write-MetricHeader 'windows_iis_log_exporter_last_run_timestamp' 'gauge' 'Unix timestamp of last run'))
|
||||
$metrics.Add("windows_iis_log_exporter_last_run_timestamp $timestamp")
|
||||
|
||||
return ($metrics -join "`n")
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# OUTPUT
|
||||
# ============================================================================
|
||||
|
||||
switch ($Mode) {
|
||||
'stdout' {
|
||||
Get-IISLogMetrics
|
||||
}
|
||||
'textfile' {
|
||||
if (-not (Test-Path $TextfileDir)) { New-Item -ItemType Directory -Path $TextfileDir -Force | Out-Null }
|
||||
$tempFile = Join-Path $TextfileDir "windows-iis-log-metrics.tmp"
|
||||
$finalFile = Join-Path $TextfileDir "windows-iis-log-metrics.prom"
|
||||
Get-IISLogMetrics | Out-File -FilePath $tempFile -Encoding utf8 -NoNewline
|
||||
Move-Item -Path $tempFile -Destination $finalFile -Force
|
||||
Write-Host "Wrote metrics to $finalFile"
|
||||
}
|
||||
'http' {
|
||||
$prefix = "http://+:$Port/metrics/"
|
||||
$listener = [System.Net.HttpListener]::new()
|
||||
$listener.Prefixes.Add($prefix)
|
||||
$listener.Start()
|
||||
Write-Host "Listening on port $Port..."
|
||||
try {
|
||||
while ($listener.IsListening) {
|
||||
$context = $listener.GetContext()
|
||||
$response = $context.Response
|
||||
$metricsOutput = Get-IISLogMetrics
|
||||
$buffer = [System.Text.Encoding]::UTF8.GetBytes($metricsOutput)
|
||||
$response.ContentType = 'text/plain; version=0.0.4; charset=utf-8'
|
||||
$response.ContentLength64 = $buffer.Length
|
||||
$response.OutputStream.Write($buffer, 0, $buffer.Length)
|
||||
$response.OutputStream.Close()
|
||||
}
|
||||
} finally {
|
||||
$listener.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user