| <#
|
| 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."
|