# IPmonitor Remote Agent (PowerShell) # Agent ID: default-agent | Targets: 275 | Generated: 2026-05-03 09:20:31 $AgentID = "default-agent" $ServerURL = "https://ipmonitor.us/api/ping-results" $LogFile = Join-Path $PSScriptRoot "ipmonitor-agent.log" $StatusFile = Join-Path $PSScriptRoot "status.json" $IPs = @( "10.9.10.149", "10.9.10.158", "10.9.11.21", "10.9.11.48", "10.9.11.49", "10.9.11.56", "10.9.150.50", "10.9.150.51", "10.9.150.53", "10.9.150.54", "10.9.150.55", "10.9.150.56", "10.9.150.57", "10.9.150.59", "10.9.150.67", "10.9.150.68", "10.9.150.78", "10.9.150.80", "10.9.150.81", "10.9.150.82", "10.9.150.83", "10.9.21.10", "10.9.21.100", "10.9.21.101", "10.9.21.102", "10.9.21.103", "10.9.21.104", "10.9.21.105", "10.9.21.106", "10.9.21.107", "10.9.21.108", "10.9.21.109", "10.9.21.11", "10.9.21.110", "10.9.21.111", "10.9.21.112", "10.9.21.113", "10.9.21.114", "10.9.21.115", "10.9.21.116", "10.9.21.117", "10.9.21.118", "10.9.21.119", "10.9.21.12", "10.9.21.120", "10.9.21.121", "10.9.21.122", "10.9.21.123", "10.9.21.124", "10.9.21.125", "10.9.21.126", "10.9.21.127", "10.9.21.128", "10.9.21.129", "10.9.21.13", "10.9.21.130", "10.9.21.131", "10.9.21.132", "10.9.21.133", "10.9.21.134", "10.9.21.135", "10.9.21.136", "10.9.21.137", "10.9.21.138", "10.9.21.139", "10.9.21.14", "10.9.21.140", "10.9.21.141", "10.9.21.142", "10.9.21.143", "10.9.21.144", "10.9.21.145", "10.9.21.146", "10.9.21.147", "10.9.21.148", "10.9.21.149", "10.9.21.15", "10.9.21.150", "10.9.21.151", "10.9.21.152", "10.9.21.153", "10.9.21.154", "10.9.21.155", "10.9.21.156", "10.9.21.157", "10.9.21.158", "10.9.21.159", "10.9.21.16", "10.9.21.160", "10.9.21.161", "10.9.21.162", "10.9.21.163", "10.9.21.164", "10.9.21.165", "10.9.21.166", "10.9.21.167", "10.9.21.168", "10.9.21.169", "10.9.21.17", "10.9.21.170", "10.9.21.171", "10.9.21.172", "10.9.21.173", "10.9.21.174", "10.9.21.175", "10.9.21.176", "10.9.21.177", "10.9.21.178", "10.9.21.179", "10.9.21.18", "10.9.21.180", "10.9.21.181", "10.9.21.182", "10.9.21.183", "10.9.21.184", "10.9.21.185", "10.9.21.186", "10.9.21.187", "10.9.21.188", "10.9.21.189", "10.9.21.19", "10.9.21.190", "10.9.21.191", "10.9.21.192", "10.9.21.193", "10.9.21.194", "10.9.21.195", "10.9.21.196", "10.9.21.197", "10.9.21.198", "10.9.21.199", "10.9.21.2", "10.9.21.20", "10.9.21.200", "10.9.21.201", "10.9.21.202", "10.9.21.203", "10.9.21.204", "10.9.21.205", "10.9.21.206", "10.9.21.207", "10.9.21.208", "10.9.21.209", "10.9.21.21", "10.9.21.210", "10.9.21.211", "10.9.21.212", "10.9.21.213", "10.9.21.214", "10.9.21.215", "10.9.21.216", "10.9.21.217", "10.9.21.218", "10.9.21.219", "10.9.21.22", "10.9.21.220", "10.9.21.221", "10.9.21.222", "10.9.21.223", "10.9.21.224", "10.9.21.225", "10.9.21.226", "10.9.21.227", "10.9.21.228", "10.9.21.229", "10.9.21.23", "10.9.21.230", "10.9.21.231", "10.9.21.232", "10.9.21.233", "10.9.21.234", "10.9.21.235", "10.9.21.236", "10.9.21.237", "10.9.21.238", "10.9.21.239", "10.9.21.24", "10.9.21.240", "10.9.21.241", "10.9.21.242", "10.9.21.243", "10.9.21.244", "10.9.21.245", "10.9.21.246", "10.9.21.247", "10.9.21.248", "10.9.21.249", "10.9.21.25", "10.9.21.250", "10.9.21.251", "10.9.21.252", "10.9.21.253", "10.9.21.254", "10.9.21.26", "10.9.21.27", "10.9.21.28", "10.9.21.29", "10.9.21.3", "10.9.21.30", "10.9.21.31", "10.9.21.32", "10.9.21.33", "10.9.21.34", "10.9.21.35", "10.9.21.36", "10.9.21.37", "10.9.21.38", "10.9.21.39", "10.9.21.4", "10.9.21.40", "10.9.21.41", "10.9.21.42", "10.9.21.43", "10.9.21.44", "10.9.21.45", "10.9.21.46", "10.9.21.47", "10.9.21.48", "10.9.21.49", "10.9.21.5", "10.9.21.50", "10.9.21.51", "10.9.21.52", "10.9.21.53", "10.9.21.54", "10.9.21.55", "10.9.21.56", "10.9.21.57", "10.9.21.58", "10.9.21.59", "10.9.21.6", "10.9.21.60", "10.9.21.61", "10.9.21.62", "10.9.21.63", "10.9.21.64", "10.9.21.65", "10.9.21.66", "10.9.21.67", "10.9.21.68", "10.9.21.69", "10.9.21.7", "10.9.21.70", "10.9.21.71", "10.9.21.72", "10.9.21.73", "10.9.21.74", "10.9.21.75", "10.9.21.76", "10.9.21.77", "10.9.21.78", "10.9.21.79", "10.9.21.8", "10.9.21.80", "10.9.21.81", "10.9.21.82", "10.9.21.83", "10.9.21.84", "10.9.21.85", "10.9.21.86", "10.9.21.87", "10.9.21.88", "10.9.21.89", "10.9.21.9", "10.9.21.90", "10.9.21.91", "10.9.21.92", "10.9.21.93", "10.9.21.94", "10.9.21.95", "10.9.21.96", "10.9.21.97", "10.9.21.98", "10.9.21.99", "192.168.99.5" ) function Write-Log($msg) { $line = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $msg" Write-Host $line Add-Content -Path $LogFile -Value $line } # Detect corporate proxy $proxy = [System.Net.WebRequest]::GetSystemWebProxy().GetProxy($ServerURL) $proxyStr = "none" $proxyParams = @{} if ($proxy -and $proxy.AbsoluteUri -ne $ServerURL) { $proxyParams = @{ Proxy = $proxy; ProxyUseDefaultCredentials = $true } $proxyStr = $proxy.Authority Write-Log "[INFO] Proxy detected: $proxy" } else { Write-Log "[INFO] Direct connection" } # --- 1. Connectivity test at startup --- $baseURL = $ServerURL -replace '/api/ping-results','' Write-Log "[TEST] Checking connectivity to $baseURL ..." try { $r = Invoke-WebRequest -Uri "$baseURL/api/status" -Method GET -TimeoutSec 5 -UseBasicParsing @proxyParams Write-Log "[TEST] Server reachable - OK ($($r.StatusCode))" } catch { Write-Log "[WARNING] Server not reachable: $($_.Exception.Message) - will continue anyway" } $AgentVersion = "v2.0" $SleepSec = 15 Write-Host "" Write-Host "===== IPmonitor Remote Agent $AgentVersion =====" -ForegroundColor Cyan Write-Host " Agent: $AgentID" -ForegroundColor White Write-Host " Targets: $($IPs.Count)" -ForegroundColor White Write-Host " Server: $ServerURL" -ForegroundColor White Write-Host " Proxy: $proxyStr" -ForegroundColor White Write-Host " Sleep: ${SleepSec}s between cycles" -ForegroundColor White Write-Host " Threads: $MaxConcurrent concurrent pings" -ForegroundColor White Write-Host "============================================" -ForegroundColor Cyan Write-Host "" Write-Log "[START] Agent=$AgentVersion AgentID=$AgentID Targets=$($IPs.Count) Server=$ServerURL Sleep=${SleepSec}s" # --- 2. Cycle counter + uptime --- $startTime = Get-Date $cycleNum = 0 function Send-Batch($buf, $label) { $json = (@{ agent_id = $AgentID; timestamp = (Get-Date).ToString("o"); results = $buf } | ConvertTo-Json -Depth 5) $delays = @(0, 2, 5) $lastErr = $null $postOk = $false $latMs = 0 # --- 3. Retry with backoff --- for ($attempt = 0; $attempt -lt 3; $attempt++) { if ($attempt -gt 0) { Write-Log "[RETRY] Attempt $($attempt+1)/3 after $($delays[$attempt])s delay..." Start-Sleep -Seconds $delays[$attempt] } try { $sw = [System.Diagnostics.Stopwatch]::StartNew() Invoke-RestMethod -Uri $ServerURL -Method POST -Body $json -ContentType "application/json" -TimeoutSec 10 @proxyParams $sw.Stop() $latMs = $sw.ElapsedMilliseconds $postOk = $true break } catch { $lastErr = $_.Exception.Message } } if ($postOk) { $up = ($buf | Where-Object { $_.status -eq "UP" }).Count Write-Log "[OK] $label Batch=$($buf.Count) UP=$up DN=$($buf.Count - $up) (${latMs}ms)" } else { Write-Log "[FAIL] $label $lastErr" } return @{ ok = $postOk; latency = $latMs } } $batch = 5 $MaxConcurrent = 15 while ($true) { $cycleNum++ $elapsed = (Get-Date) - $startTime $uptimeStr = "{0}h{1:D2}m" -f [int][math]::Floor($elapsed.TotalHours), $elapsed.Minutes # --- 4. Healthcheck before each cycle (HTTP via proxy) --- try { $sw = [System.Diagnostics.Stopwatch]::StartNew() Invoke-WebRequest -Uri "$baseURL/api/status" -Method GET -TimeoutSec 5 -UseBasicParsing @proxyParams | Out-Null $sw.Stop() Write-Log "[HC] Server reachable ($($sw.ElapsedMilliseconds)ms) Proxy=$proxyStr" } catch { Write-Log "[HC] Server UNREACHABLE ($($_.Exception.Message)) Proxy=$proxyStr" } # --- Parallel ping sweep using runspace pool --- $pool = [RunspaceFactory]::CreateRunspacePool(1, $MaxConcurrent) $pool.Open() $jobs = [System.Collections.ArrayList]::new() $pingScript = { param($ip) try { $r = Test-Connection -ComputerName $ip -Count 1 -ErrorAction SilentlyContinue if ($r) { return @{ ip = $ip; status = "UP"; latency_ms = $r.ResponseTime } } } catch {} return @{ ip = $ip; status = "DOWN"; latency_ms = 0 } } foreach ($ip in $IPs) { $ps = [PowerShell]::Create().AddScript($pingScript).AddArgument($ip) $ps.RunspacePool = $pool [void]$jobs.Add(@{ Pipe = $ps; Handle = $ps.BeginInvoke() }) } # Collect first-pass results $allResults = @() foreach ($j in $jobs) { $r = $j.Pipe.EndInvoke($j.Handle) $j.Pipe.Dispose() if ($r) { $allResults += $r } } $pool.Close(); $pool.Dispose() # Re-verify DOWNs to avoid false negatives $downs = $allResults | Where-Object { $_.status -eq "DOWN" } if ($downs.Count -gt 0) { Write-Log "[RECHECK] Re-verifying $($downs.Count) DOWNs..." $pool2 = [RunspaceFactory]::CreateRunspacePool(1, $MaxConcurrent) $pool2.Open() $jobs2 = [System.Collections.ArrayList]::new() foreach ($d in $downs) { $ps = [PowerShell]::Create().AddScript($pingScript).AddArgument($d.ip) $ps.RunspacePool = $pool2 [void]$jobs2.Add(@{ Pipe = $ps; Handle = $ps.BeginInvoke(); OrigIP = $d.ip }) } $flipped = 0 foreach ($j in $jobs2) { $r = $j.Pipe.EndInvoke($j.Handle) $j.Pipe.Dispose() if ($r -and $r.status -eq "UP") { $orig = $allResults | Where-Object { $_.ip -eq $j.OrigIP } | Select-Object -First 1 if ($orig) { $orig.status = "UP"; $orig.latency_ms = $r.latency_ms } $flipped++ } } $pool2.Close(); $pool2.Dispose() if ($flipped -gt 0) { Write-Log "[RECHECK] $flipped IPs flipped DOWN->UP" } } # Send results in batches $buf = @() $total = 0; $totalUp = 0 $lastPostOk = $false; $lastPostLat = 0 foreach ($r in $allResults) { $buf += $r if ($buf.Count -ge $batch) { $up = ($buf | Where-Object { $_.status -eq "UP" }).Count $total += $buf.Count; $totalUp += $up $res = Send-Batch $buf "Progress=$total/$($IPs.Count)" $lastPostOk = $res.ok; $lastPostLat = $res.latency $buf = @() } } # send remaining if ($buf.Count -gt 0) { $up = ($buf | Where-Object { $_.status -eq "UP" }).Count $total += $buf.Count; $totalUp += $up $res = Send-Batch $buf "Progress=$total/$($IPs.Count)" $lastPostOk = $res.ok; $lastPostLat = $res.latency } # --- 5. Status file --- $postStatus = if ($lastPostOk) { "OK" } else { "FAIL" } @{ cycle = $cycleNum running_since = $startTime.ToString("o") last_post_status = $postStatus last_post_time = (Get-Date).ToString("o") up = $totalUp down = $total - $totalUp total = $total proxy = $proxyStr } | ConvertTo-Json | Set-Content -Path $StatusFile -Encoding UTF8 # --- 6. Visual summary --- $postLabel = if ($lastPostOk) { "POST OK (${lastPostLat}ms)" } else { "POST FAIL" } $summary = "[#$cycleNum $uptimeStr] UP:$totalUp DN:$($total - $totalUp) | $postLabel | Proxy: $proxyStr | Next ${SleepSec}s" Write-Host "" Write-Host $summary -ForegroundColor Cyan Write-Host "" Write-Log "[CYCLE] $summary" Start-Sleep -Seconds $SleepSec }