a1a17e81a1
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.
310 lines
15 KiB
PowerShell
310 lines
15 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Windows DHCP Server Prometheus Exporter
|
|
.DESCRIPTION
|
|
Prometheus exporter for Windows DHCP Server - scope utilization, active leases,
|
|
free addresses, reservations, failover status, DHCP statistics, and lease
|
|
expiration tracking via PowerShell. Uses the built-in DhcpServer module.
|
|
.PARAMETER Mode
|
|
Output mode: 'stdout' (default), 'textfile', or 'http'
|
|
.PARAMETER Port
|
|
HTTP port for http mode (default: 9534)
|
|
.PARAMETER TextfileDir
|
|
Directory for textfile collector output (default: C:\ProgramData\node_exporter)
|
|
.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_dhcp_up
|
|
- windows_dhcp_exporter_info{version}
|
|
|
|
Scope:
|
|
- windows_dhcp_scope_pool_total{scope_id,name}
|
|
- windows_dhcp_scope_pool_in_use{scope_id,name}
|
|
- windows_dhcp_scope_pool_free{scope_id,name}
|
|
- windows_dhcp_scope_pool_reserved{scope_id,name}
|
|
- windows_dhcp_scope_pool_utilization{scope_id,name}
|
|
- windows_dhcp_scope_pool_pending{scope_id,name}
|
|
- windows_dhcp_scope_state{scope_id,name,state}
|
|
- windows_dhcp_scope_leases_expiring{scope_id,name,within}
|
|
- windows_dhcp_scope_lease_longest_seconds{scope_id,name}
|
|
- windows_dhcp_scope_lease_shortest_seconds{scope_id,name}
|
|
|
|
Failover:
|
|
- windows_dhcp_failover_state{name,partner,mode,state}
|
|
- windows_dhcp_failover_scope_count{name}
|
|
|
|
Statistics:
|
|
- windows_dhcp_discovers_total
|
|
- windows_dhcp_offers_total
|
|
- windows_dhcp_requests_total
|
|
- windows_dhcp_acks_total
|
|
- windows_dhcp_naks_total
|
|
- windows_dhcp_declines_total
|
|
- windows_dhcp_releases_total
|
|
|
|
Totals:
|
|
- windows_dhcp_scopes_total
|
|
- windows_dhcp_leases_active_total
|
|
- windows_dhcp_exporter_duration_seconds
|
|
- windows_dhcp_exporter_last_run_timestamp
|
|
#>
|
|
|
|
param(
|
|
[ValidateSet('stdout','textfile','http')]
|
|
[string]$Mode = 'stdout',
|
|
|
|
[int]$Port = 9534,
|
|
|
|
[string]$TextfileDir = 'C:\ProgramData\node_exporter',
|
|
|
|
[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 'WindowsDhcpExporter' -Action $action -Trigger $trigger `
|
|
-Settings $settings -RunLevel Highest -User 'SYSTEM' -Force
|
|
Write-Host 'Scheduled task "WindowsDhcpExporter" 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"
|
|
}
|
|
|
|
# ============================================================================
|
|
# METRIC COLLECTION
|
|
# ============================================================================
|
|
|
|
function Get-DhcpMetrics {
|
|
$startTime = Get-Date
|
|
$metrics = [System.Collections.Generic.List[string]]::new()
|
|
|
|
# --- Exporter status ---
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_up' 'gauge' 'Exporter status (1=up, 0=down)'))
|
|
|
|
try {
|
|
Import-Module DhcpServer -ErrorAction Stop
|
|
$metrics.Add('windows_dhcp_up 1')
|
|
} catch {
|
|
$metrics.Add('windows_dhcp_up 0')
|
|
return ($metrics -join "`n")
|
|
}
|
|
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_exporter_info' 'gauge' 'Exporter version information'))
|
|
$metrics.Add("windows_dhcp_exporter_info{version=`"$Version`"} 1")
|
|
|
|
# --- Scope metrics ---
|
|
try {
|
|
$scopes = Get-DhcpServerv4Scope -ErrorAction Stop
|
|
$totalScopes = ($scopes | Measure-Object).Count
|
|
$totalActive = 0
|
|
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_pool_total' 'gauge' 'Total addresses in the DHCP scope'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_pool_in_use' 'gauge' 'Currently leased addresses'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_pool_free' 'gauge' 'Available addresses in the scope'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_pool_reserved' 'gauge' 'Number of reservations in the scope'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_pool_utilization' 'gauge' 'Scope utilization percentage'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_pool_pending' 'gauge' 'Pending offers'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_state' 'gauge' 'Scope state (1=current state)'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_leases_expiring' 'gauge' 'Leases expiring within threshold'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_lease_longest_seconds' 'gauge' 'Remaining time on the longest lease'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scope_lease_shortest_seconds' 'gauge' 'Remaining time on the shortest lease'))
|
|
|
|
foreach ($scope in $scopes) {
|
|
$scopeId = $scope.ScopeId.ToString()
|
|
$scopeName = Get-PrometheusEscape $scope.Name
|
|
|
|
try {
|
|
$stats = Get-DhcpServerv4ScopeStatistics -ScopeId $scope.ScopeId -ErrorAction Stop
|
|
$total = $stats.AddressesAvailable + $stats.AddressesInUse
|
|
$inUse = $stats.AddressesInUse
|
|
$free = $stats.AddressesAvailable
|
|
$pending = $stats.PendingOffers
|
|
$reserved = (Get-DhcpServerv4Reservation -ScopeId $scope.ScopeId -ErrorAction SilentlyContinue | Measure-Object).Count
|
|
$util = if ($total -gt 0) { [math]::Round(($inUse / $total) * 100, 2) } else { 0 }
|
|
|
|
$totalActive += $inUse
|
|
|
|
$metrics.Add("windows_dhcp_scope_pool_total{scope_id=`"$scopeId`",name=`"$scopeName`"} $total")
|
|
$metrics.Add("windows_dhcp_scope_pool_in_use{scope_id=`"$scopeId`",name=`"$scopeName`"} $inUse")
|
|
$metrics.Add("windows_dhcp_scope_pool_free{scope_id=`"$scopeId`",name=`"$scopeName`"} $free")
|
|
$metrics.Add("windows_dhcp_scope_pool_reserved{scope_id=`"$scopeId`",name=`"$scopeName`"} $reserved")
|
|
$metrics.Add("windows_dhcp_scope_pool_utilization{scope_id=`"$scopeId`",name=`"$scopeName`"} $util")
|
|
$metrics.Add("windows_dhcp_scope_pool_pending{scope_id=`"$scopeId`",name=`"$scopeName`"} $pending")
|
|
} catch {
|
|
# Skip scope stats if unavailable
|
|
}
|
|
|
|
# Scope state
|
|
$stateStr = $scope.State.ToString()
|
|
$metrics.Add("windows_dhcp_scope_state{scope_id=`"$scopeId`",name=`"$scopeName`",state=`"$stateStr`"} 1")
|
|
|
|
# Lease expiration tracking
|
|
try {
|
|
$leases = Get-DhcpServerv4Lease -ScopeId $scope.ScopeId -ErrorAction Stop |
|
|
Where-Object { $_.AddressState -match 'Active' -and $_.LeaseExpiryTime }
|
|
$now = Get-Date
|
|
$exp1h = 0; $exp4h = 0; $exp24h = 0
|
|
$longest = 0; $shortest = [int]::MaxValue
|
|
|
|
foreach ($lease in $leases) {
|
|
$remaining = ($lease.LeaseExpiryTime - $now).TotalSeconds
|
|
if ($remaining -gt 0) {
|
|
if ($remaining -le 3600) { $exp1h++ }
|
|
if ($remaining -le 14400) { $exp4h++ }
|
|
if ($remaining -le 86400) { $exp24h++ }
|
|
if ($remaining -gt $longest) { $longest = [math]::Round($remaining) }
|
|
if ($remaining -lt $shortest) { $shortest = [math]::Round($remaining) }
|
|
}
|
|
}
|
|
if ($shortest -eq [int]::MaxValue) { $shortest = 0 }
|
|
|
|
$metrics.Add("windows_dhcp_scope_leases_expiring{scope_id=`"$scopeId`",name=`"$scopeName`",within=`"1h`"} $exp1h")
|
|
$metrics.Add("windows_dhcp_scope_leases_expiring{scope_id=`"$scopeId`",name=`"$scopeName`",within=`"4h`"} $exp4h")
|
|
$metrics.Add("windows_dhcp_scope_leases_expiring{scope_id=`"$scopeId`",name=`"$scopeName`",within=`"24h`"} $exp24h")
|
|
$metrics.Add("windows_dhcp_scope_lease_longest_seconds{scope_id=`"$scopeId`",name=`"$scopeName`"} $longest")
|
|
$metrics.Add("windows_dhcp_scope_lease_shortest_seconds{scope_id=`"$scopeId`",name=`"$scopeName`"} $shortest")
|
|
} catch {
|
|
# Skip lease details if unavailable
|
|
}
|
|
}
|
|
|
|
# Scope totals
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_scopes_total' 'gauge' 'Total number of configured scopes'))
|
|
$metrics.Add("windows_dhcp_scopes_total $totalScopes")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_leases_active_total' 'gauge' 'Total active leases across all scopes'))
|
|
$metrics.Add("windows_dhcp_leases_active_total $totalActive")
|
|
} catch {
|
|
# DHCP scope enumeration failed
|
|
}
|
|
|
|
# --- Failover ---
|
|
try {
|
|
$failovers = Get-DhcpServerv4Failover -ErrorAction Stop
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_failover_state' 'gauge' 'Failover relationship state (1=Normal)'))
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_failover_scope_count' 'gauge' 'Number of scopes in the failover relationship'))
|
|
|
|
foreach ($fo in $failovers) {
|
|
$foName = Get-PrometheusEscape $fo.Name
|
|
$partner = Get-PrometheusEscape $fo.PartnerServer
|
|
$foMode = $fo.Mode.ToString()
|
|
$foState = $fo.State.ToString()
|
|
$stateVal = if ($foState -eq 'Normal') { 1 } else { 0 }
|
|
$scopeCount = ($fo.ScopeId | Measure-Object).Count
|
|
|
|
$metrics.Add("windows_dhcp_failover_state{name=`"$foName`",partner=`"$partner`",mode=`"$foMode`",state=`"$foState`"} $stateVal")
|
|
$metrics.Add("windows_dhcp_failover_scope_count{name=`"$foName`"} $scopeCount")
|
|
}
|
|
} catch {
|
|
# No failover configured - skip
|
|
}
|
|
|
|
# --- DHCP server statistics ---
|
|
try {
|
|
$stats = Get-DhcpServerv4Statistics -ErrorAction Stop
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_discovers_total' 'counter' 'Total DHCPDISCOVER packets received'))
|
|
$metrics.Add("windows_dhcp_discovers_total $($stats.Discovers)")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_offers_total' 'counter' 'Total DHCPOFFER packets sent'))
|
|
$metrics.Add("windows_dhcp_offers_total $($stats.Offers)")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_requests_total' 'counter' 'Total DHCPREQUEST packets received'))
|
|
$metrics.Add("windows_dhcp_requests_total $($stats.Requests)")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_acks_total' 'counter' 'Total DHCPACK packets sent'))
|
|
$metrics.Add("windows_dhcp_acks_total $($stats.Acks)")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_naks_total' 'counter' 'Total DHCPNAK packets sent'))
|
|
$metrics.Add("windows_dhcp_naks_total $($stats.Naks)")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_declines_total' 'counter' 'Total DHCPDECLINE packets received'))
|
|
$metrics.Add("windows_dhcp_declines_total $($stats.Declines)")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_releases_total' 'counter' 'Total DHCPRELEASE packets received'))
|
|
$metrics.Add("windows_dhcp_releases_total $($stats.Releases)")
|
|
} catch {
|
|
# Statistics unavailable
|
|
}
|
|
|
|
# --- Duration and timestamp ---
|
|
$duration = [math]::Round(((Get-Date) - $startTime).TotalSeconds, 2)
|
|
$timestamp = [math]::Round((Get-Date -UFormat %s), 0)
|
|
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_exporter_duration_seconds' 'gauge' 'Script execution time'))
|
|
$metrics.Add("windows_dhcp_exporter_duration_seconds $duration")
|
|
$metrics.AddRange([string[]](Write-MetricHeader 'windows_dhcp_exporter_last_run_timestamp' 'gauge' 'Unix timestamp of last successful run'))
|
|
$metrics.Add("windows_dhcp_exporter_last_run_timestamp $timestamp")
|
|
|
|
return ($metrics -join "`n")
|
|
}
|
|
|
|
# ============================================================================
|
|
# OUTPUT
|
|
# ============================================================================
|
|
|
|
switch ($Mode) {
|
|
'stdout' {
|
|
Get-DhcpMetrics
|
|
}
|
|
'textfile' {
|
|
if (-not (Test-Path $TextfileDir)) { New-Item -ItemType Directory -Path $TextfileDir -Force | Out-Null }
|
|
$tempFile = Join-Path $TextfileDir "windows-dhcp-metrics.tmp"
|
|
$finalFile = Join-Path $TextfileDir "windows-dhcp-metrics.prom"
|
|
Get-DhcpMetrics | 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-DhcpMetrics
|
|
$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()
|
|
}
|
|
}
|
|
}
|