<# Orynium netcheck.ps1 Read-only network / connectivity / VPN inspection for Windows Server & Windows VPS. GUARANTEE: makes NO changes. It only reads state - no set-, new-, remove-, restart-, or disable- cmdlets are used anywhere. Safe to run over RDP/VNC. Usage (PowerShell, as Administrator for full detail): powershell -ExecutionPolicy Bypass -File .\netcheck.ps1 powershell -ExecutionPolicy Bypass -File .\netcheck.ps1 -Domain example.com -Yes Params: -Domain include DNS / TLS / web checks for this domain -Yes skip all prompts, use defaults (for automation) #> param( [string]$Domain = "", [string]$ExpectedGeo = "", [switch]$Yes, [switch]$Offline # no internet on this box: local checks only ) $ErrorActionPreference = "SilentlyContinue" $script:OK = @() $script:WARN = @() $script:ISSUE = @() $InputCount = 0 $SkipRest = $false $MaxBeforeSkip = 5 # Resolve a writable temp directory. $env:TEMP is not guaranteed to be set # (scheduled tasks, some service accounts, PowerShell on non-Windows). $TempDir = $env:TEMP if ([string]::IsNullOrWhiteSpace($TempDir)) { $TempDir = $env:TMP } if ([string]::IsNullOrWhiteSpace($TempDir)) { $TempDir = [System.IO.Path]::GetTempPath() } if ([string]::IsNullOrWhiteSpace($TempDir)) { $TempDir = "." } $LogFile = Join-Path $TempDir ("orynium-netcheck-{0}.log" -f (Get-Date -Format "yyyyMMdd-HHmmss")) try { New-Item -ItemType File -Path $LogFile -Force | Out-Null } catch { } $script:SectionN = 0 function Say([string]$m, [string]$color = "Gray") { Write-Host $m -ForegroundColor $color; Add-Content $LogFile $m } function Info([string]$m) { Write-Host " $m" -ForegroundColor DarkGray; Add-Content $LogFile " $m" } function LogOnly([string]$m) { Add-Content $LogFile $m } function Detail([string]$text, [int]$n = 6) { Add-Content $LogFile $text ($text -split "`n" | Select-Object -First $n) | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray } } function Section([string]$t) { $script:SectionN++ Write-Host "" Write-Host ("-" * 60) -ForegroundColor DarkGray Write-Host (" > {0:d2} {1}" -f $script:SectionN, $t) -ForegroundColor Cyan Add-Content $LogFile ("== {0:d2} {1} ==" -f $script:SectionN, $t) } function MarkOK([string]$m) { $script:OK += $m; Write-Host " [ok] $m" -ForegroundColor Green; Add-Content $LogFile "[OK] $m" } function MarkWarn([string]$m) { $script:WARN += $m; Write-Host " [!] $m" -ForegroundColor Yellow; Add-Content $LogFile "[WARN] $m" } function MarkIssue([string]$m) { $script:ISSUE += $m; Write-Host " [X] $m" -ForegroundColor Red; Add-Content $LogFile "[ISSUE] $m" } # ---- 3-way verification: run three independent probes for one question ---- # A single failing tool is a tool problem; three failing tools is a real one. function Verify3([string]$label, [scriptblock[]]$probes) { $pass = 0; $total = 0; $i = 1 foreach ($p in $probes) { $total++ try { if (& $p) { $pass++; LogOnly " probe $i ok" } else { LogOnly " probe $i FAILED" } } catch { LogOnly " probe $i FAILED: $($_.Exception.Message)" } $i++ } LogOnly " [3-way] $label -> $pass/$total passed" return @{ Pass = $pass; Total = $total } } function Ask([string]$question, [string]$default) { $script:InputCount++ if ($Yes -or $script:SkipRest) { return $default } $suffix = " [default: $default]" if ($script:InputCount -gt $MaxBeforeSkip) { $suffix += " (or 'skip-rest' to accept defaults for all remaining)" } $reply = Read-Host "$question$suffix" if ($reply -eq "skip-rest") { $script:SkipRest = $true; return $default } if ([string]::IsNullOrWhiteSpace($reply)) { return $default } return $reply } function AskYN([string]$question, [string]$default) { $a = Ask "$question (y/n)" $default return ($a -match '^[Yy]') } Write-Host "Orynium netcheck.ps1 v2.0 (read-only)" -ForegroundColor White Say "Log: $LogFile" Say ("Started: {0}" -f (Get-Date)) # Admin check (affects how much detail is visible) $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) if (-not $IsAdmin) { MarkWarn "Not running as Administrator - some details (services, firewall) may be limited" } # ---- intake ---- if ([string]::IsNullOrWhiteSpace($Domain)) { $Domain = Ask "Enter a domain for DNS/TLS/web checks (blank to skip)" "" } $RunVpn = AskYN "Run VPN / tunnel / proxy detection" "y" $PingTarget = Ask "External IP for ping/MTU tests" "1.1.1.1" if ([string]::IsNullOrWhiteSpace($ExpectedGeo)) { $ExpectedGeo = Ask "Expected country code for this server's public IP (e.g. US, IT, IR, FR, IN - blank to skip)" "" } $RunTls = $true if (-not [string]::IsNullOrWhiteSpace($Domain)) { $RunTls = AskYN "Run TLS check against $Domain" "y" } # Offline overrides every answer above - applied last so nothing re-enables a # network call. Local checks (adapters, MTU, firewall, VPN, routes) still run. if ($Offline) { $Domain = ""; $ExpectedGeo = ""; $RunTls = $false Write-Host "" Write-Host " OFFLINE MODE" -ForegroundColor Cyan Write-Host " External lookups and domain checks disabled. Nothing leaves this server." -ForegroundColor DarkGray } # --------------------------------------------------------------------------- Section "System" $os = Get-CimInstance Win32_OperatingSystem Say (" OS: {0} (build {1})" -f $os.Caption, $os.BuildNumber) Say (" Host: {0}" -f $env:COMPUTERNAME) MarkOK ("Windows detected: {0}" -f $os.Caption) # --------------------------------------------------------------------------- Section "Network adapters / NICs" $adapters = Get-NetAdapter | Where-Object { $_.Status -ne $null } if ($adapters) { foreach ($a in $adapters) { Say (" - {0} [{1}] status={2} speed={3} mac={4}" -f $a.Name, $a.InterfaceDescription, $a.Status, $a.LinkSpeed, $a.MacAddress) } $up = ($adapters | Where-Object { $_.Status -eq "Up" }).Count if ($up -ge 1) { MarkOK "$up adapter(s) Up" } else { MarkIssue "No adapter is Up" } } else { # Fallback for older hosts without Get-NetAdapter Say (ipconfig /all | Out-String) MarkWarn "Get-NetAdapter unavailable - used ipconfig fallback" } # ---- MTU check (standard = 1500) ---- Section "MTU per interface (standard = 1500)" $mtuProblem = $false $ifs = Get-NetIPInterface -ErrorAction SilentlyContinue | Where-Object { $_.ConnectionState -eq "Connected" } if ($ifs) { foreach ($i in $ifs) { $nm = (Get-NetAdapter -InterfaceIndex $i.InterfaceIndex).Name if ($i.NlMtu -eq 1500) { Say (" - {0} ({1}): MTU={2} [OK]" -f $nm, $i.AddressFamily, $i.NlMtu) } elseif ($nm -match 'VPN|TAP|WireGuard|Tunnel|Loopback|isatap') { Say (" - {0} ({1}): MTU={2} (tunnel/virtual - different is normal)" -f $nm, $i.AddressFamily, $i.NlMtu) } else { Say (" - {0} ({1}): MTU={2} [!= 1500]" -f $nm, $i.AddressFamily, $i.NlMtu) $mtuProblem = $true } } if ($mtuProblem) { MarkWarn "One or more physical interfaces have MTU != 1500 - can cause fragmentation; confirm it's intentional" } else { MarkOK "All physical interfaces at standard 1500 MTU" } } else { Say (netsh interface ipv4 show subinterfaces | Out-String) MarkWarn "Get-NetIPInterface unavailable - used netsh fallback" } # --------------------------------------------------------------------------- Section "Routing" $defRoute = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue if ($defRoute) { Say (" Default gateway: {0}" -f ($defRoute.NextHop -join ", ")); MarkOK "Default route present" } else { MarkIssue "No default route configured" } # --------------------------------------------------------------------------- Section "Primary IP / gateway locality" $script:PrimaryIP = $null $gw = ($defRoute | Select-Object -First 1).NextHop $primIf = ($defRoute | Select-Object -First 1).InterfaceIndex if ($primIf) { $primAddr = Get-NetIPAddress -InterfaceIndex $primIf -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress -notmatch '^169\.254\.' } | Select-Object -First 1 if ($primAddr) { $script:PrimaryIP = $primAddr.IPAddress $prefix = $primAddr.PrefixLength Say (" Primary IP: {0}/{1}" -f $script:PrimaryIP, $prefix) Say (" Default gateway: {0}" -f $gw) if ($script:PrimaryIP -match '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.|169\.254\.)') { MarkWarn "Primary IP $($script:PrimaryIP) is PRIVATE/internal - server is behind NAT (public IP is at the provider edge)" } else { MarkOK "Primary IP $($script:PrimaryIP) is a public address bound directly to this host" } # same-subnet-as-gateway test using prefix length if ($gw) { try { $ipBytes = ([System.Net.IPAddress]::Parse($script:PrimaryIP)).GetAddressBytes() $gwBytes = ([System.Net.IPAddress]::Parse($gw)).GetAddressBytes() [Array]::Reverse($ipBytes); [Array]::Reverse($gwBytes) $ipInt = [BitConverter]::ToUInt32($ipBytes,0) $gwInt = [BitConverter]::ToUInt32($gwBytes,0) $mask = [uint32]([math]::Pow(2,32) - [math]::Pow(2,(32-$prefix))) if (($ipInt -band $mask) -eq ($gwInt -band $mask)) { MarkOK "Gateway $gw is in the same subnet as the primary IP (/$prefix) - normal L2-adjacent setup" } else { MarkWarn "Gateway $gw is OUTSIDE the primary IP's subnet (/$prefix) - unusual; on-link route or wrong netmask" } } catch { Say " (could not compute subnet relationship)" } } } } # --------------------------------------------------------------------------- Section "DNS configuration" $dns = Get-DnsClientServerAddress -ErrorAction SilentlyContinue | Where-Object { $_.ServerAddresses } if ($dns) { foreach ($d in $dns) { Say (" {0}: {1}" -f $d.InterfaceAlias, ($d.ServerAddresses -join ", ")) } MarkOK "DNS servers configured" } else { MarkIssue "No DNS servers found" } # --------------------------------------------------------------------------- Section "Connectivity (each result confirmed 3 ways)" if ($Offline) { Info "skipped - offline mode" } else { # ICMP against three targets $r1 = Verify3 "ICMP" @( { Test-Connection -ComputerName $PingTarget -Count 2 -Quiet -ErrorAction SilentlyContinue }, { Test-Connection -ComputerName "8.8.8.8" -Count 1 -Quiet -ErrorAction SilentlyContinue }, { Test-Connection -ComputerName "9.9.9.9" -Count 1 -Quiet -ErrorAction SilentlyContinue } ) if ($r1.Pass -eq $r1.Total) { MarkOK "ICMP works ($($r1.Pass)/$($r1.Total) targets replied)" } elseif ($r1.Pass -gt 0) { MarkWarn "ICMP partially working ($($r1.Pass)/$($r1.Total)) - an upstream may be filtering" } else { MarkWarn "No ICMP replies ($($r1.Total) tried) - commonly firewalled; check the TCP result below" } # Outbound TCP/443 three ways $r2 = Verify3 "outbound HTTPS" @( { (Invoke-WebRequest -Uri "https://1.1.1.1" -UseBasicParsing -TimeoutSec 6).StatusCode -lt 400 }, { (Test-NetConnection -ComputerName "github.com" -Port 443 -WarningAction SilentlyContinue).TcpTestSucceeded }, { (Test-NetConnection -ComputerName "www.google.com" -Port 443 -WarningAction SilentlyContinue).TcpTestSucceeded } ) if ($r2.Pass -eq $r2.Total) { MarkOK "Outbound HTTPS confirmed ($($r2.Pass)/$($r2.Total) probes agreed)" } elseif ($r2.Pass -gt 0) { MarkWarn "Outbound HTTPS inconsistent ($($r2.Pass)/$($r2.Total)) - likely one endpoint blocked, not the network" } else { MarkIssue "Outbound HTTPS FAILED on all $($r2.Total) probes - real egress block (firewall or proxy)" } # DNS three ways $r3 = Verify3 "DNS resolution" @( { [bool](Resolve-DnsName -Name "cloudflare.com" -Type A -ErrorAction SilentlyContinue) }, { [bool](Resolve-DnsName -Name "google.com" -Type A -ErrorAction SilentlyContinue) }, { [bool]([System.Net.Dns]::GetHostAddresses("github.com")) } ) if ($r3.Pass -eq $r3.Total) { MarkOK "DNS resolution confirmed ($($r3.Pass)/$($r3.Total) agreed)" } elseif ($r3.Pass -gt 0) { MarkWarn "DNS partially working ($($r3.Pass)/$($r3.Total)) - one resolver misconfigured" } else { MarkIssue "DNS resolution FAILED on all $($r3.Total) probes - check DNS server settings" } } # --------------------------------------------------------------------------- Section "Path MTU (measurement only)" $mtuOk = $null foreach ($size in 1472,1400,1372,1300) { $res = ping -n 1 -f -l $size -w 1500 $PingTarget 2>$null if ($res -match "bytes=") { $mtuOk = $size; break } } if ($mtuOk) { MarkOK ("Largest unfragmented payload to {0}: {1} bytes (path MTU ~{2})" -f $PingTarget, $mtuOk, ($mtuOk + 28)) } else { MarkWarn "Could not confirm path MTU (ICMP may be filtered)" } # --------------------------------------------------------------------------- Section "Firewall (state, listing only)" $fw = Get-NetFirewallProfile -ErrorAction SilentlyContinue if ($fw) { $anyOn = $false foreach ($p in $fw) { Say (" Profile {0}: {1}" -f $p.Name, ($(if ($p.Enabled) { "ON" } else { "off" }))) if ($p.Enabled) { $anyOn = $true } } if ($anyOn) { MarkOK "Windows Firewall is enabled on at least one profile" } else { MarkWarn "Windows Firewall is OFF on all profiles - relying solely on upstream/provider firewall" } } else { Say (netsh advfirewall show allprofiles state | Out-String) MarkWarn "Get-NetFirewallProfile unavailable - used netsh fallback" } # --------------------------------------------------------------------------- Section "Listening ports" $listen = Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | Select-Object LocalAddress, LocalPort, OwningProcess -Unique | Sort-Object LocalPort if ($listen) { foreach ($l in ($listen | Select-Object -First 40)) { $pname = (Get-Process -Id $l.OwningProcess -ErrorAction SilentlyContinue).ProcessName Say (" {0}:{1} ({2})" -f $l.LocalAddress, $l.LocalPort, $pname) } MarkOK ("{0} listening endpoint(s)" -f $listen.Count) } else { Say (netstat -ano | Out-String) } # --------------------------------------------------------------------------- if ($RunVpn) { Section "VPN / tunnel / proxy detection" $vpnPattern = 'openvpn|wireguard|tailscale|zerotier|softether|strongswan|nordvpn|expressvpn|surfshark|protonvpn|cisco.?anyconnect|globalprotect|forticlient|pulse.?secure|xray|v2ray|sing.?box|clash|hysteria|trojan|shadowsocks|outline|hiddify|netbird|nebula|openconnect' # Adapter names that indicate a tunnel $tunAdapters = Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object { $_.InterfaceDescription -match 'TAP|TUN|VPN|WireGuard|Tailscale|WAN Miniport|Tunnel' -or $_.Name -match 'VPN|TAP|WireGuard|Tailscale' } if ($tunAdapters) { Say " Tunnel-type adapters:" foreach ($t in $tunAdapters) { Say (" - {0} [{1}] status={2}" -f $t.Name, $t.InterfaceDescription, $t.Status) } MarkWarn "Tunnel/VPN adapter(s) present - see list above" } else { MarkOK "No TAP/TUN/VPN/WireGuard adapters found" } # Running services matching known VPN software $svc = Get-Service -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq "Running" -and ($_.Name -match $vpnPattern -or $_.DisplayName -match $vpnPattern) } if ($svc) { Say " Running VPN/proxy services:" foreach ($s in $svc) { Say (" - {0} ({1})" -f $s.DisplayName, $s.Name) } MarkIssue ("VPN/proxy service(s) running: " + (($svc | ForEach-Object { $_.Name }) -join ", ")) } else { MarkOK "No known VPN/proxy services running" } # Processes $proc = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match $vpnPattern } if ($proc) { Say " Matching processes:" foreach ($p in ($proc | Select-Object ProcessName -Unique)) { Say (" - {0}" -f $p.ProcessName) } MarkWarn "Process(es) matching VPN/proxy tooling found" } # Installed programs (registry, read-only) $paths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" ) $installed = Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -match $vpnPattern } | Select-Object -ExpandProperty DisplayName -Unique if ($installed) { Say " Installed VPN/proxy software:" foreach ($i in $installed) { Say (" - {0}" -f $i) } MarkIssue ("VPN/proxy software installed: " + ($installed -join ", ")) } # ---- SSH server on Windows (OpenSSH) can tunnel just like on Linux ---- $sshSvc = Get-Service -Name "sshd" -ErrorAction SilentlyContinue if ($sshSvc -and $sshSvc.Status -eq "Running") { Info "OpenSSH server (sshd) is running" $cfg = "$env:ProgramData\ssh\sshd_config" if (Test-Path $cfg) { $c = Get-Content $cfg -ErrorAction SilentlyContinue $tcpFwd = ($c | Where-Object { $_ -match '^\s*AllowTcpForwarding\s+(\S+)' } | Select-Object -Last 1) $gwPorts = ($c | Where-Object { $_ -match '^\s*GatewayPorts\s+(\S+)' } | Select-Object -Last 1) $permTun = ($c | Where-Object { $_ -match '^\s*PermitTunnel\s+(\S+)' } | Select-Object -Last 1) if ($tcpFwd) { Info "sshd_config: $($tcpFwd.Trim())" } if ($gwPorts) { Info "sshd_config: $($gwPorts.Trim())" } if ($permTun) { Info "sshd_config: $($permTun.Trim())" if ($permTun -notmatch 'no\s*$') { MarkIssue "SSH PermitTunnel enabled - sshd can build a layer-3 VPN" } } if (-not $tcpFwd -or $tcpFwd -match 'yes') { MarkWarn "SSH TCP forwarding is enabled (default) - any SSH user can create a SOCKS proxy or port forward" } } else { MarkWarn "sshd is running but sshd_config was not readable - tunnelling policy unknown" } } # live ssh tunnels launched from this host $sshProc = Get-CimInstance Win32_Process -Filter "Name='ssh.exe' OR Name='autossh.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match '\s-[DLRW]\s*\d' } if ($sshProc) { Say " Live SSH tunnels / SOCKS proxies:" foreach ($p in $sshProc) { Info $p.CommandLine } MarkIssue "Active SSH tunnel(s) or SOCKS proxy running via ssh -D/-L/-R" } if (-not $tunAdapters -and -not $svc -and -not $proc -and -not $installed -and -not $sshProc) { MarkOK "No VPN/tunnel/proxy indicators found on this host" } } # --------------------------------------------------------------------------- Section "Public IP / geo / NAT" if ($Offline) { Info "skipped - offline mode" } else { try { $pub = (Invoke-WebRequest -Uri "https://icanhazip.com" -UseBasicParsing -TimeoutSec 6).Content.Trim() if ($pub -match '^\d{1,3}(\.\d{1,3}){3}$') { Say (" Public (external) IPv4: {0}" -f $pub); MarkOK "Public IPv4 reachable ($pub)" if ($script:PrimaryIP) { if ($pub -eq $script:PrimaryIP) { MarkOK "External IP matches local primary IP - no NAT/proxy in front (direct public binding)" } else { MarkWarn "External IP ($pub) differs from local primary IP ($($script:PrimaryIP)) - traffic is NATed or via a proxy/edge" } } try { $geo = Invoke-RestMethod -Uri "https://ipinfo.io/$pub/json" -TimeoutSec 6 if ($geo.country) { Say (" Geo: {0}{1}{2}" -f $geo.country, $(if($geo.city){" / $($geo.city)"}), $(if($geo.org){" ($($geo.org))"})) if ($ExpectedGeo) { if ($geo.country.ToUpper() -eq $ExpectedGeo.ToUpper()) { MarkOK "Public IP geolocates to $($geo.country), matching expected $($ExpectedGeo.ToUpper())" } else { MarkIssue "Public IP geolocates to $($geo.country) but you expected $($ExpectedGeo.ToUpper()) - IP is NOT in the required location" } } } } catch { MarkWarn "Could not determine geolocation (ipinfo.io unreachable or rate-limited)" } } else { MarkWarn "Could not confirm public IPv4" } } catch { MarkWarn "No egress to icanhazip.com" } } # --------------------------------------------------------------------------- if (-not [string]::IsNullOrWhiteSpace($Domain)) { Section "DNS for $Domain" # Resolve-DnsName is not present on every host (nor on PowerShell Core for # non-Windows), so fall back to the .NET resolver before declaring failure. $ips = @() $a = Resolve-DnsName -Name $Domain -Type A -ErrorAction SilentlyContinue if ($a) { $ips = ($a | Where-Object { $_.IPAddress }).IPAddress } if (-not $ips -or $ips.Count -eq 0) { try { $ips = [System.Net.Dns]::GetHostAddresses($Domain) | ForEach-Object { $_.IPAddressToString } } catch { $ips = @() } } if ($ips -and $ips.Count -gt 0) { Say (" Resolves to: {0}" -f ($ips -join ", ")) MarkOK "$Domain resolves" } else { MarkIssue "$Domain does not resolve" } if ($RunTls) { Section "TLS handshake for ${Domain}:443" try { $tcp = New-Object System.Net.Sockets.TcpClient($Domain, 443) $ssl = New-Object System.Net.Security.SslStream($tcp.GetStream(), $false, ({ $true })) $ssl.AuthenticateAsClient($Domain) $cert = $ssl.RemoteCertificate $c2 = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($cert) Say (" Subject: {0}" -f $c2.Subject) Say (" Issuer: {0}" -f $c2.Issuer) Say (" Expires: {0}" -f $c2.NotAfter) if ($c2.NotAfter -gt (Get-Date)) { MarkOK "TLS handshake OK, certificate valid until $($c2.NotAfter)" } else { MarkIssue "Certificate is EXPIRED ($($c2.NotAfter))" } $ssl.Close(); $tcp.Close() } catch { MarkIssue "TLS handshake to ${Domain}:443 failed: $($_.Exception.Message)" } } Section "Web reachability for $Domain" $webCode = $null try { $resp = Invoke-WebRequest -Uri "https://$Domain/" -UseBasicParsing -TimeoutSec 8 -MaximumRedirection 5 $webCode = [int]$resp.StatusCode Say (" https://{0} -> HTTP {1}" -f $Domain, $webCode) MarkOK "Site responds ($webCode)" } catch { $webCode = [int]($_.Exception.Response.StatusCode.value__) Say (" https://{0} -> HTTP {1}" -f $Domain, $webCode) MarkIssue ("Site request failed: {0}" -f $_.Exception.Message) } # If unreachable, trace where it breaks if (-not ($webCode -ge 200 -and $webCode -lt 400)) { Section "Site unreachable - tracing the break point for $Domain" $siteIp = (Resolve-DnsName -Name $Domain -Type A -ErrorAction SilentlyContinue | Where-Object { $_.IPAddress } | Select-Object -First 1).IPAddress if (-not $siteIp) { try { $siteIp = ([System.Net.Dns]::GetHostAddresses($Domain) | Select-Object -First 1).IPAddressToString } catch { $siteIp = $null } } if (-not $siteIp) { MarkIssue "Break at DNS: $Domain does not resolve - fix the A/AAAA record" } else { Say " Resolves to: $siteIp" $tcp443 = Test-NetConnection -ComputerName $Domain -Port 443 -WarningAction SilentlyContinue if ($tcp443.TcpTestSucceeded) { MarkOK "TCP/443 opens to $siteIp - server reachable, so the problem is above TCP (web server, vhost, WAF, or app returning $webCode)" } else { MarkIssue "Break at TCP: cannot open port 443 to $siteIp - firewall/host down/port closed" Say " Route toward ${siteIp}:" Say ((Test-NetConnection -ComputerName $siteIp -TraceRoute -WarningAction SilentlyContinue).TraceRoute -join " -> ") } } # is it this server's egress, or the site? # Confirmed against three control hosts so one blocked endpoint cannot # produce a false "your internet is down" verdict. $rc = Verify3 "control hosts" @( { (Test-NetConnection -ComputerName "1.1.1.1" -Port 443 -WarningAction SilentlyContinue).TcpTestSucceeded }, { (Test-NetConnection -ComputerName "github.com" -Port 443 -WarningAction SilentlyContinue).TcpTestSucceeded }, { (Test-NetConnection -ComputerName "www.google.com" -Port 443 -WarningAction SilentlyContinue).TcpTestSucceeded } ) if ($rc.Pass -eq 0) { MarkIssue "No control host reachable (0/$($rc.Total)) - this server's egress is broken, not $Domain specifically" } elseif ($rc.Pass -lt $rc.Total) { Info "control hosts: $($rc.Pass)/$($rc.Total) reachable - egress mostly works; $Domain looks specific to itself" } else { Info "control hosts: $($rc.Pass)/$($rc.Total) reachable - this server's internet is fine, so the fault is with $Domain" } } Say (" Manual follow-up: https://search.google.com/test/rich-results?url=https://{0}" -f $Domain) } # --------------------------------------------------------------------------- Section "SUMMARY" if ($script:ISSUE.Count -eq 0 -and $script:WARN.Count -eq 0) { Write-Host " VERDICT: ALL CLEAR - no issues or warnings found." -ForegroundColor Green } elseif ($script:ISSUE.Count -eq 0) { Write-Host " VERDICT: OK WITH WARNINGS - no hard issues, review warnings below." -ForegroundColor Yellow } else { Write-Host " VERDICT: ISSUES FOUND - see the issue list below." -ForegroundColor Red } Say (" OK: {0} WARN: {1} ISSUE: {2}" -f $script:OK.Count, $script:WARN.Count, $script:ISSUE.Count) if ($script:ISSUE.Count -gt 0) { Write-Host "`n Issues to look at:" -ForegroundColor Red foreach ($i in $script:ISSUE) { Say " - $i" } } if ($script:WARN.Count -gt 0) { Write-Host "`n Warnings (context-dependent):" -ForegroundColor Yellow foreach ($w in $script:WARN) { Say " - $w" } } Say ("`nFull log: {0}" -f $LogFile) Say ("Finished: {0}" -f (Get-Date)) Say "`nThis script made no configuration changes - read-only inspection only."