<# Hardened Windows Server 2022 – Tailscale-only RDP Version 2025-06-26-rev11 Changes: - Added error handling with Try/Catch blocks - Improved verbose and error output - Removed forced reboot - Refactored firewall cleanup to only remove previous custom rules - Warn against deleting all non‑script/Veeam rules #> param ( [switch]$VerboseMode ) # Helper for logging function Log-Info { param($Message) Write-Host "[INFO] $Message" } function Log-Error { param($Message) Write-Host "[ERROR] $Message" -ForegroundColor Red } # ── 0. Configuration ────────────────────────────────────────────────────── $ErrorActionPreference = 'Stop' $stamp = (Get-Date).ToString('yyyyMMdd-HHmmss') $backupFile = "C:\FirewallBackup-$stamp.wfw" $regBackup = "C:\RegBackup-$stamp.reg" $logFile = "$env:SystemRoot\System32\LogFiles\Firewall\pfirewall.log" $tailSubnet = '100.64.0.0/10' # your Tailnet $fwGroup = 'CustomHardening' if ($VerboseMode) { $PSBoundParameters['Verbose'] = $true } # Confirm dangerous actions Log-Info "This script will set DefaultInbound to Block and may remove existing custom rules." if (-not (Read-Host "Proceed? (Y/N)") -match '^[Yy]') { Log-Info "Aborting per user request." return } # ── 1. Backups ───────────────────────────────────────────────────────────── try { Log-Info "Exporting registry backup to $regBackup" reg.exe export HKLM $regBackup /y | Out-Null Log-Info "Exporting firewall settings to $backupFile" netsh advfirewall export $backupFile | Out-Null } catch { Log-Error "Backup failed: $_" return } # ── 2. IPv6 preference ───────────────────────────────────────────────────── try { $ipv6Key = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters' if (-not (Test-Path $ipv6Key)) { New-Item -Path $ipv6Key -Force | Out-Null } New-ItemProperty -Path $ipv6Key -Name DisabledComponents -Type DWord -Value 0x20 -Force | Out-Null Log-Info "IPv6 preference set to prefer IPv4." } catch { Log-Error "Failed to set IPv6 preference: $_" } # ── 3. Default-deny inbound ───────────────────────────────────────────────── try { Set-NetFirewallProfile -Profile Domain,Private,Public -DefaultInboundAction Block -DefaultOutboundAction Allow -Verbose:$VerboseMode Log-Info "Default inbound=Block, outbound=Allow applied." } catch { Log-Error "Failed to set default policy: $_" } # ── 4. Cleanup old custom rules ────────────────────────────────────────────── try { $oldRules = Get-NetFirewallRule | Where-Object { $_.Group -eq $fwGroup } if ($oldRules) { Log-Info "Removing $($oldRules.Count) existing custom rules in group '$fwGroup'." $oldRules | Remove-NetFirewallRule } else { Log-Info "No existing custom rules in group '$fwGroup' to remove." } } catch { Log-Error "Cleanup of old rules failed: $_" } # Warning about deleting all others Log-Info "**Note**: Deleting all rules not in group '$fwGroup' or 'Veeam Networking' is risky and may break OS services." # ── 5. Block common attack ports ───────────────────────────────────────────── $blockPorts = @( @{N='TCP 135 RPC'; P='TCP'; Port='135'} @{N='TCP 139 NetBIOS'; P='TCP'; Port='139'} @{N='TCP 445 SMB'; P='TCP'; Port='445'} @{N='UDP 137-138 NetBIOS'; P='UDP'; Port='137-138'} @{N='UDP 5355 LLMNR'; P='UDP'; Port='5355'} @{N='UDP 5357 WSD'; P='UDP'; Port='5357'} @{N='UDP 1900 SSDP'; P='UDP'; Port='1900'} ) foreach ($b in $blockPorts) { try { if (-not (Get-NetFirewallRule -DisplayName "Block $($b.N)" -ErrorAction SilentlyContinue)) { New-NetFirewallRule -DisplayName "Block $($b.N)" -Group $fwGroup -Direction Inbound -Protocol $b.P -LocalPort $b.Port -Action Block -Verbose:$VerboseMode Log-Info "Added block rule for $($b.N)." } } catch { Log-Error "Failed to add block rule for $($b.N): $_" } } # ── 6. Allow Tailnet and essential inbound ────────────────────────────────── $rules = @( @{Name='Allow RDP TCP 3389 (Tailnet)'; Protocol='TCP'; Port=3389}, @{Name='Allow RDP UDP 3389 (Tailnet)'; Protocol='UDP'; Port=3389} ) foreach ($r in $rules) { try { New-NetFirewallRule -DisplayName $r.Name -Group $fwGroup -Direction Inbound -Protocol $r.Protocol -LocalPort $r.Port -RemoteAddress $tailSubnet -Action Allow -Verbose:$VerboseMode Log-Info "Added $($r.Name)." } catch { Log-Error "Failed to add $($r.Name): $_" } } # Allow Tailscale process try { $tsExe = (Get-Command tailscaled.exe -ErrorAction SilentlyContinue).Source if ($tsExe) { New-NetFirewallRule -DisplayName 'Allow tailscaled.exe inbound' -Group $fwGroup -Program $tsExe -Direction Inbound -Action Allow -Verbose:$VerboseMode New-NetFirewallRule -DisplayName 'Allow tailscaled.exe outbound' -Group $fwGroup -Program $tsExe -Direction Outbound -Action Allow -Verbose:$VerboseMode Log-Info "Added tailscaled.exe allow rules." } else { Log-Info "tailscaled.exe not found; skipping." } } catch { Log-Error "Failed to add tailscaled.exe rules: $_" } # ── 7. Service hardening, SMB1 disable, etc. ───────────────────────────────── # ... Additional segments would also include Try/Catch and Log calls # ── X. Final output ───────────────────────────────────────────────────────── Log-Info "Firewall hardening script completed." Log-Info "Backup files: $backupFile, $regBackup" # Reboot removal: intentional skip Log-Info "Reboot step has been removed. Please reboot manually if required."