Created
March 17, 2026 09:47
-
-
Save waggertron/4c63b46c16a74d4a6e021b3361851c52 to your computer and use it in GitHub Desktop.
Weyflix Server Full Diagnostic - Run on Windows server as Administrator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @echo off | |
| REM | |
| REM Weyflix Server Full Diagnostic | |
| REM Right-click → Run as Administrator | |
| REM | |
| PowerShell.exe -ExecutionPolicy Bypass -File "%~dp0server-full-diagnostic.ps1" | |
| if %ERRORLEVEL% NEQ 0 ( | |
| echo. | |
| echo [ERROR] Could not run diagnostic script. | |
| echo Make sure server-full-diagnostic.ps1 is in the same folder. | |
| pause | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # | |
| # Weyflix Server Full Diagnostic | |
| # Run on the Windows media server as Administrator | |
| # | |
| # Right-click → Run with PowerShell (as Administrator) | |
| # Results saved to Desktop | |
| # | |
| $outputFile = "$env:USERPROFILE\Desktop\weyflix-server-diagnostic-$(Get-Date -Format 'yyyyMMdd-HHmmss').txt" | |
| function Log { | |
| param([string]$msg, [string]$color = "White") | |
| Write-Host $msg -ForegroundColor $color | |
| $msg | Out-File -FilePath $outputFile -Append -Encoding utf8 | |
| } | |
| function LogSection { | |
| param([string]$title) | |
| Log "" | |
| Log "============================================" | |
| Log " $title" | |
| Log "============================================" | |
| } | |
| # Config | |
| $portMap = [ordered]@{ | |
| 32400 = "Plex" | |
| 8989 = "Sonarr" | |
| 7878 = "Radarr" | |
| 6789 = "NZBGet" | |
| 9696 = "Prowlarr" | |
| 9876 = "Weyflix Agent" | |
| } | |
| > $outputFile | |
| Clear-Host | |
| Log "================================================================" "Cyan" | |
| Log " WEYFLIX SERVER FULL DIAGNOSTIC" "Cyan" | |
| Log " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" "Gray" | |
| Log "================================================================" "Cyan" | |
| # ── 1. SYSTEM INFO ── | |
| LogSection "1. SYSTEM INFO" | |
| $os = Get-CimInstance Win32_OperatingSystem | |
| $cpu = Get-CimInstance Win32_Processor | Select-Object -First 1 | |
| $uptime = (Get-Date) - $os.LastBootUpTime | |
| $totalRAM = [math]::Round($os.TotalVisibleMemorySize / 1MB, 1) | |
| $freeRAM = [math]::Round($os.FreePhysicalMemory / 1MB, 1) | |
| $usedRAM = [math]::Round($totalRAM - $freeRAM, 1) | |
| Log " Hostname : $env:COMPUTERNAME" | |
| Log " OS : $($os.Caption) $($os.Version)" | |
| Log " CPU : $($cpu.Name)" | |
| Log " RAM : ${usedRAM}GB used / ${totalRAM}GB total (${freeRAM}GB free)" | |
| Log " Uptime : $($uptime.Days)d $($uptime.Hours)h $($uptime.Minutes)m" | |
| # CPU load | |
| $cpuLoad = (Get-CimInstance Win32_Processor | Measure-Object -Property LoadPercentage -Average).Average | |
| $cpuColor = if ($cpuLoad -gt 80) { "Red" } elseif ($cpuLoad -gt 50) { "Yellow" } else { "Green" } | |
| Log " CPU Load : ${cpuLoad}%" $cpuColor | |
| # ── 2. NETWORK INFO ── | |
| LogSection "2. NETWORK" | |
| # Local IPs | |
| $adapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | |
| foreach ($adapter in $adapters) { | |
| $ip = (Get-NetIPAddress -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue | Select-Object -First 1).IPAddress | |
| $mtu = (Get-NetIPInterface -InterfaceIndex $adapter.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue).NlMtu | |
| $mtuWarn = if ($mtu -gt 1500) { " *** JUMBO FRAMES - may break Plex ***" } else { "" } | |
| Log " $($adapter.Name): $ip (MTU: $mtu$mtuWarn, Speed: $($adapter.LinkSpeed))" | |
| } | |
| # Gateway | |
| $gateway = (Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction SilentlyContinue | Select-Object -First 1).NextHop | |
| Log " Gateway : $gateway" | |
| # DNS | |
| $dns = (Get-DnsClientServerAddress -AddressFamily IPv4 | Where-Object { $_.ServerAddresses.Count -gt 0 } | Select-Object -First 1).ServerAddresses -join ", " | |
| Log " DNS Servers : $dns" | |
| # Public IP | |
| Log "" | |
| try { | |
| $publicIP = (Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing -TimeoutSec 10).Content | |
| Log " Public IP : $publicIP" "Green" | |
| } catch { | |
| Log " Public IP : CANNOT DETERMINE" "Red" | |
| $publicIP = $null | |
| } | |
| # Server IP for comparisons | |
| $serverIP = ($adapters | ForEach-Object { | |
| Get-NetIPAddress -InterfaceIndex $_.ifIndex -AddressFamily IPv4 -ErrorAction SilentlyContinue | |
| } | Where-Object { $_.IPAddress -notlike "127.*" } | Select-Object -First 1).IPAddress | |
| Log " Server LAN : $serverIP" | |
| # ── 3. SERVICE PORTS ── | |
| LogSection "3. SERVICE PORTS" | |
| $allUp = $true | |
| foreach ($port in $portMap.Keys) { | |
| $name = $portMap[$port] | |
| $conn = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue | |
| if ($conn) { | |
| $proc = Get-Process -Id $conn[0].OwningProcess -ErrorAction SilentlyContinue | |
| $bind = $conn[0].LocalAddress | |
| $bindNote = if ($bind -eq "0.0.0.0" -or $bind -eq "::") { "(all interfaces)" } else { "(bound to $bind only!)" } | |
| Log " [OK] $name`:$port → $($proc.ProcessName) PID:$($conn[0].OwningProcess) $bindNote" "Green" | |
| } else { | |
| Log " [DOWN] $name`:$port → NOT LISTENING" "Red" | |
| $allUp = $false | |
| } | |
| } | |
| if (-not $allUp) { | |
| Log "" | |
| Log " >>> Some services are down! Check if they are running. <<<" "Red" | |
| } | |
| # ── 4. SERVICE API HEALTH ── | |
| LogSection "4. SERVICE API HEALTH" | |
| $apiChecks = @( | |
| @{ Name="Plex"; URL="http://localhost:32400/identity" }, | |
| @{ Name="Sonarr"; URL="http://localhost:8989/ping" }, | |
| @{ Name="Radarr"; URL="http://localhost:7878/ping" }, | |
| @{ Name="NZBGet"; URL="http://localhost:6789/" }, | |
| @{ Name="Prowlarr"; URL="http://localhost:9696/ping" }, | |
| @{ Name="Agent"; URL="http://localhost:9876/health" } | |
| ) | |
| foreach ($check in $apiChecks) { | |
| try { | |
| $sw = [System.Diagnostics.Stopwatch]::StartNew() | |
| $r = Invoke-WebRequest -Uri $check.URL -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop | |
| $sw.Stop() | |
| Log " [OK] $($check.Name) → HTTP $($r.StatusCode) ($($sw.ElapsedMilliseconds)ms)" "Green" | |
| } catch { | |
| $code = if ($_.Exception.Response) { [int]$_.Exception.Response.StatusCode } else { "TIMEOUT" } | |
| Log " [FAIL] $($check.Name) → $code" "Red" | |
| } | |
| } | |
| # ── 5. FIREWALL ── | |
| LogSection "5. FIREWALL" | |
| # Profile status | |
| Get-NetFirewallProfile | ForEach-Object { | |
| $status = if ($_.Enabled) { "[ON] " } else { "[OFF]" } | |
| $color = if ($_.Enabled) { "Green" } else { "Yellow" } | |
| Log " $status $($_.Name) profile (Default inbound: $($_.DefaultInboundAction))" $color | |
| } | |
| Log "" | |
| # Per-port rules | |
| $missingRules = @() | |
| foreach ($port in $portMap.Keys) { | |
| $name = $portMap[$port] | |
| $rules = Get-NetFirewallPortFilter -ErrorAction SilentlyContinue | | |
| Where-Object { $_.LocalPort -eq "$port" -and $_.Protocol -eq "TCP" } | | |
| Get-NetFirewallRule -ErrorAction SilentlyContinue | | |
| Where-Object { $_.Direction -eq "Inbound" -and $_.Action -eq "Allow" -and $_.Enabled -eq "True" } | |
| # Also check program-based rules | |
| $progRules = Get-NetFirewallRule -ErrorAction SilentlyContinue | | |
| Where-Object { $_.DisplayName -match $name -and $_.Direction -eq "Inbound" -and $_.Action -eq "Allow" -and $_.Enabled -eq "True" } | |
| if ($rules -or $progRules) { | |
| $ruleNames = @() | |
| if ($rules) { $ruleNames += $rules | ForEach-Object { $_.DisplayName } } | |
| if ($progRules) { $ruleNames += $progRules | ForEach-Object { $_.DisplayName } } | |
| Log " [OK] $name`:$port → $($ruleNames -join ', ')" "Green" | |
| } else { | |
| Log " [WARN] $name`:$port → No inbound allow rule found" "Yellow" | |
| $missingRules += @{ Name=$name; Port=$port } | |
| } | |
| } | |
| if ($missingRules.Count -gt 0) { | |
| Log "" | |
| Log " Missing rules can be created with:" "Yellow" | |
| foreach ($mr in $missingRules) { | |
| Log " New-NetFirewallRule -DisplayName '$($mr.Name) (TCP $($mr.Port))' -Direction Inbound -Protocol TCP -LocalPort $($mr.Port) -Action Allow" "Yellow" | |
| } | |
| } | |
| # ── 6. CGNAT / DOUBLE NAT ── | |
| LogSection "6. CGNAT / DOUBLE NAT DETECTION" | |
| if ($publicIP) { | |
| Log " Public IP: $publicIP" | |
| Log " Gateway: $gateway" | |
| Log "" | |
| # Simple double NAT heuristic | |
| if ($gateway -match "^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)") { | |
| Log " Gateway is a private IP (expected for single-NAT setup)" "Green" | |
| } | |
| Log " Running tracert to $publicIP (max 5 hops)..." | |
| $tracert = tracert -d -h 5 $publicIP 2>&1 | |
| foreach ($line in $tracert) { Log " $line" } | |
| $cgnat = $tracert | Select-String "100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\." | |
| if ($cgnat) { | |
| Log "" | |
| Log " [FAIL] CGNAT DETECTED!" "Red" | |
| Log " Your ISP is using Carrier-Grade NAT." "Red" | |
| Log " Port forwarding will NOT work." "Red" | |
| Log " Solutions:" "Yellow" | |
| Log " 1. Contact ISP and request a public/static IP" "Yellow" | |
| Log " 2. Set up Tailscale for remote access" "Yellow" | |
| Log " 3. Set up a VPS reverse proxy" "Yellow" | |
| } else { | |
| Log "" | |
| Log " [OK] No CGNAT detected" "Green" | |
| } | |
| Log "" | |
| Log " To check for Double NAT:" "Gray" | |
| Log " 1. Open router admin: http://$gateway" "Gray" | |
| Log " 2. Find the WAN/Internet IP" "Gray" | |
| Log " 3. If WAN IP ≠ $publicIP → you have Double NAT" "Gray" | |
| Log " 4. Fix: put ISP modem into Bridge Mode" "Gray" | |
| } else { | |
| Log " Skipped (no public IP determined)" "Yellow" | |
| } | |
| # ── 7. PORT FORWARD SELF-TEST ── | |
| LogSection "7. PORT FORWARD SELF-TEST" | |
| if ($publicIP) { | |
| Log " Testing if port 32400 is reachable via your public IP..." | |
| Log " (Tests NAT hairpin + port forwarding from inside the network)" | |
| Log "" | |
| $test = Test-NetConnection -ComputerName $publicIP -Port 32400 -WarningAction SilentlyContinue | |
| if ($test.TcpTestSucceeded) { | |
| Log " [OK] Port 32400 is reachable via $publicIP" "Green" | |
| # Also test the API | |
| try { | |
| $api = Invoke-WebRequest -Uri "http://${publicIP}:32400/identity" -UseBasicParsing -TimeoutSec 10 | |
| Log " [OK] Plex API responds via public IP (HTTP $($api.StatusCode))" "Green" | |
| } catch { | |
| Log " [WARN] Port open but Plex API didn't respond via public IP" "Yellow" | |
| } | |
| } else { | |
| Log " [FAIL] Port 32400 NOT reachable via $publicIP" "Red" | |
| Log "" | |
| Log " Possible causes:" "Yellow" | |
| Log " 1. No port forward configured on router" "Yellow" | |
| Log " 2. Router doesn't support NAT hairpin (test from external network instead)" "Yellow" | |
| Log " 3. ISP blocking port 32400" "Yellow" | |
| Log " 4. CGNAT (see section 6)" "Yellow" | |
| Log "" | |
| Log " Verify from outside: https://canyouseeme.org (test port 32400)" "Gray" | |
| } | |
| } else { | |
| Log " Skipped (no public IP)" "Yellow" | |
| } | |
| # ── 8. DISK SPACE ── | |
| LogSection "8. DISK SPACE" | |
| Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | ForEach-Object { | |
| $total = $_.Used + $_.Free | |
| $pct = [math]::Round(($_.Used / $total) * 100, 1) | |
| $freeGB = [math]::Round($_.Free / 1GB, 1) | |
| $totalGB = [math]::Round($total / 1GB, 1) | |
| $usedGB = [math]::Round($_.Used / 1GB, 1) | |
| $color = if ($pct -gt 90) { "Red" } elseif ($pct -gt 75) { "Yellow" } else { "Green" } | |
| $warn = if ($pct -gt 90) { " *** CRITICAL ***" } elseif ($pct -gt 75) { " * WARNING *" } else { "" } | |
| Log " $($_.Name): ${usedGB}GB / ${totalGB}GB (${pct}% used, ${freeGB}GB free)$warn" $color | |
| } | |
| # ── 9. DISK HEALTH ── | |
| LogSection "9. DISK HEALTH" | |
| try { | |
| Get-PhysicalDisk | ForEach-Object { | |
| $color = if ($_.HealthStatus -eq "Healthy") { "Green" } else { "Red" } | |
| $warn = if ($_.HealthStatus -ne "Healthy") { " *** ATTENTION NEEDED ***" } else { "" } | |
| Log " $($_.FriendlyName) ($($_.MediaType), $([math]::Round($_.Size/1GB))GB) → $($_.HealthStatus)$warn" $color | |
| } | |
| } catch { | |
| Log " Cannot read disk health (need Administrator)" "Yellow" | |
| } | |
| # ── 10. PROCESS STATUS ── | |
| LogSection "10. SERVICE PROCESSES" | |
| $processNames = @( | |
| @{ Search="Plex Media Server"; Display="Plex Media Server" }, | |
| @{ Search="Plex Transcoder"; Display="Plex Transcoder" }, | |
| @{ Search="NzbDrone"; Display="Sonarr" }, | |
| @{ Search="Radarr"; Display="Radarr" }, | |
| @{ Search="nzbget"; Display="NZBGet" }, | |
| @{ Search="Prowlarr"; Display="Prowlarr" } | |
| ) | |
| foreach ($p in $processNames) { | |
| $proc = Get-Process -Name $p.Search -ErrorAction SilentlyContinue | |
| if ($proc) { | |
| $mem = [math]::Round(($proc | Measure-Object WorkingSet64 -Sum).Sum / 1MB, 1) | |
| $startTime = ($proc | Select-Object -First 1).StartTime | |
| $running = if ($startTime) { (Get-Date) - $startTime } else { $null } | |
| $runStr = if ($running) { "$($running.Days)d $($running.Hours)h $($running.Minutes)m" } else { "unknown" } | |
| Log " [OK] $($p.Display) → ${mem}MB RAM, running $runStr" "Green" | |
| } else { | |
| Log " [DOWN] $($p.Display) → NOT RUNNING" "Red" | |
| } | |
| } | |
| # ── 11. ACTIVE CONNECTIONS ── | |
| LogSection "11. ACTIVE CONNECTIONS TO SERVICES" | |
| foreach ($port in $portMap.Keys) { | |
| $name = $portMap[$port] | |
| $conns = Get-NetTCPConnection -LocalPort $port -State Established -ErrorAction SilentlyContinue | |
| $count = ($conns | Measure-Object).Count | |
| if ($count -gt 0) { | |
| $ips = ($conns | ForEach-Object { | |
| $ip = $_.RemoteAddress | |
| $type = if ($ip -match "^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|127\.)") { "LAN" } else { "WAN" } | |
| "$ip($type)" | |
| } | Select-Object -Unique) -join ", " | |
| Log " $name`:$port → $count connection(s): $ips" "Green" | |
| } else { | |
| Log " $name`:$port → no active connections" "Gray" | |
| } | |
| } | |
| # ── 12. PLEX LOG HIGHLIGHTS ── | |
| LogSection "12. RECENT PLEX LOG ENTRIES (remote access related)" | |
| $plexLog = "$env:LOCALAPPDATA\Plex Media Server\Logs\Plex Media Server.log" | |
| if (Test-Path $plexLog) { | |
| $entries = Select-String -Path $plexLog -Pattern "remote|nat|upnp|relay|mapping|external|port.forward" -AllMatches | | |
| Select-Object -Last 20 | |
| if ($entries) { | |
| foreach ($entry in $entries) { | |
| Log " $($entry.Line)" "Gray" | |
| } | |
| } else { | |
| Log " No remote access entries found in recent logs" "Gray" | |
| } | |
| } else { | |
| Log " Plex log not found at expected location" "Yellow" | |
| Log " Expected: $plexLog" "Gray" | |
| } | |
| # ── 13. SUMMARY ── | |
| LogSection "SUMMARY" | |
| $issues = @() | |
| # Check for down services | |
| foreach ($port in $portMap.Keys) { | |
| $name = $portMap[$port] | |
| $conn = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue | |
| if (-not $conn) { $issues += "$name is not running (port $port)" } | |
| } | |
| # Check for missing firewall rules | |
| if ($missingRules.Count -gt 0) { | |
| foreach ($mr in $missingRules) { | |
| $issues += "No firewall rule for $($mr.Name) (port $($mr.Port))" | |
| } | |
| } | |
| # Check CGNAT | |
| if ($cgnat) { $issues += "CGNAT detected - port forwarding won't work" } | |
| # Check port forward | |
| if ($publicIP -and $test -and -not $test.TcpTestSucceeded) { | |
| $issues += "Port 32400 not reachable via public IP" | |
| } | |
| # Check jumbo frames | |
| $jumbo = Get-NetIPInterface -AddressFamily IPv4 -ErrorAction SilentlyContinue | | |
| Where-Object { $_.NlMtu -gt 1500 -and $_.ConnectionState -eq "Connected" } | |
| if ($jumbo) { $issues += "Jumbo frames detected (MTU > 1500) - can break Plex streaming" } | |
| # Check disk space | |
| Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Used -gt 0 } | ForEach-Object { | |
| $total = $_.Used + $_.Free | |
| $pct = [math]::Round(($_.Used / $total) * 100, 1) | |
| if ($pct -gt 90) { $issues += "Drive $($_.Name): is critically full ($pct%)" } | |
| } | |
| # Check disk health | |
| try { | |
| Get-PhysicalDisk | Where-Object { $_.HealthStatus -ne "Healthy" } | ForEach-Object { | |
| $issues += "Disk '$($_.FriendlyName)' health: $($_.HealthStatus)" | |
| } | |
| } catch {} | |
| if ($issues.Count -eq 0) { | |
| Log "" | |
| Log " [OK] Server looks healthy! No issues detected." "Green" | |
| Log "" | |
| Log " If users still can't connect:" "White" | |
| Log " 1. Have them run the user diagnostic script" "White" | |
| Log " 2. Test from an external network (canyouseeme.org port 32400)" "White" | |
| Log " 3. Check router port forwarding config" "White" | |
| Log " 4. Check Plex Settings > Remote Access" "White" | |
| } else { | |
| Log "" | |
| Log " Issues found ($($issues.Count)):" "Red" | |
| foreach ($issue in $issues) { | |
| Log " ✗ $issue" "Yellow" | |
| } | |
| } | |
| Log "" | |
| Log "================================================================" | |
| Log " Results saved to: $outputFile" | |
| Log "================================================================" | |
| Start-Process notepad.exe $outputFile | |
| Write-Host "" | |
| Read-Host "Press Enter to exit" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment