Run this from the workstation behind the proxy. It streams one slow, long-lived
HTTPS upload to this server, which never times out, and records exactly when and how the connection dies.
1. Easiest: one line, downloads and runs
Paste into cmd.exe or PowerShell in a folder you can write to:
Three files in %TEMP%\proxyprobe: a detailed .log,
a per-sample .csv, and a probe-summary.csv with one row per run.
The log captures the proxy selected, the TLS certificate actually presented (a corporate
issuer there proves TLS interception), every response header, the exact UTC wall-clock of
the drop to line up against the ELB 460 entries, and the WinSock error code.
Endpoints
GET /health quick check ·
POST /sink-10d2f1 the upload sink ·
GET /trickle?kbps=16&minutes=60 download-direction test
The script
<#
probe.ps1 -- Proxy timeout probe (client side, runs BEHIND the Blue Coat proxy)
v2 -- forensic logging build
Streams a slow, long-lived HTTPS upload to a server that imposes NO timeout, and
records everything needed to prove WHERE a long upload is being cut off:
* full machine / OS / PowerShell / clock-sync context
* the proxy actually selected for this URL, plus WinINET + WinHTTP config and PAC URL
* DNS resolution of the target and the proxy
* the TLS certificate actually presented <-- if the issuer is a corporate CA,
that is direct proof the proxy is intercepting (MITM) the TLS session
* every response header from the preflight <-- Blue Coat ProxySG stamps
X-BlueCoat-Via, which is direct proof the traffic traversed the proxy
* the live TCP 5-tuple (local ip:port -> proxy ip:port)
* a timestamped progress sample every -ProgressSec, to a .log and a .csv
* on failure: absolute UTC wall-clock of the drop (to line up with the ELB 460
log), elapsed to the millisecond, bytes sent, the entire exception chain,
WinSock error name + code, and any HTTP error page the proxy returned
* -Repeat N runs back-to-back and prints a consistency summary, which is what
actually proves "it dies at the same interval every time"
Target the server BY IP. The proxy blocks the hostname but not the address, and a
certificate cannot match a raw IP, so certificate validation is OFF by default.
The certificate is still captured and logged, which is what the evidence rests on.
QUICK START (no admin, nothing to install):
powershell -ExecutionPolicy Bypass -File .\probe.ps1 -Url https://IP/sink-XXXX -Minutes 90
PROVE THE INTERVAL IS REPEATABLE (what you want for the ticket):
powershell -ExecutionPolicy Bypass -File .\probe.ps1 -Url https://IP/sink-XXXX -Minutes 90 -Repeat 3
OPTIONS
-Url https sink endpoint on the test server
-Minutes how long one upload runs if never dropped (default 60)
-RateKBps trickle rate KB/s (default 16)
-Repeat number of back-to-back runs (default 1)
-PauseSec pause between runs (default 10)
-ProgressSec progress sample interval (default 10)
-Proxy default azbcpxy.nycnet:8080 | "system" = auto-detect | "none" = direct
| any host[:port] (port defaults to 8080)
-VerifyCert re-enable full TLS validation (off by default; only useful by hostname)
-Chunked chunked transfer-encoding instead of fixed Content-Length
-LogDir output folder (default %TEMP%\proxyprobe)
#>
param(
[Parameter(Mandatory=$true)][string]$Url,
[double]$Minutes = 60,
[double]$RateKBps = 16,
[int]$Repeat = 1,
[int]$PauseSec = 10,
[int]$ProgressSec = 10,
[string]$Proxy = "azbcpxy.nycnet:8080",
[switch]$Insecure, # accepted for compatibility; certificate checking is OFF by default
[switch]$VerifyCert, # opt back IN to full certificate validation
[switch]$Chunked,
[string]$RunId = ([guid]::NewGuid().ToString("N").Substring(0,8)),
[string]$LogDir = (Join-Path $env:TEMP "proxyprobe")
)
$ErrorActionPreference = "Stop"
# We connect to the server BY IP, because the proxy blocks the hostname but not the
# address. A certificate can never match an IP it was not issued for, so validation is
# disabled by default here. The certificate is still captured and logged in full -- that
# is the evidence we actually care about, and disabling validation does not weaken it.
$script:SkipCert = -not $VerifyCert
if (-not (Test-Path -LiteralPath $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null }
$logPath = Join-Path $LogDir "probe-$RunId.log"
$samplesPath = Join-Path $LogDir "probe-$RunId-samples.csv"
$summaryPath = Join-Path $LogDir "probe-summary.csv"
function TS { "{0} | {1}Z" -f (Get-Date).ToString("yyyy-MM-dd HH:mm:ss.fff zzz"), (Get-Date).ToUniversalTime().ToString("HH:mm:ss.fff") }
function Log([string]$m) {
$line = "[{0}] {1}" -f (TS), $m
Write-Host $line
try { Add-Content -LiteralPath $logPath -Value $line -Encoding UTF8 } catch {}
}
function Section([string]$t) { Log ""; Log ("======== $t " + ("=" * [math]::Max(4, 58 - $t.Length))) }
function Try-Log([string]$label, [scriptblock]$sb) {
try { $v = & $sb; if ($null -ne $v) { foreach ($ln in ($v | Out-String -Width 200).TrimEnd().Split("`n")) { Log (" {0}: {1}" -f $label, $ln.TrimEnd()) } } }
catch { Log (" {0}: <unavailable: {1}>" -f $label, $_.Exception.Message) }
}
function Test-TcpPort([string]$h, [int]$port, [int]$timeoutMs = 5000) {
try {
$c = New-Object System.Net.Sockets.TcpClient
$iar = $c.BeginConnect($h, $port, $null, $null)
$ok = $iar.AsyncWaitHandle.WaitOne($timeoutMs, $false)
if ($ok -and $c.Connected) { $c.EndConnect($iar); $c.Close(); return $true }
$c.Close(); return $false
} catch { return $false }
}
# ---------------------------------------------------------------- TLS plumbing
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls
try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls13 } catch {}
} catch {}
[System.Net.ServicePointManager]::Expect100Continue = $false
[System.Net.ServicePointManager]::DefaultConnectionLimit = 10
# Capture whatever certificate is actually presented to us. This is the single most
# valuable artifact: a corporate issuer here proves TLS interception by the proxy.
#
# NOTE: the primary mechanism MUST be a COMPILED delegate, not a PowerShell scriptblock.
# A scriptblock used as ServerCertificateValidationCallback runs on an I/O thread with no
# runspace under PowerShell 7, where it throws and breaks the TLS handshake outright.
# We fall back to a scriptblock only on Windows PowerShell 5.x, where it is safe.
$script:CertCaptureOk = $false
$script:CertMode = "default validation"
try {
if (-not ("ProbeCertCapture" -as [type])) {
Add-Type -TypeDefinition @"
using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public static class ProbeCertCapture {
public static bool AllowInsecure = false;
public static bool Captured = false;
public static string Subject = "", Issuer = "", Thumbprint = "", Serial = "";
public static string NotBefore = "", NotAfter = "", Sans = "", PolicyErrors = "", Chain = "";
public static bool Validate(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors errors) {
try {
X509Certificate2 x = new X509Certificate2(cert);
Subject = x.Subject; Issuer = x.Issuer; Thumbprint = x.Thumbprint; Serial = x.SerialNumber;
NotBefore = x.NotBefore.ToString("o"); NotAfter = x.NotAfter.ToString("o");
string sn = "";
foreach (X509Extension ext in x.Extensions) { if (ext.Oid != null && ext.Oid.Value == "2.5.29.17") sn = ext.Format(false); }
Sans = sn; PolicyErrors = errors.ToString();
string ch = "";
if (chain != null && chain.ChainElements != null) {
foreach (X509ChainElement el in chain.ChainElements) { ch += " " + el.Certificate.Subject + " [issuer] " + el.Certificate.Issuer + "\n"; }
}
Chain = ch; Captured = true;
} catch { }
if (AllowInsecure) return true;
return errors == SslPolicyErrors.None;
}
public static RemoteCertificateValidationCallback GetCallback() { return Validate; }
}
"@
}
[ProbeCertCapture]::AllowInsecure = $script:SkipCert
[Net.ServicePointManager]::ServerCertificateValidationCallback = [ProbeCertCapture]::GetCallback()
$script:CertCaptureOk = $true
$script:CertMode = $(if ($script:SkipCert) { "validation DISABLED, cert captured (compiled delegate)" } else { "validation enforced, cert captured (compiled delegate)" })
} catch {
Write-Host ("WARN: compiled certificate hook unavailable ({0})." -f $_.Exception.Message)
if ($script:SkipCert) {
if ($PSVersionTable.PSVersion.Major -le 5) {
try {
[Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
$script:CertMode = "validation DISABLED (scriptblock fallback; cert details NOT captured)"
Write-Host " Using the PowerShell 5.x scriptblock fallback instead."
} catch { $script:CertMode = "UNAVAILABLE - validation will fail against an IP" }
} else {
$script:CertMode = "UNAVAILABLE - PowerShell 7 cannot use the scriptblock fallback safely"
Write-Host " On PowerShell 7 there is no safe fallback. Try Windows PowerShell 5.1 instead."
}
}
}
function Log-CertInfo {
if (-not $script:CertCaptureOk) { Log " cert: <not captured -- compiled hook unavailable; see cert mode above>"; return }
if (-not [ProbeCertCapture]::Captured) { Log " cert: <no TLS handshake completed yet>"; return }
Log (" cert subject : " + [ProbeCertCapture]::Subject)
Log (" cert ISSUER : " + [ProbeCertCapture]::Issuer)
Log (" cert thumbprint : " + [ProbeCertCapture]::Thumbprint)
Log (" cert serial : " + [ProbeCertCapture]::Serial)
Log (" cert validity : " + [ProbeCertCapture]::NotBefore + " -> " + [ProbeCertCapture]::NotAfter)
if ([ProbeCertCapture]::Sans) { Log (" cert SANs : " + [ProbeCertCapture]::Sans) }
Log (" cert policyErrors: " + [ProbeCertCapture]::PolicyErrors)
$ch = [ProbeCertCapture]::Chain
if ($ch) { Log " cert chain :"; foreach ($l in $ch.Split("`n")) { if ($l.Trim()) { Log $l.TrimEnd() } } }
Log " >> If the ISSUER above is an internal/corporate CA rather than a public CA,"
Log " >> the proxy is terminating and re-signing TLS (SSL inspection)."
}
# ---------------------------------------------------------------- proxy selection
$webProxy = $null; $proxyDesc = ""; $proxyTarget = $null
if ($Proxy -eq "none") {
$proxyDesc = "DIRECT (forced, -Proxy none)"
} elseif ($Proxy -eq "system") {
$webProxy = [System.Net.WebRequest]::GetSystemWebProxy()
$webProxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
try {
$proxyTarget = $webProxy.GetProxy([Uri]$Url)
if ($proxyTarget -and ($proxyTarget.AbsoluteUri -ne ([Uri]$Url).AbsoluteUri)) { $proxyDesc = "system -> $proxyTarget" }
else { $proxyDesc = "system -> DIRECT (no proxy configured for this URL)"; $proxyTarget = $null }
} catch { $proxyDesc = "system (could not resolve)" }
} else {
# Explicit named proxy. Default is the NYC Blue Coat box on 8080.
$pspec = $Proxy
if ($pspec -notmatch '^https?://') { $pspec = "http://$pspec" }
if ($pspec -notmatch ':\d+(/|$)') { $pspec = $pspec.TrimEnd('/') + ":8080" }
$webProxy = New-Object System.Net.WebProxy($pspec, $true)
$webProxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
$proxyTarget = [Uri]$pspec
$proxyDesc = "EXPLICIT -> $pspec"
}
$bufSize = 16384
$bytesSec = [long]([math]::Round($RateKBps * 1024.0)); if ($bytesSec -lt 1) { $bytesSec = 1 }
$durSec = [long]($Minutes * 60)
$totalBytes = $bytesSec * $durSec
# ---------------------------------------------------------------- banner + forensics
Log ("#################### proxy-timeout-probe v2 session=$RunId ####################")
Log ("target url : $Url")
Log ("plan : $RateKBps KB/s for $Minutes min (~{0:N1} MB) mode={1} repeat={2}" -f ($totalBytes/1MB), $(if($Chunked){"chunked"}else{"fixed Content-Length"}), $Repeat)
Log ("proxy : $proxyDesc")
Log ("cert mode : $script:CertMode")
if ($script:SkipCert) {
Log " (certificate validation is intentionally OFF: we connect by IP,"
Log " which no hostname certificate can match. The certificate itself"
Log " is still captured and logged below, which is the real evidence.)"
}
if ($proxyTarget) {
if (Test-TcpPort $proxyTarget.Host $proxyTarget.Port) {
Log ("proxy check : TCP connect to {0}:{1} succeeded" -f $proxyTarget.Host, $proxyTarget.Port)
} else {
Log ""
Log (" !! WARNING: cannot open a TCP connection to the proxy {0}:{1}." -f $proxyTarget.Host, $proxyTarget.Port)
Log " !! Check the name and port, or override with -Proxy host:port."
Log " !! The run below will almost certainly fail at the connect stage."
}
} elseif ($Proxy -ne "none") {
Log ""
Log " !! WARNING: this URL is NOT being routed through a proxy, so the run would"
Log " !! prove nothing about proxy behaviour. Pass -Proxy host:port explicitly."
}
Log ("log : $logPath")
Log ("samples csv : $samplesPath")
Log ("summary csv : $summaryPath")
Section "MACHINE / ENVIRONMENT"
Log (" computer : " + $env:COMPUTERNAME)
Log (" user : " + $env:USERDOMAIN + "\" + $env:USERNAME)
Log (" process : PID $PID 64bit=" + [Environment]::Is64BitProcess)
Log (" powershell : " + $PSVersionTable.PSVersion + " CLR " + $PSVersionTable.CLRVersion)
Log (" os : " + [Environment]::OSVersion.VersionString)
Try-Log "os caption" { (Get-CimInstance Win32_OperatingSystem -ErrorAction Stop).Caption }
Log (" timezone : " + ([System.TimeZoneInfo]::Local).DisplayName)
Log (" local time : " + (Get-Date).ToString("o"))
Log (" utc time : " + (Get-Date).ToUniversalTime().ToString("o"))
Try-Log "clock sync" { w32tm /query /status 2>&1 | Select-String "Source|Last Successful|Phase Offset" }
Try-Log "local ipv4" { (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop | Where-Object { $_.IPAddress -notlike "127.*" } | ForEach-Object { "$($_.IPAddress)/$($_.PrefixLength) ($($_.InterfaceAlias))" }) }
Section "PROXY CONFIGURATION (as this machine sees it)"
Log (" selected for target : " + $(if ($proxyTarget) { "$proxyTarget" } else { "DIRECT" }))
Try-Log "WinINET" {
$k = Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings" -ErrorAction Stop
"ProxyEnable=$($k.ProxyEnable) ProxyServer=$($k.ProxyServer) AutoConfigURL=$($k.AutoConfigURL) Bypass=$($k.ProxyOverride)"
}
Try-Log "WinHTTP" { netsh winhttp show proxy 2>&1 }
Log (" proxy credentials : DefaultCredentials (integrated auth if the proxy demands it)")
Section "DNS RESOLUTION"
$targetHost = ([Uri]$Url).Host
Try-Log "target $targetHost" { [System.Net.Dns]::GetHostAddresses($targetHost) | ForEach-Object { $_.IPAddressToString } }
if ($proxyTarget) { Try-Log ("proxy " + $proxyTarget.Host) { [System.Net.Dns]::GetHostAddresses($proxyTarget.Host) | ForEach-Object { $_.IPAddressToString } } }
# ---------------------------------------------------------------- preflight
Section "PREFLIGHT (GET /health -- proves path, TLS and proxy before the long run)"
$healthUrl = ($Url -replace '[^/]+/?$','health')
Log (" GET $healthUrl")
try {
$r0 = [System.Net.HttpWebRequest]::Create($healthUrl)
$r0.Method = "GET"; $r0.Proxy = $webProxy; $r0.Timeout = 30000
$r0.UserAgent = "proxy-timeout-probe/2.0 session=$RunId"
$resp0 = $r0.GetResponse()
Log (" -> HTTP {0} {1} (protocol {2})" -f [int]$resp0.StatusCode, $resp0.StatusDescription, $resp0.ProtocolVersion)
Log " response headers (look for X-BlueCoat-Via / Via / X-Cache -- proof of proxy transit):"
foreach ($h in $resp0.Headers.AllKeys) { Log (" {0}: {1}" -f $h, $resp0.Headers[$h]) }
$sr0 = New-Object System.IO.StreamReader($resp0.GetResponseStream())
Log (" body: " + $sr0.ReadToEnd().Trim()); $sr0.Close(); $resp0.Close()
} catch {
Log (" PREFLIGHT FAILED: " + $_.Exception.Message)
Log " (continuing to the upload test anyway)"
}
Log " TLS certificate presented on that connection:"
Log-CertInfo
# ---------------------------------------------------------------- csv headers
if (-not (Test-Path -LiteralPath $samplesPath)) { Add-Content -LiteralPath $samplesPath -Value "iter,ts_local,ts_utc,elapsed_s,bytes_sent,inst_kbps,avg_kbps" }
if (-not (Test-Path -LiteralPath $summaryPath)) { Add-Content -LiteralPath $summaryPath -Value "session,iter,start_utc,end_utc,elapsed_s,bytes_sent,outcome,detail" }
function Unwrap-WebException($ex) {
$e = $ex
while ($e) { if ($e -is [System.Net.WebException]) { return $e }; $e = $e.InnerException }
return $null
}
# ---------------------------------------------------------------- one run
function Invoke-ProbeRun([int]$iter) {
Section ("RUN $iter of $Repeat")
$startLocal = Get-Date
$startUtc = $startLocal.ToUniversalTime()
Log (" start (utc) : " + $startUtc.ToString("o"))
$req = [System.Net.HttpWebRequest]::Create($Url)
$req.Method = "POST"; $req.Proxy = $webProxy
$req.UserAgent = "proxy-timeout-probe/2.0 session=$RunId"
$req.Headers.Add("X-Run-Id", "$RunId-$iter")
$req.ContentType = "application/octet-stream"
$req.KeepAlive = $true
$req.AllowWriteStreamBuffering = $false
# Each iteration gets its own connection group so .NET cannot hand us a pooled,
# already-aged socket from the previous run. Every run therefore measures the
# lifetime of a FRESH connection, which is the only fair way to time a cutoff.
$req.ConnectionGroupName = "probe-$RunId-$iter"
# CRITICAL: never let .NET's own 100s/300s defaults fire and look like a proxy timeout
$req.Timeout = [System.Threading.Timeout]::Infinite
$req.ReadWriteTimeout = [System.Threading.Timeout]::Infinite
if ($Chunked) { $req.SendChunked = $true } else { $req.ContentLength = $totalBytes }
$buf = [byte[]]::new($bufSize)
for ($i=0; $i -lt $bufSize; $i++) { $buf[$i] = [byte](65 + ($i % 26)) }
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$sent = [long]0; $lastBytes = [long]0; $lastT = 0.0; $nextLog = [double]$ProgressSec
$phase = "connect"; $outcome = "?"; $detail = ""
try {
Log " opening request stream (establishes the CONNECT tunnel through the proxy)..."
$stream = $req.GetRequestStream()
$phase = "streaming"
Log " connected -- streaming now"
Log " transport: ONE TCP connection carrying ONE HTTP POST, body streamed"
Log " continuously for the whole run (no reconnects, no retries)."
Try-Log "tcp socket" {
$c = @(Get-NetTCPConnection -OwningProcess $PID -State Established -ErrorAction Stop)
@("$($c.Count) established connection(s) owned by this process (expect 1)") +
($c | ForEach-Object { " $($_.LocalAddress):$($_.LocalPort) -> $($_.RemoteAddress):$($_.RemotePort)" })
}
Log " TLS certificate on the upload connection:"
Log-CertInfo
while ($true) {
if ($Chunked) { if ($sw.Elapsed.TotalSeconds -ge $durSec) { break }; $toWrite = $bufSize }
else { $rem = $totalBytes - $sent; if ($rem -le 0) { break }; $toWrite = [int][math]::Min([long]$bufSize, $rem) }
$stream.Write($buf, 0, $toWrite); $stream.Flush(); $sent += $toWrite
$target = $sent / [double]$bytesSec
$ms = [int](($target - $sw.Elapsed.TotalSeconds) * 1000.0)
if ($ms -gt 0) { Start-Sleep -Milliseconds $ms }
$el = $sw.Elapsed.TotalSeconds
if ($el -ge $nextLog) {
$inst = if (($el - $lastT) -gt 0) { (($sent - $lastBytes)/1KB)/($el - $lastT) } else { 0 }
$avg = ($sent/1KB)/[math]::Max(1.0,$el)
Log (" t+{0:hh\:mm\:ss} sent {1,10:N1} KB inst {2,6:N1} KB/s avg {3,6:N1} KB/s" -f $sw.Elapsed, ($sent/1KB), $inst, $avg)
Add-Content -LiteralPath $samplesPath -Value ("{0},{1},{2},{3:F3},{4},{5:F2},{6:F2}" -f `
$iter, (Get-Date).ToString("s"), (Get-Date).ToUniversalTime().ToString("s"), $el, $sent, $inst, $avg)
$lastBytes = $sent; $lastT = $el; $nextLog += $ProgressSec
}
}
$phase = "awaiting-response"
$stream.Close()
Log (" upload finished: {0:N1} KB written, waiting for the server's response..." -f ($sent/1KB))
$resp = $req.GetResponse()
$sr = New-Object System.IO.StreamReader($resp.GetResponseStream())
$body = $sr.ReadToEnd(); $sr.Close()
Log (" RESULT: COMPLETED in {0:hh\:mm\:ss} -> HTTP {1}; server said: {2}" -f $sw.Elapsed, [int]$resp.StatusCode, $body.Trim())
foreach ($h in $resp.Headers.AllKeys) { Log (" {0}: {1}" -f $h, $resp.Headers[$h]) }
$resp.Close()
$outcome = "COMPLETED"; $detail = "http-200"
}
catch {
$el = $sw.Elapsed
$ex = $_.Exception
$outcome = $(if ($phase -eq "connect") { "CONNECT-FAILED" } else { "DROPPED" })
Log (" *** {0} *** during phase '{1}'" -f $outcome, $phase)
if ($outcome -eq "CONNECT-FAILED") {
Log " (the connection never established, so this is a reachability/TLS/proxy-auth"
Log " problem, NOT a timeout. Fix this before drawing any timeout conclusions.)"
}
Log (" drop wall-clock (LOCAL): " + (Get-Date).ToString("o"))
Log (" drop wall-clock (UTC) : " + (Get-Date).ToUniversalTime().ToString("o") + " <-- match this against the ELB 460 entries")
Log (" elapsed : {0:hh\:mm\:ss}.{1:000} ({2:F3} seconds)" -f $el, $el.Milliseconds, $el.TotalSeconds)
Log (" bytes sent : {0:N0} ({1:N1} KB)" -f $sent, ($sent/1KB))
Log " exception chain:"
$e = $ex; $lvl = 0
while ($e) {
Log (" [{0}] {1}: {2} (HRESULT 0x{3:X8})" -f $lvl, $e.GetType().FullName, $e.Message, $e.HResult)
if ($e -is [System.Net.WebException]) { Log (" WebExceptionStatus = " + $e.Status) }
if ($e -is [System.Net.Sockets.SocketException]) { Log (" SocketError = " + $e.SocketErrorCode + " WinSock = " + $e.NativeErrorCode) ; $detail = "$($e.SocketErrorCode)/$($e.NativeErrorCode)" }
$e = $e.InnerException; $lvl++
}
$we = Unwrap-WebException $ex
if ($we) {
if (-not $detail -or $detail -eq "") { $detail = "$($we.Status)" }
if ($we.Response) {
Log " the proxy/server returned an HTTP error response -- capturing it (Blue Coat usually names the policy here):"
try {
Log (" status: {0} {1}" -f [int]$we.Response.StatusCode, $we.Response.StatusDescription)
foreach ($h in $we.Response.Headers.AllKeys) { Log (" {0}: {1}" -f $h, $we.Response.Headers[$h]) }
$ers = New-Object System.IO.StreamReader($we.Response.GetResponseStream())
$etxt = $ers.ReadToEnd(); $ers.Close()
if ($etxt.Length -gt 2000) { $etxt = $etxt.Substring(0,2000) + " ...[truncated]" }
foreach ($l in $etxt.Split("`n")) { Log (" | " + $l.TrimEnd()) }
} catch { Log (" <could not read error body: " + $_.Exception.Message + ">") }
}
}
Log " TLS certificate seen on this connection:"
Log-CertInfo
}
finally { $sw.Stop() }
$endUtc = (Get-Date).ToUniversalTime()
Add-Content -LiteralPath $summaryPath -Value ("{0},{1},{2},{3},{4:F3},{5},{6},{7}" -f `
$RunId, $iter, $startUtc.ToString("s"), $endUtc.ToString("s"), $sw.Elapsed.TotalSeconds, $sent, $outcome, $detail)
return [PSCustomObject]@{ Iter=$iter; Outcome=$outcome; Seconds=$sw.Elapsed.TotalSeconds; Bytes=$sent; Detail=$detail }
}
# ---------------------------------------------------------------- main loop
$results = @()
for ($i = 1; $i -le $Repeat; $i++) {
$results += (Invoke-ProbeRun $i)
if ($i -lt $Repeat) { Log (" pausing $PauseSec s before the next run..."); Start-Sleep -Seconds $PauseSec }
}
Section "SESSION SUMMARY"
Log (" {0,-5} {1,-10} {2,12} {3,14} {4}" -f "iter","outcome","elapsed_s","sent_KB","detail")
foreach ($r in $results) { Log (" {0,-5} {1,-10} {2,12:F3} {3,14:N1} {4}" -f $r.Iter, $r.Outcome, $r.Seconds, ($r.Bytes/1KB), $r.Detail) }
$fails = @($results | Where-Object { $_.Outcome -eq "CONNECT-FAILED" })
if ($fails.Count -gt 0) {
Log ""
Log (" NOTE: {0} run(s) never established a connection at all. Those are reachability," -f $fails.Count)
Log " TLS or proxy-auth failures, not timeouts, and prove nothing about timeouts."
}
$drops = @($results | Where-Object { $_.Outcome -eq "DROPPED" })
if ($drops.Count -ge 2) {
$mn = ($drops | Measure-Object Seconds -Minimum).Minimum
$mx = ($drops | Measure-Object Seconds -Maximum).Maximum
$av = ($drops | Measure-Object Seconds -Average).Average
Log ""
Log (" {0} of {1} runs were cut off." -f $drops.Count, $results.Count)
Log (" cutoff min {0:F3}s / avg {1:F3}s / max {2:F3}s / spread {3:F3}s" -f $mn, $av, $mx, ($mx-$mn))
if ($av -lt 5.0) {
Log " >> These cutoffs happened almost immediately, so they are connection failures"
Log " >> rather than a timeout. Resolve those before drawing timeout conclusions."
} elseif (($mx - $mn) -lt ([math]::Max(5.0, $av * 0.05))) {
Log " >> The cutoff is TIGHTLY CLUSTERED. Random loss does not do that; a configured"
Log " >> timeout does. The server never closed and has no timeout, so the limit is"
Log " >> being enforced by a device in the middle."
} else {
Log " >> The cutoffs are spread out rather than clustered, which looks more like"
Log " >> instability than a fixed configured timeout. Consider more runs."
}
} elseif (@($results | Where-Object { $_.Outcome -eq "COMPLETED" }).Count -eq $results.Count) {
Log (" All runs completed the full {0} minutes with no cutoff on this path." -f $Minutes)
}
Log ""
Log (" detailed log : $logPath")
Log (" samples csv : $samplesPath")
Log (" summary csv : $summaryPath")
Log ("#################### end session $RunId ####################")