a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
401 lines
17 KiB
PowerShell
401 lines
17 KiB
PowerShell
######################################################################################
|
|
#### windows-network-smoke-tests.ps1 — Verify Windows network configuration ####
|
|
#### Checks NIC status, IP config, gateway, DNS, DC, firewall, NTP, link speed. ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version: 1.0 ####
|
|
#### ####
|
|
#### Usage: .\windows-network-smoke-tests.ps1 ####
|
|
#### .\windows-network-smoke-tests.ps1 -Gateway 10.0.0.1 ####
|
|
#### .\windows-network-smoke-tests.ps1 -OutputFormat tap ####
|
|
#### ####
|
|
#### See -Help for all options. ####
|
|
######################################################################################
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$Gateway,
|
|
[string]$DnsServer,
|
|
[string]$DomainController,
|
|
[int]$ExpectedLinkSpeedMbps = 1000,
|
|
[string]$NtpServer,
|
|
[string]$TestUrl = "http://www.msftconnecttest.com/connecttest.txt",
|
|
[ValidateSet("text", "tap")]
|
|
[string]$OutputFormat = "text",
|
|
[switch]$NoColor,
|
|
[switch]$Help
|
|
)
|
|
|
|
$ErrorActionPreference = "Continue"
|
|
|
|
# ============================================================================
|
|
# STATE & HELPERS
|
|
# ============================================================================
|
|
|
|
$script:Pass = 0; $script:Fail = 0; $script:Skip = 0; $script:Total = 0
|
|
$script:Results = @(); $script:StartTime = $null
|
|
|
|
function Write-Color {
|
|
param([string]$Text, [string]$Color = "White")
|
|
if ($NoColor) { Write-Host $Text } else { Write-Host $Text -ForegroundColor $Color }
|
|
}
|
|
|
|
function Write-Log { param([string]$Msg) Write-Color "[INFO] $Msg" "Cyan" }
|
|
function Write-Warn { param([string]$Msg) Write-Color "[WARN] $Msg" "Yellow" }
|
|
function Write-Err { param([string]$Msg) Write-Color "[ERROR] $Msg" "Red" }
|
|
|
|
function Record-Pass {
|
|
param([string]$Name, [string]$Detail = "")
|
|
$script:Pass++; $script:Total++
|
|
$script:Results += [PSCustomObject]@{ Status = "PASS"; Name = $Name; Detail = $Detail }
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "ok $($script:Total) - $Name"
|
|
} else {
|
|
$msg = " $(if ($NoColor) { '[PASS]' } else { [char]0x2713 }) $Name"
|
|
if ($Detail) { $msg += " - $Detail" }
|
|
Write-Color $msg "Green"
|
|
}
|
|
}
|
|
|
|
function Record-Fail {
|
|
param([string]$Name, [string]$Detail = "")
|
|
$script:Fail++; $script:Total++
|
|
$script:Results += [PSCustomObject]@{ Status = "FAIL"; Name = $Name; Detail = $Detail }
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "not ok $($script:Total) - $Name"
|
|
if ($Detail) { Write-Host " # $Detail" }
|
|
} else {
|
|
$msg = " $(if ($NoColor) { '[FAIL]' } else { [char]0x2717 }) $Name"
|
|
if ($Detail) { $msg += " - $Detail" }
|
|
Write-Color $msg "Red"
|
|
}
|
|
}
|
|
|
|
function Record-Skip {
|
|
param([string]$Name, [string]$Reason = "")
|
|
$script:Skip++; $script:Total++
|
|
$script:Results += [PSCustomObject]@{ Status = "SKIP"; Name = $Name; Detail = $Reason }
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-Host "ok $($script:Total) - $Name # SKIP $Reason"
|
|
} else {
|
|
$msg = " $(if ($NoColor) { '[SKIP]' } else { [char]0x2298 }) $Name"
|
|
if ($Reason) { $msg += " - $Reason" }
|
|
Write-Color $msg "Yellow"
|
|
}
|
|
}
|
|
|
|
function Write-TapHeader { Write-Host "TAP version 13" }
|
|
|
|
function Write-TapFooter {
|
|
Write-Host "1..$($script:Total)"
|
|
Write-Host "# pass $($script:Pass)"
|
|
Write-Host "# fail $($script:Fail)"
|
|
Write-Host "# skip $($script:Skip)"
|
|
}
|
|
|
|
function Write-Summary {
|
|
$duration = [math]::Floor(((Get-Date) - $script:StartTime).TotalSeconds)
|
|
Write-Host ""
|
|
$separator = [string]::new([char]0x2500, 50)
|
|
Write-Color $separator "White"
|
|
Write-Color "Windows Network Smoke Tests - Summary" "White"
|
|
$summaryLine = " $($script:Pass) passed $($script:Fail) failed $($script:Skip) skipped (${duration}s)"
|
|
Write-Host $summaryLine
|
|
Write-Color $separator "White"
|
|
if ($script:Fail -eq 0) { Write-Color "All tests passed." "Green" }
|
|
else { Write-Color "$($script:Fail) test(s) failed." "Red" }
|
|
}
|
|
|
|
function Show-Usage {
|
|
@"
|
|
Usage: .\windows-network-smoke-tests.ps1 [OPTIONS]
|
|
|
|
Smoke-test Windows network configuration. PowerShell 5.1+, no modules.
|
|
Auto-detects gateway, DNS, DC, and NTP when not specified.
|
|
|
|
Parameters:
|
|
-Gateway STR Default gateway IP (default: auto-detect)
|
|
-DnsServer STR DNS server IP (default: auto-detect)
|
|
-DomainController STR Domain controller hostname/IP (default: auto-detect)
|
|
-ExpectedLinkSpeedMbps N Minimum link speed in Mbps (default: 1000)
|
|
-NtpServer STR NTP server (default: auto-detect from w32tm)
|
|
-TestUrl STR URL for internet test (default: msftconnecttest.com)
|
|
-OutputFormat FORMAT Output: text (default), tap
|
|
-NoColor Disable colored output
|
|
-Help Show this help message
|
|
|
|
Examples:
|
|
.\windows-network-smoke-tests.ps1
|
|
.\windows-network-smoke-tests.ps1 -Gateway 10.0.0.1 -DnsServer 10.0.0.53
|
|
.\windows-network-smoke-tests.ps1 -DomainController dc01.corp.local
|
|
.\windows-network-smoke-tests.ps1 -ExpectedLinkSpeedMbps 10000
|
|
.\windows-network-smoke-tests.ps1 -OutputFormat tap
|
|
.\windows-network-smoke-tests.ps1 -NoColor
|
|
"@
|
|
}
|
|
|
|
# ============================================================================
|
|
# TESTS
|
|
# ============================================================================
|
|
|
|
function Test-NicStatus {
|
|
try {
|
|
$adapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" }
|
|
if ($adapters -and @($adapters).Count -gt 0) {
|
|
$first = @($adapters)[0].Name
|
|
Record-Pass "NIC status ($first is Up)"
|
|
} else {
|
|
Record-Fail "NIC status" "no adapter is Up"
|
|
}
|
|
} catch { Record-Fail "NIC status" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-IpConfiguration {
|
|
try {
|
|
$ips = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop |
|
|
Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" }
|
|
if ($ips) {
|
|
$first = @($ips)[0]
|
|
Record-Pass "IPv4 address assigned ($($first.IPAddress)/$($first.PrefixLength))"
|
|
} else {
|
|
Record-Fail "IPv4 address assigned" "no IPv4 address found"
|
|
}
|
|
} catch { Record-Fail "IPv4 address assigned" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-DefaultGateway {
|
|
$gw = $Gateway
|
|
if (-not $gw) {
|
|
try {
|
|
$route = Get-NetRoute -DestinationPrefix "0.0.0.0/0" -ErrorAction Stop | Select-Object -First 1
|
|
$gw = $route.NextHop
|
|
} catch {
|
|
Record-Fail "default gateway exists" "no default route found"
|
|
return
|
|
}
|
|
}
|
|
if (-not $gw -or $gw -eq "0.0.0.0") {
|
|
Record-Fail "default gateway exists" "no gateway configured"
|
|
return
|
|
}
|
|
Record-Pass "default gateway exists ($gw)"
|
|
try {
|
|
$ping = Test-Connection -ComputerName $gw -Count 2 -Quiet -ErrorAction Stop
|
|
if ($ping) { Record-Pass "default gateway reachable" }
|
|
else { Record-Fail "default gateway reachable" "$gw did not respond" }
|
|
} catch { Record-Fail "default gateway reachable" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-DnsServers {
|
|
$dns = $DnsServer
|
|
if (-not $dns) {
|
|
try {
|
|
$dnsAddrs = Get-DnsClientServerAddress -AddressFamily IPv4 -ErrorAction Stop |
|
|
Where-Object { $_.ServerAddresses.Count -gt 0 } |
|
|
Select-Object -ExpandProperty ServerAddresses -First 1
|
|
if ($dnsAddrs) { $dns = @($dnsAddrs)[0] }
|
|
} catch { }
|
|
}
|
|
if ($dns) { Record-Pass "DNS servers configured ($dns)" }
|
|
else { Record-Fail "DNS servers configured" "no DNS servers found" }
|
|
}
|
|
|
|
function Test-DnsResolution {
|
|
try {
|
|
$result = Resolve-DnsName -Name "www.microsoft.com" -Type A -DnsOnly -ErrorAction Stop
|
|
if ($result) { Record-Pass "DNS resolution (www.microsoft.com)" }
|
|
else { Record-Fail "DNS resolution" "no result for www.microsoft.com" }
|
|
} catch { Record-Fail "DNS resolution" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-DomainController {
|
|
$dc = $DomainController
|
|
if (-not $dc) {
|
|
try {
|
|
$nltest = nltest /dsgetdc: 2>&1
|
|
$dcLine = $nltest | Select-String "DC: \\\\"
|
|
if ($dcLine) { $dc = ($dcLine -replace ".*DC: \\\\", "").Trim() }
|
|
} catch { }
|
|
}
|
|
if (-not $dc) {
|
|
$isDomain = $false
|
|
try {
|
|
$cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
|
|
if ($cs.PartOfDomain) { $isDomain = $true }
|
|
} catch { }
|
|
if (-not $isDomain) {
|
|
Record-Skip "domain controller reachable" "not domain-joined"
|
|
Record-Skip "domain controller LDAP port 389" "not domain-joined"
|
|
} else {
|
|
Record-Fail "domain controller reachable" "domain-joined but DC not found"
|
|
Record-Skip "domain controller LDAP port 389" "DC not identified"
|
|
}
|
|
return
|
|
}
|
|
try {
|
|
$ping = Test-Connection -ComputerName $dc -Count 2 -Quiet -ErrorAction Stop
|
|
if ($ping) { Record-Pass "domain controller reachable ($dc)" }
|
|
else { Record-Fail "domain controller reachable" "$dc did not respond" }
|
|
} catch { Record-Fail "domain controller reachable" $_.Exception.Message }
|
|
try {
|
|
$ldap = Test-NetConnection -ComputerName $dc -Port 389 -WarningAction SilentlyContinue -ErrorAction Stop
|
|
if ($ldap.TcpTestSucceeded) { Record-Pass "domain controller LDAP port 389" }
|
|
else { Record-Fail "domain controller LDAP port 389" "TCP 389 connection failed" }
|
|
} catch { Record-Fail "domain controller LDAP port 389" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-FirewallProfile {
|
|
try {
|
|
$profiles = Get-NetFirewallProfile -ErrorAction Stop | Where-Object { $_.Enabled -eq $true }
|
|
if (-not $profiles) {
|
|
Record-Fail "firewall profile" "no firewall profile is enabled"
|
|
return
|
|
}
|
|
$activeNames = ($profiles | ForEach-Object { $_.Name }) -join ", "
|
|
$hasPublic = $profiles | Where-Object { $_.Name -eq "Public" }
|
|
if ($hasPublic) { Record-Fail "firewall profile ($activeNames)" "Public profile is active - expected Domain or Private" }
|
|
else { Record-Pass "firewall profile ($activeNames)" }
|
|
} catch { Record-Fail "firewall profile" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-FirewallRules {
|
|
$checks = @(
|
|
@{ Name = "RDP"; Pattern = "*Remote Desktop*" }
|
|
@{ Name = "WinRM"; Pattern = "*Windows Remote Management*" }
|
|
@{ Name = "ICMP"; Pattern = "*ICMPv4*" }
|
|
)
|
|
$found = @(); $missing = @()
|
|
foreach ($check in $checks) {
|
|
try {
|
|
$rule = Get-NetFirewallRule -Enabled True -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.DisplayName -like $check.Pattern } | Select-Object -First 1
|
|
if ($rule) { $found += $check.Name } else { $missing += $check.Name }
|
|
} catch { $missing += $check.Name }
|
|
}
|
|
if ($missing.Count -eq 0) { Record-Pass "firewall rules ($($found -join ', '))" }
|
|
elseif ($found.Count -gt 0) { Record-Fail "firewall rules" "missing: $($missing -join ', '); found: $($found -join ', ')" }
|
|
else { Record-Fail "firewall rules" "none of RDP, WinRM, ICMP rules found enabled" }
|
|
}
|
|
|
|
function Test-NtpSync {
|
|
try {
|
|
$status = w32tm /query /status 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Record-Fail "NTP synchronization" "w32tm query failed - is the Windows Time service running?"
|
|
return
|
|
}
|
|
$sourceLine = $status | Select-String "Source:"
|
|
$source = if ($sourceLine) { ($sourceLine -replace "Source:\s*", "").Trim() } else { "unknown" }
|
|
if ($source -match "Free-Running|Local CMOS|VM IC") {
|
|
Record-Fail "NTP synchronization" "source is $source - not synced to a remote server"
|
|
return
|
|
}
|
|
$lastSync = $status | Select-String "Last Successful Sync Time:"
|
|
if ($lastSync) { Record-Pass "NTP synchronized ($source)" }
|
|
else { Record-Fail "NTP synchronization" "no successful sync recorded" }
|
|
} catch { Record-Fail "NTP synchronization" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-TimeDrift {
|
|
$ntp = $NtpServer
|
|
if (-not $ntp) {
|
|
try {
|
|
$cfg = w32tm /query /status 2>&1
|
|
$sourceLine = $cfg | Select-String "Source:"
|
|
if ($sourceLine) { $ntp = ($sourceLine -replace "Source:\s*", "").Trim() }
|
|
} catch { }
|
|
}
|
|
if (-not $ntp -or $ntp -match "Free-Running|Local CMOS|VM IC") {
|
|
Record-Skip "time drift" "no valid NTP source available"
|
|
return
|
|
}
|
|
try {
|
|
$strip = w32tm /stripchart /computer:$ntp /samples:1 /dataonly 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Record-Fail "time drift" "w32tm stripchart failed for $ntp"
|
|
return
|
|
}
|
|
$offsetLine = $strip | Select-String "[\+\-]?\d+\.\d+s" | Select-Object -Last 1
|
|
if ($offsetLine) {
|
|
$match = [regex]::Match($offsetLine, "([\+\-]?\d+\.\d+)s")
|
|
if ($match.Success) {
|
|
$offset = [math]::Abs([double]$match.Groups[1].Value)
|
|
if ($offset -le 1.0) { Record-Pass "time drift (${offset}s offset from $ntp)" }
|
|
else { Record-Fail "time drift" "${offset}s offset exceeds 1.0s threshold" }
|
|
} else { Record-Fail "time drift" "could not parse offset from w32tm output" }
|
|
} else { Record-Fail "time drift" "no offset data in w32tm output" }
|
|
} catch { Record-Fail "time drift" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-LinkSpeed {
|
|
try {
|
|
$adapter = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -First 1
|
|
if (-not $adapter) { Record-Fail "link speed" "no active adapter found"; return }
|
|
$linkSpeedStr = $adapter.LinkSpeed
|
|
$speedMbps = 0
|
|
if ($linkSpeedStr -match "(\d+(\.\d+)?)\s*Gbps") { $speedMbps = [int]([double]$Matches[1] * 1000) }
|
|
elseif ($linkSpeedStr -match "(\d+)\s*Mbps") { $speedMbps = [int]$Matches[1] }
|
|
elseif ($linkSpeedStr -match "(\d+)\s*Kbps") { $speedMbps = [int]([double]$Matches[1] / 1000) }
|
|
if ($speedMbps -ge $ExpectedLinkSpeedMbps) { Record-Pass "link speed ($speedMbps Mbps >= $ExpectedLinkSpeedMbps Mbps)" }
|
|
else { Record-Fail "link speed" "$speedMbps Mbps is below expected $ExpectedLinkSpeedMbps Mbps" }
|
|
} catch { Record-Fail "link speed" $_.Exception.Message }
|
|
}
|
|
|
|
function Test-VlanTagging {
|
|
try {
|
|
$adapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" }
|
|
if (-not $adapters) { Record-Skip "VLAN tagging" "no active adapter"; return }
|
|
$tagged = @($adapters) | Where-Object { $_.VlanID -and $_.VlanID -ne 0 }
|
|
if ($tagged) {
|
|
$first = @($tagged)[0]
|
|
Record-Pass "VLAN tagging ($($first.Name) VLAN $($first.VlanID))"
|
|
} else { Record-Skip "VLAN tagging" "adapter not VLAN-tagged" }
|
|
} catch { Record-Skip "VLAN tagging" "VlanID property not available" }
|
|
}
|
|
|
|
function Test-InternetConnectivity {
|
|
try {
|
|
$response = Invoke-WebRequest -Uri $TestUrl -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop
|
|
if ($response.StatusCode -eq 200) { Record-Pass "internet connectivity (HTTP $($response.StatusCode))" }
|
|
else { Record-Fail "internet connectivity" "HTTP $($response.StatusCode)" }
|
|
} catch { Record-Fail "internet connectivity" $_.Exception.Message }
|
|
}
|
|
|
|
# ============================================================================
|
|
# MAIN
|
|
# ============================================================================
|
|
|
|
if ($Help) { Show-Usage; exit 0 }
|
|
|
|
$script:StartTime = Get-Date
|
|
|
|
if ($OutputFormat -eq "tap") {
|
|
Write-TapHeader
|
|
} else {
|
|
Write-Host ""
|
|
Write-Color "Windows Network Smoke Tests" "White"
|
|
Write-Host "Host: $($env:COMPUTERNAME)"
|
|
Write-Host "Time: $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
|
|
Write-Host ""
|
|
}
|
|
|
|
Test-NicStatus
|
|
Test-IpConfiguration
|
|
Test-DefaultGateway
|
|
Test-DnsServers
|
|
Test-DnsResolution
|
|
Test-DomainController
|
|
Test-FirewallProfile
|
|
Test-FirewallRules
|
|
Test-NtpSync
|
|
Test-TimeDrift
|
|
Test-LinkSpeed
|
|
Test-VlanTagging
|
|
Test-InternetConnectivity
|
|
|
|
if ($OutputFormat -eq "tap") { Write-TapFooter } else { Write-Summary }
|
|
|
|
if ($script:Fail -eq 0) { exit 0 } else { exit 1 }
|