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,274 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Windows LAPS Prometheus Metrics Exporter
|
||||
.DESCRIPTION
|
||||
Prometheus exporter for Windows Local Administrator Password Solution (LAPS).
|
||||
Monitors password expiration, rotation status, coverage across OUs, and
|
||||
stale passwords. Exports metrics for windows_exporter textfile collector
|
||||
or standalone HTTP listener.
|
||||
.PARAMETER TextFile
|
||||
Write to windows_exporter textfile directory
|
||||
.PARAMETER OutFile
|
||||
Write metrics to a specific file path
|
||||
.PARAMETER Install
|
||||
Create a scheduled task for automatic collection
|
||||
.PARAMETER Listen
|
||||
Start HTTP listener on specified address:port
|
||||
.PARAMETER Interval
|
||||
Collection interval in seconds for scheduled task (default: 300)
|
||||
.NOTES
|
||||
Author: Phil Connor
|
||||
Contact: contact@mylinux.work
|
||||
Website: https://mylinux.work
|
||||
License: MIT
|
||||
Version: 1.0
|
||||
#>
|
||||
|
||||
param(
|
||||
[switch]$TextFile,
|
||||
[string]$OutFile = "",
|
||||
[switch]$Install,
|
||||
[string]$Listen = "",
|
||||
[int]$Interval = 300
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
$Version = "1.0"
|
||||
$TextfileDir = "C:\ProgramData\node_exporter"
|
||||
$MetricPrefix = "windows_laps"
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
function Get-PrometheusEscape {
|
||||
param([string]$Value)
|
||||
$Value -replace '\\', '\\' -replace '"', '\"' -replace "`n", ''
|
||||
}
|
||||
|
||||
function Write-MetricHeader {
|
||||
param([string]$Name, [string]$Help, [string]$Type)
|
||||
"# HELP $Name $Help"
|
||||
"# TYPE $Name $Type"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# METRIC GENERATION
|
||||
# ============================================================================
|
||||
|
||||
function Get-LapsMetrics {
|
||||
$startTime = Get-Date
|
||||
$metrics = [System.Collections.ArrayList]::new()
|
||||
|
||||
# Check if LAPS module is available
|
||||
$lapsAvailable = $false
|
||||
if (Get-Module -ListAvailable -Name "LAPS" -ErrorAction SilentlyContinue) {
|
||||
Import-Module LAPS -ErrorAction SilentlyContinue
|
||||
$lapsAvailable = $true
|
||||
}
|
||||
# Also check for Windows LAPS (new in Server 2022+)
|
||||
$windowsLaps = $false
|
||||
if (Get-Command "Get-LapsAADPassword" -ErrorAction SilentlyContinue) {
|
||||
$windowsLaps = $true
|
||||
$lapsAvailable = $true
|
||||
}
|
||||
|
||||
$up = if ($lapsAvailable) { 1 } else { 0 }
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_up" "LAPS exporter status (1=up, 0=down)" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_up $up")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_exporter_info" "Exporter version information" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_exporter_info{version=`"$Version`"} 1")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
if ($up -eq 0) {
|
||||
$endTime = Get-Date
|
||||
$duration = [math]::Round(($endTime - $startTime).TotalSeconds, 2)
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_exporter_duration_seconds" "Script execution time" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_exporter_duration_seconds $duration")
|
||||
return ($metrics -join "`n")
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# COMPUTER ACCOUNT LAPS STATUS
|
||||
# ========================================================================
|
||||
|
||||
try {
|
||||
$computers = Get-ADComputer -Filter {Enabled -eq $true} -Properties `
|
||||
"ms-Mcs-AdmPwdExpirationTime", "ms-Mcs-AdmPwd", "DistinguishedName", "OperatingSystem" `
|
||||
-ErrorAction Stop
|
||||
|
||||
$totalComputers = @($computers).Count
|
||||
$lapsManaged = 0
|
||||
$lapsUnmanaged = 0
|
||||
$passwordExpired = 0
|
||||
$passwordExpiringSoon = 0
|
||||
$now = Get-Date
|
||||
$soonThreshold = $now.AddDays(7)
|
||||
|
||||
foreach ($computer in $computers) {
|
||||
$expTime = $computer."ms-Mcs-AdmPwdExpirationTime"
|
||||
|
||||
if ($expTime -and $expTime -gt 0) {
|
||||
$lapsManaged++
|
||||
$expDate = [DateTime]::FromFileTime($expTime)
|
||||
|
||||
if ($expDate -lt $now) {
|
||||
$passwordExpired++
|
||||
} elseif ($expDate -lt $soonThreshold) {
|
||||
$passwordExpiringSoon++
|
||||
}
|
||||
} else {
|
||||
$lapsUnmanaged++
|
||||
}
|
||||
}
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_computers_total" "Total enabled computer accounts" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_computers_total $totalComputers")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_computers_managed" "Computers with LAPS password set" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_computers_managed $lapsManaged")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_computers_unmanaged" "Computers without LAPS password" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_computers_unmanaged $lapsUnmanaged")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_passwords_expired" "Computers with expired LAPS passwords" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_passwords_expired $passwordExpired")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_passwords_expiring_soon" "Computers with LAPS passwords expiring within 7 days" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_passwords_expiring_soon $passwordExpiringSoon")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
# Coverage percentage
|
||||
$coverage = if ($totalComputers -gt 0) { [math]::Round(($lapsManaged / $totalComputers) * 100, 1) } else { 0 }
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_coverage_percent" "LAPS coverage percentage" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_coverage_percent $coverage")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
# ====================================================================
|
||||
# PER-OU BREAKDOWN
|
||||
# ====================================================================
|
||||
|
||||
$ouStats = @{}
|
||||
foreach ($computer in $computers) {
|
||||
$dn = $computer.DistinguishedName
|
||||
$ouParts = ($dn -split ",") | Where-Object { $_ -match "^OU=" }
|
||||
$ou = if ($ouParts.Count -gt 0) { ($ouParts[0] -replace "^OU=", "") } else { "Default" }
|
||||
|
||||
if (-not $ouStats.ContainsKey($ou)) {
|
||||
$ouStats[$ou] = @{ Total = 0; Managed = 0 }
|
||||
}
|
||||
$ouStats[$ou].Total++
|
||||
|
||||
$expTime = $computer."ms-Mcs-AdmPwdExpirationTime"
|
||||
if ($expTime -and $expTime -gt 0) {
|
||||
$ouStats[$ou].Managed++
|
||||
}
|
||||
}
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_ou_computers_total" "Computers per OU" "gauge"))
|
||||
foreach ($ou in $ouStats.Keys) {
|
||||
$ouName = Get-PrometheusEscape $ou
|
||||
[void]$metrics.Add("${MetricPrefix}_ou_computers_total{ou=`"$ouName`"} $($ouStats[$ou].Total)")
|
||||
}
|
||||
[void]$metrics.Add("")
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_ou_computers_managed" "LAPS-managed computers per OU" "gauge"))
|
||||
foreach ($ou in $ouStats.Keys) {
|
||||
$ouName = Get-PrometheusEscape $ou
|
||||
[void]$metrics.Add("${MetricPrefix}_ou_computers_managed{ou=`"$ouName`"} $($ouStats[$ou].Managed)")
|
||||
}
|
||||
[void]$metrics.Add("")
|
||||
|
||||
} catch {
|
||||
[void]$metrics.Add("# ERROR: Failed to query AD computers: $_")
|
||||
[void]$metrics.Add("")
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# LAPS GPO STATUS
|
||||
# ========================================================================
|
||||
|
||||
try {
|
||||
$gpos = Get-GPO -All -ErrorAction Stop | Where-Object {
|
||||
$_.DisplayName -match "LAPS|Local Administrator Password"
|
||||
}
|
||||
|
||||
$lapsGpos = @($gpos).Count
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_gpo_count" "LAPS-related GPOs" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_gpo_count $lapsGpos")
|
||||
[void]$metrics.Add("")
|
||||
} catch {
|
||||
# GPO query may not be available on all systems
|
||||
}
|
||||
|
||||
# ========================================================================
|
||||
# EXPORTER RUNTIME
|
||||
# ========================================================================
|
||||
|
||||
$endTime = Get-Date
|
||||
$duration = [math]::Round(($endTime - $startTime).TotalSeconds, 2)
|
||||
$timestamp = [math]::Round((Get-Date -UFormat %s), 0)
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_exporter_duration_seconds" "Script execution time" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_exporter_duration_seconds $duration")
|
||||
[void]$metrics.Add("")
|
||||
|
||||
[void]$metrics.Add((Write-MetricHeader "${MetricPrefix}_exporter_last_run_timestamp" "Unix timestamp of last run" "gauge"))
|
||||
[void]$metrics.Add("${MetricPrefix}_exporter_last_run_timestamp $timestamp")
|
||||
|
||||
return ($metrics -join "`n")
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# OUTPUT MODES
|
||||
# ============================================================================
|
||||
|
||||
if ($Install) {
|
||||
$scriptPath = $MyInvocation.MyCommand.Path
|
||||
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" -TextFile"
|
||||
$trigger = New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Seconds $Interval) -Once -At (Get-Date)
|
||||
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
||||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
|
||||
Register-ScheduledTask -TaskName "LAPS Metrics Exporter" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force
|
||||
Write-Host "Scheduled task 'LAPS Metrics Exporter' created (interval: ${Interval}s)"
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($Listen -ne "") {
|
||||
$port = $Listen -replace '.*:', ''
|
||||
if (-not $port) { $port = "9199" }
|
||||
$listener = [System.Net.HttpListener]::new()
|
||||
$listener.Prefixes.Add("http://+:$port/")
|
||||
$listener.Start()
|
||||
Write-Host "LAPS exporter listening on port $port..."
|
||||
|
||||
while ($listener.IsListening) {
|
||||
$context = $listener.GetContext()
|
||||
$response = $context.Response
|
||||
$output = Get-LapsMetrics
|
||||
$buffer = [System.Text.Encoding]::UTF8.GetBytes($output)
|
||||
$response.ContentType = "text/plain; version=0.0.4"
|
||||
$response.ContentLength64 = $buffer.Length
|
||||
$response.OutputStream.Write($buffer, 0, $buffer.Length)
|
||||
$response.Close()
|
||||
}
|
||||
} elseif ($TextFile -or $OutFile -ne "") {
|
||||
$outputPath = if ($OutFile -ne "") { $OutFile } else { Join-Path $TextfileDir "laps.prom" }
|
||||
$outputDir = Split-Path $outputPath -Parent
|
||||
if (-not (Test-Path $outputDir)) { New-Item -ItemType Directory -Path $outputDir -Force | Out-Null }
|
||||
$tempFile = Join-Path $outputDir ".laps-metrics.tmp"
|
||||
$metricsOutput = Get-LapsMetrics
|
||||
$metricsOutput | Out-File -FilePath $tempFile -Encoding utf8 -NoNewline
|
||||
Move-Item -Path $tempFile -Destination $outputPath -Force
|
||||
Write-Host "Metrics written to $outputPath"
|
||||
} else {
|
||||
Get-LapsMetrics
|
||||
}
|
||||
Reference in New Issue
Block a user