# ping-test-sekundengenau-https-parallel-v4.ps1 # Robuste Langzeitmessung fuer Windows 11: # FRITZ!Box + DG-Gateway + 8.8.8.8 per ICMP # Google + Cloudflare per HTTPS # Alle fuenf Tests eines Messzeitpunkts werden gestartet, bevor Ergebnisse gesammelt werden. # HTTPS wird ueber curl.exe mit hartem Timeout ausgefuehrt, damit kein einzelner # HTTPS-Test das gesamte Protokoll blockieren kann. $ErrorActionPreference = "Continue" $FritzIP = "192.XXX.XXX.X" $DGGateway = "100.108.0.1" $InternetIP = "8.8.8.8" $GoogleUrl = "https://www.google.com/generate_204" $CloudflareUrl = "https://www.cloudflare.com/" $PingTimeoutMs = 900 $HttpTimeoutSeconds = 0.9 $HttpWaitMs = 1100 $IntervalMs = 1000 $OutputFile = Join-Path ([Environment]::GetFolderPath("MyDocuments")) "ping-test-sekundengenau-https-parallel-v4.csv" # curl.exe finden $curlCommand = Get-Command curl.exe -ErrorAction SilentlyContinue if ($null -ne $curlCommand) { $CurlPath = $curlCommand.Source } else { $CurlPath = Join-Path $env:SystemRoot "System32\curl.exe" } if (-not (Test-Path $CurlPath)) { Write-Host "" Write-Host "FEHLER: curl.exe wurde nicht gefunden." Write-Host "Das Skript wird beendet." exit 1 } Write-Host "" Write-Host "Verwendetes curl:" Write-Host $CurlPath Write-Host "" function Start-IcmpTest { param([string]$Target) $ping = [System.Net.NetworkInformation.Ping]::new() try { $task = $ping.SendPingAsync($Target, $PingTimeoutMs) return [pscustomobject]@{ Task = $task Ping = $ping Error = "" } } catch { $ping.Dispose() return [pscustomobject]@{ Task = $null Ping = $null Error = $_.Exception.Message } } } function Start-CurlTest { param([string]$Url) try { $psi = [System.Diagnostics.ProcessStartInfo]::new() $psi.FileName = $CurlPath # Keine Datei schreiben; nur HTTP-Statuscode ausgeben. # --max-time begrenzt den kompletten Request. # --connect-timeout begrenzt den Verbindungsaufbau. $psi.Arguments = "-sS --max-time $HttpTimeoutSeconds --connect-timeout $HttpTimeoutSeconds -o NUL -w `"HTTP_CODE:%{http_code}`" `"$Url`"" $psi.UseShellExecute = $false $psi.CreateNoWindow = $true $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $process = [System.Diagnostics.Process]::new() $process.StartInfo = $psi if (-not $process.Start()) { $process.Dispose() return [pscustomobject]@{ Process = $null Url = $Url Error = "curl.exe konnte nicht gestartet werden." } } return [pscustomobject]@{ Process = $process Url = $Url Error = "" } } catch { return [pscustomobject]@{ Process = $null Url = $Url Error = $_.Exception.Message } } } function Get-IcmpResult { param($Item) try { if ($null -eq $Item.Task) { return [pscustomobject]@{ Status = "ERROR" Ms = "" Error = $Item.Error } } $reply = $Item.Task.GetAwaiter().GetResult() if ($reply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success) { return [pscustomobject]@{ Status = "OK" Ms = [int]$reply.RoundtripTime Error = "" } } return [pscustomobject]@{ Status = "TIMEOUT" Ms = "" Error = [string]$reply.Status } } catch { return [pscustomobject]@{ Status = "ERROR" Ms = "" Error = $_.Exception.Message } } finally { if ($null -ne $Item.Ping) { $Item.Ping.Dispose() } } } function Get-CurlResult { param($Item) if ($null -eq $Item.Process) { return [pscustomobject]@{ Status = "ERROR" Ms = "" Code = "" Error = $Item.Error } } $process = $Item.Process $sw = [System.Diagnostics.Stopwatch]::StartNew() try { # Der curl-Prozess wurde bereits parallel zu den anderen Tests gestartet. $finished = $process.WaitForExit($HttpWaitMs) if (-not $finished) { try { $process.Kill() } catch {} try { $process.WaitForExit(200) } catch {} $sw.Stop() return [pscustomobject]@{ Status = "TIMEOUT" Ms = "" Code = "" Error = "curl timeout" } } $sw.Stop() $stdout = $process.StandardOutput.ReadToEnd().Trim() $stderr = $process.StandardError.ReadToEnd().Trim() $exitCode = $process.ExitCode $httpCode = "" if ($stdout -match "HTTP_CODE:(\d{3})") { $httpCode = [int]$Matches[1] } if ($exitCode -eq 0 -and $httpCode -ge 200 -and $httpCode -lt 400) { return [pscustomobject]@{ Status = "OK" Ms = [int]$sw.ElapsedMilliseconds Code = $httpCode Error = "" } } # curl exit code 28 = operation timeout if ($exitCode -eq 28) { return [pscustomobject]@{ Status = "TIMEOUT" Ms = "" Code = $httpCode Error = "curl timeout" } } $err = $stderr if ([string]::IsNullOrWhiteSpace($err)) { $err = "curl exit code $exitCode" } return [pscustomobject]@{ Status = "ERROR" Ms = "" Code = $httpCode Error = $err } } catch { try { $process.Kill() } catch {} return [pscustomobject]@{ Status = "ERROR" Ms = "" Code = "" Error = $_.Exception.Message } } finally { $process.Dispose() } } # CSV-Datei neu anlegen. "Timestamp;Fritz_Status;Fritz_ms;DG_Status;DG_ms;Internet_Status;Internet_ms;GoogleHTTPS_Status;GoogleHTTPS_ms;GoogleHTTPCode;GoogleHTTPError;CloudflareHTTPS_Status;CloudflareHTTPS_ms;CloudflareHTTPCode;CloudflareHTTPError" | Set-Content -Path $OutputFile -Encoding utf8 Write-Host "========================================================" Write-Host " Sekunden- und Parallelmessung v4 gestartet" Write-Host "========================================================" Write-Host "Ausgabe:" Write-Host $OutputFile Write-Host "" Write-Host "HTTPS: curl.exe mit 0,9 s Timeout" Write-Host "Abbruch jederzeit mit STRG+C." Write-Host "" $nextTick = [System.Diagnostics.Stopwatch]::GetTimestamp() $frequency = [double][System.Diagnostics.Stopwatch]::Frequency $intervalTicks = [int64]($frequency * ($IntervalMs / 1000.0)) while ($true) { $timestamp = Get-Date # ALLE fuenf Tests zuerst starten. $fritzTask = Start-IcmpTest $FritzIP $dgTask = Start-IcmpTest $DGGateway $internetTask = Start-IcmpTest $InternetIP $googleTask = Start-CurlTest $GoogleUrl $cloudflareTask = Start-CurlTest $CloudflareUrl # Erst jetzt Ergebnisse einsammeln. $fritz = Get-IcmpResult $fritzTask $dg = Get-IcmpResult $dgTask $internet = Get-IcmpResult $internetTask $google = Get-CurlResult $googleTask $cloudflare = Get-CurlResult $cloudflareTask $ts = $timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff") $googleError = ([string]$google.Error).Replace(";", ",") $cloudflareError = ([string]$cloudflare.Error).Replace(";", ",") $line = "{0};{1};{2};{3};{4};{5};{6};{7};{8};{9};{10};{11};{12};{13};{14}" -f ` $ts, $fritz.Status, $fritz.Ms, $dg.Status, $dg.Ms, $internet.Status, $internet.Ms, $google.Status, $google.Ms, $google.Code, $googleError, $cloudflare.Status, $cloudflare.Ms, $cloudflare.Code, $cloudflareError Add-Content -Path $OutputFile -Value $line -Encoding utf8 Write-Host $line # Stabiler 1-Sekunden-Takt. $nextTick += $intervalTicks $nowTick = [System.Diagnostics.Stopwatch]::GetTimestamp() $remainingTicks = $nextTick - $nowTick if ($remainingTicks -gt 0) { $sleepMs = [int](($remainingTicks * 1000.0) / $frequency) if ($sleepMs -gt 0) { Start-Sleep -Milliseconds $sleepMs } } else { # Kein Aufholen durch Sofortmessungen. $nextTick = [System.Diagnostics.Stopwatch]::GetTimestamp() } }