| <#
|
| Hardened Windows Server 2022 – Tailscale-only RDP
|
| Version 2025-06-26-rev10-ascii-verbose
|
| #>
|
|
|
| # ── 0. Variables ────────────────────────────────────────────────────────────
|
| $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'
|
| $disableSpooler = $false # keep Spooler for RDP printing
|
| $autoReboot = $false # disabled automatic reboot
|
|
|
| Write-Host "Starting Windows Server 2022 Hardening Script at $(Get-Date)" -ForegroundColor Green
|
| Write-Host "Script timestamp: $stamp" -ForegroundColor Cyan
|
|
|
| # ── 0a. Backups ─────────────────────────────────────────────────────────────
|
| Write-Host "`n=== Creating System Backups ===" -ForegroundColor Yellow
|
|
|
| try {
|
| reg.exe export HKLM $regBackup /y | Out-Null
|
| if (Test-Path $regBackup) {
|
| Write-Host "SUCCESS: Registry backup created at $regBackup" -ForegroundColor Green
|
| } else {
|
| Write-Host "WARNING: Registry backup may not have been created properly" -ForegroundColor Yellow
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to create registry backup - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| try {
|
| netsh advfirewall export $backupFile | Out-Null
|
| if (Test-Path $backupFile) {
|
| Write-Host "SUCCESS: Firewall backup created at $backupFile" -ForegroundColor Green
|
| } else {
|
| Write-Host "WARNING: Firewall backup may not have been created properly" -ForegroundColor Yellow
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to create firewall backup - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 0b. IPv6 preference (0x20) ──────────────────────────────────────────────
|
| Write-Host "`n=== Configuring IPv6 Settings ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $ipv6Reg = 'HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters'
|
| if (-not (Test-Path $ipv6Reg)) {
|
| New-Item -Path $ipv6Reg -Force | Out-Null
|
| Write-Host "Created IPv6 registry path" -ForegroundColor Cyan
|
| }
|
| New-ItemProperty -Path $ipv6Reg -Name DisabledComponents -Type DWord -Value 0x20 -Force | Out-Null
|
| Write-Host "SUCCESS: IPv6 preference configured (DisabledComponents = 0x20)" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to configure IPv6 settings - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 1. Default-deny inbound ────────────────────────────────────────────────
|
| Write-Host "`n=== Setting Firewall Default Policies ===" -ForegroundColor Yellow
|
|
|
| try {
|
| Set-NetFirewallProfile -Profile Domain,Private,Public -DefaultInboundAction Block -DefaultOutboundAction Allow
|
| Write-Host "SUCCESS: Set default inbound action to BLOCK and outbound to ALLOW for all profiles" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to set firewall default policies - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 2. Remove previous custom rules ─────────────────────────────────────────
|
| Write-Host "`n=== Removing Previous Custom Rules ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $existingRules = Get-NetFirewallRule | Where-Object { $_.Group -eq $fwGroup }
|
| if ($existingRules) {
|
| $ruleCount = ($existingRules | Measure-Object).Count
|
| $existingRules | Remove-NetFirewallRule
|
| Write-Host "SUCCESS: Removed $ruleCount existing custom hardening rules" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: No existing custom hardening rules found to remove" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to remove existing custom rules - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 2c. Disable built-in RDP rules ─────────────────────────────────────────
|
| Write-Host "`n=== Disabling Built-in RDP Rules ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $rdpRules = Get-NetFirewallRule -DisplayName 'Remote Desktop - User Mode *'
|
| if ($rdpRules) {
|
| $rdpRules | Disable-NetFirewallRule
|
| $rdpCount = ($rdpRules | Measure-Object).Count
|
| Write-Host "SUCCESS: Disabled $rdpCount built-in RDP firewall rules" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: No built-in RDP rules found to disable" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to disable built-in RDP rules - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 3. Inbound BLOCK rules ─────────────────────────────────────────────────
|
| Write-Host "`n=== Creating Inbound BLOCK Rules ===" -ForegroundColor Yellow
|
|
|
| $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' }
|
| @{ N='TCP 5985 WinRM' ; P='TCP' ; Port='5985' }
|
| @{ N='TCP 5986 WinRM-SSL' ; P='TCP' ; Port='5986' }
|
| )
|
|
|
| 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
|
| Write-Host "SUCCESS: Created block rule for $($b.N)" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: Block rule for $($b.N) already exists, skipping" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to create block rule for $($b.N) - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| }
|
|
|
| # ── 4. Allow rules (Tailnet only) ───────────────────────────────────────────
|
| Write-Host "`n=== Creating Tailnet Allow Rules ===" -ForegroundColor Yellow
|
|
|
| try {
|
| New-NetFirewallRule -DisplayName 'Allow RDP TCP 3389 (Tailnet)' -Group $fwGroup -Direction Inbound -Protocol TCP -LocalPort 3389 -RemoteAddress $tailSubnet -Action Allow
|
| Write-Host "SUCCESS: Created RDP TCP 3389 allow rule for Tailnet ($tailSubnet)" -ForegroundColor Green
|
|
|
| # Verify the rule was created with correct remote address
|
| $createdRule = Get-NetFirewallRule -DisplayName 'Allow RDP TCP 3389 (Tailnet)' | Get-NetFirewallAddressFilter
|
| if ($createdRule.RemoteAddress -eq $tailSubnet) {
|
| Write-Host " Verified: Remote address correctly set to $tailSubnet" -ForegroundColor Cyan
|
| } else {
|
| Write-Host " WARNING: Remote address not set correctly. Current: $($createdRule.RemoteAddress)" -ForegroundColor Yellow
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to create RDP TCP allow rule - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| try {
|
| New-NetFirewallRule -DisplayName 'Allow RDP UDP 3389 (Tailnet)' -Group $fwGroup -Direction Inbound -Protocol UDP -LocalPort 3389 -RemoteAddress $tailSubnet -Action Allow
|
| Write-Host "SUCCESS: Created RDP UDP 3389 allow rule for Tailnet ($tailSubnet)" -ForegroundColor Green
|
|
|
| # Verify the rule was created with correct remote address
|
| $createdRule = Get-NetFirewallRule -DisplayName 'Allow RDP UDP 3389 (Tailnet)' | Get-NetFirewallAddressFilter
|
| if ($createdRule.RemoteAddress -eq $tailSubnet) {
|
| Write-Host " Verified: Remote address correctly set to $tailSubnet" -ForegroundColor Cyan
|
| } else {
|
| Write-Host " WARNING: Remote address not set correctly. Current: $($createdRule.RemoteAddress)" -ForegroundColor Yellow
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to create RDP UDP allow rule - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| $tsExe = (Get-Command tailscaled.exe -ErrorAction SilentlyContinue).Source
|
| if ($tsExe) {
|
| Write-Host "Found Tailscale daemon at: $tsExe" -ForegroundColor Cyan
|
|
|
| try {
|
| New-NetFirewallRule -DisplayName 'Allow tailscaled.exe inbound' -Group $fwGroup -Program $tsExe -Direction Inbound -Action Allow
|
| Write-Host "SUCCESS: Created inbound allow rule for tailscaled.exe" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to create inbound tailscaled rule - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| if ((Get-NetFirewallProfile -Profile Domain,Private,Public | Where-Object { $_.DefaultOutboundAction -eq 'Block' })) {
|
| try {
|
| New-NetFirewallRule -DisplayName 'Allow tailscaled.exe outbound' -Group $fwGroup -Program $tsExe -Direction Outbound -Action Allow
|
| Write-Host "SUCCESS: Created outbound allow rule for tailscaled.exe" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to create outbound tailscaled rule - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| } else {
|
| Write-Host "INFO: Outbound traffic allowed by default, skipping outbound tailscaled rule" -ForegroundColor Cyan
|
| }
|
| } else {
|
| Write-Host "WARNING: Tailscale daemon (tailscaled.exe) not found in PATH" -ForegroundColor Yellow
|
| }
|
|
|
| try {
|
| New-NetFirewallRule -DisplayName 'Allow ICMPv4 Echo (Tailnet)' -Group $fwGroup -Direction Inbound -Protocol ICMPv4 -IcmpType 8 -RemoteAddress $tailSubnet -Action Allow
|
| Write-Host "SUCCESS: Created ICMPv4 Echo allow rule for Tailnet ($tailSubnet)" -ForegroundColor Green
|
|
|
| # Verify the rule was created with correct remote address
|
| $createdRule = Get-NetFirewallRule -DisplayName 'Allow ICMPv4 Echo (Tailnet)' | Get-NetFirewallAddressFilter
|
| if ($createdRule.RemoteAddress -eq $tailSubnet) {
|
| Write-Host " Verified: Remote address correctly set to $tailSubnet" -ForegroundColor Cyan
|
| } else {
|
| Write-Host " WARNING: Remote address not set correctly. Current: $($createdRule.RemoteAddress)" -ForegroundColor Yellow
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to create ICMPv4 allow rule - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 5. Service hardening ────────────────────────────────────────────────────
|
| Write-Host "`n=== Service Hardening ===" -ForegroundColor Yellow
|
|
|
| $servicesToManual = @(
|
| 'LanmanServer','WMPNetworkSvc','XboxNetApiSvc',
|
| 'CDPSvc','DsSvc','WSDPrintDevice',
|
| 'RemoteRegistry','Fax','TrkWks',
|
| 'SNMP','SNMPTRAP'
|
| )
|
| if ($disableSpooler) { $servicesToManual += 'Spooler' }
|
|
|
| foreach ($svc in $servicesToManual) {
|
| try {
|
| $s = Get-Service -Name $svc -ErrorAction SilentlyContinue
|
| if ($s) {
|
| $originalStatus = $s.Status
|
| $originalStartType = $s.StartType
|
|
|
| if ($s.Status -ne 'Stopped') {
|
| Stop-Service $s -Force
|
| Write-Host " Stopped service: $svc (was $originalStatus)" -ForegroundColor Cyan
|
| }
|
| Set-Service -Name $svc -StartupType Manual
|
| Write-Host "SUCCESS: Configured service $svc - StartType: $originalStartType -> Manual" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: Service $svc not found on this system" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to configure service $svc - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| }
|
|
|
| # ── 5b. Spooler defence (when spooler active)
|
| if (-not $disableSpooler) {
|
| Write-Host "`n=== Configuring Spooler Security ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $ppReg = 'HKLM:\Software\Policies\Microsoft\Windows NT\Printers\PointAndPrint'
|
| if (-not (Test-Path $ppReg)) {
|
| New-Item -Path $ppReg -Force | Out-Null
|
| Write-Host "Created Point and Print registry path" -ForegroundColor Cyan
|
| }
|
| New-ItemProperty -Path $ppReg -Name RestrictDriverInstallationToAdministrators -Type DWord -Value 1 -Force | Out-Null
|
| New-ItemProperty -Path $ppReg -Name NoWarningNoElevationOnInstall -Type DWord -Value 0 -Force | Out-Null
|
| Write-Host "SUCCESS: Configured Point and Print security settings" -ForegroundColor Green
|
|
|
| $spr = Get-NetFirewallRule -DisplayName 'File and Printer Sharing (Spooler Service - RPC)' -ErrorAction SilentlyContinue
|
| if ($spr) {
|
| $spr | Set-NetFirewallRule -RemoteAddress LocalSubnet -Profile Any -Enabled True
|
| Write-Host "SUCCESS: Updated Spooler Service RPC firewall rule to LocalSubnet only" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: Spooler Service RPC firewall rule not found" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to configure spooler security - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| }
|
|
|
| # ── 6. Disable NetBIOS over TCP/IP ──────────────────────────────────────────
|
| Write-Host "`n=== Disabling NetBIOS over TCP/IP ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $adapters = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter 'IPEnabled=True'
|
| $adapterCount = 0
|
| foreach ($adapter in $adapters) {
|
| $result = $adapter.SetTcpipNetbios(2)
|
| if ($result.ReturnValue -eq 0) {
|
| $adapterCount++
|
| }
|
| }
|
| Write-Host "SUCCESS: Disabled NetBIOS over TCP/IP on $adapterCount network adapters" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to disable NetBIOS over TCP/IP - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 7. Remove SMB1 ──────────────────────────────────────────────────────────
|
| Write-Host "`n=== Removing SMB1 Protocol ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $smb1Features = @('SMB1Protocol','SMB1Protocol-Client','SMB1Protocol-Server')
|
| foreach ($feature in $smb1Features) {
|
| try {
|
| $result = Disable-WindowsOptionalFeature -Online -FeatureName $feature -NoRestart -ErrorAction SilentlyContinue
|
| if ($result) {
|
| Write-Host "SUCCESS: Disabled Windows feature: $feature" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: Feature $feature was already disabled or not found" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "WARNING: Could not disable feature $feature - $($_.Exception.Message)" -ForegroundColor Yellow
|
| }
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to remove SMB1 protocol - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ── 8. Disable WSD / SSDP / UPnP services ──────────────────────────────────
|
| Write-Host "`n=== Disabling WSD/SSDP/UPnP Services ===" -ForegroundColor Yellow
|
|
|
| $servicesToDisable = @('SSDPSRV','upnphost','WSDSvc','WSDPrintDevice')
|
| foreach ($serviceName in $servicesToDisable) {
|
| try {
|
| $reg = "HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName"
|
| if (Test-Path $reg) {
|
| Set-ItemProperty $reg -Name Start -Value 4 -Force
|
| Write-Host "SUCCESS: Disabled service $serviceName (Start = 4)" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: Service registry key for $serviceName not found" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to disable service $serviceName - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| }
|
|
|
| # ── 9. RDP channel hardening ───────────────────────────────────────────────
|
| Write-Host "`n=== Hardening RDP Channel Security ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $rdpReg = 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp'
|
| Set-ItemProperty $rdpReg -Name SecurityLayer -Value 2
|
| Set-ItemProperty $rdpReg -Name fAllowSecProtocolNegotiation -Value 1
|
| Set-ItemProperty $rdpReg -Name UserAuthentication -Value 1
|
| Set-ItemProperty $rdpReg -Name MinEncryptionLevel -Value 3
|
| Write-Host "SUCCESS: Configured RDP security settings:" -ForegroundColor Green
|
| Write-Host " SecurityLayer = 2 (TLS)" -ForegroundColor Cyan
|
| Write-Host " fAllowSecProtocolNegotiation = 1 (Enabled)" -ForegroundColor Cyan
|
| Write-Host " UserAuthentication = 1 (NLA Required)" -ForegroundColor Cyan
|
| Write-Host " MinEncryptionLevel = 3 (High)" -ForegroundColor Cyan
|
| } catch {
|
| Write-Host "ERROR: Failed to configure RDP security settings - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ──10. Schannel: disable TLS 1.0/1.1 & weak ciphers ────────────────────────
|
| Write-Host "`n=== Disabling Weak TLS Protocols ===" -ForegroundColor Yellow
|
|
|
| foreach ($proto in 'TLS 1.0','TLS 1.1') {
|
| foreach ($side in 'Client','Server') {
|
| try {
|
| $path = "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\$proto\$side"
|
| if (-not (Test-Path $path)) {
|
| New-Item -Path $path -Force | Out-Null
|
| Write-Host "Created registry path: $path" -ForegroundColor Cyan
|
| }
|
| New-ItemProperty -Path $path -Name Enabled -Type DWord -Value 0 -Force | Out-Null
|
| New-ItemProperty -Path $path -Name DisabledByDefault -Type DWord -Value 1 -Force | Out-Null
|
| Write-Host "SUCCESS: Disabled $proto for $side" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to disable $proto for $side - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| }
|
| }
|
|
|
| Write-Host "`n=== Disabling Weak Cipher Suites ===" -ForegroundColor Yellow
|
|
|
| if (Get-Command Disable-TlsCipherSuite -ErrorAction SilentlyContinue) {
|
| try {
|
| $weakCiphers = (Get-TlsCipherSuite | Where-Object { $_.Name -match 'RC4|3DES|SHA$' })
|
| $disabledCount = 0
|
| foreach ($cipher in $weakCiphers) {
|
| try {
|
| Disable-TlsCipherSuite -Name $cipher.Name -ErrorAction SilentlyContinue
|
| $disabledCount++
|
| Write-Host " Disabled cipher suite: $($cipher.Name)" -ForegroundColor Cyan
|
| } catch {
|
| Write-Host " WARNING: Could not disable cipher suite: $($cipher.Name)" -ForegroundColor Yellow
|
| }
|
| }
|
| Write-Host "SUCCESS: Disabled $disabledCount weak cipher suites" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to disable cipher suites - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| } else {
|
| Write-Host "INFO: Disable-TlsCipherSuite cmdlet not available, using registry method" -ForegroundColor Cyan
|
| try {
|
| $cBase = 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers'
|
| $weakCiphers = @('RC4 128/128','RC4 64/128','RC4 56/128','RC4 40/128','3DES 168/168')
|
| foreach ($c in $weakCiphers) {
|
| $cPath = "$cBase\$c"
|
| if (-not (Test-Path $cPath)) {
|
| New-Item -Path $cPath -Force | Out-Null
|
| Write-Host "Created cipher registry path: $cPath" -ForegroundColor Cyan
|
| }
|
| New-ItemProperty -Path $cPath -Name Enabled -Type DWord -Value 0 -Force | Out-Null
|
| Write-Host "SUCCESS: Disabled cipher: $c" -ForegroundColor Green
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to disable weak ciphers via registry - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
| }
|
|
|
| # ──11. Remote Credential Guard ─────────────────────────────────────────────
|
| Write-Host "`n=== Configuring Remote Credential Guard ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $credReg = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CredentialsDelegation'
|
| if (-not (Test-Path $credReg)) {
|
| New-Item -Path $credReg -Force | Out-Null
|
| Write-Host "Created Credential Delegation registry path" -ForegroundColor Cyan
|
| }
|
| Set-ItemProperty $credReg -Name AllowProtectedCreds -Type DWord -Value 1
|
| Set-ItemProperty $credReg -Name EnableProtection -Type DWord -Value 1
|
| Write-Host "SUCCESS: Enabled Remote Credential Guard protection" -ForegroundColor Green
|
| } catch {
|
| Write-Host "ERROR: Failed to configure Remote Credential Guard - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ──12. Firewall logging (16 MB) + weekly rotation ──────────────────────────
|
| Write-Host "`n=== Configuring Firewall Logging ===" -ForegroundColor Yellow
|
|
|
| try {
|
| Set-NetFirewallProfile -Profile Domain,Private,Public -LogBlocked True -LogAllowed True -LogFileName $logFile -LogMaxSizeKilobytes 16384
|
| Write-Host "SUCCESS: Enabled firewall logging (16MB max, blocked and allowed traffic)" -ForegroundColor Green
|
| Write-Host " Log file location: $logFile" -ForegroundColor Cyan
|
| } catch {
|
| Write-Host "ERROR: Failed to configure firewall logging - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| Write-Host "`n=== Creating Log Rotation Task ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $taskName = 'Rotate-Firewall-Log'
|
| if (-not (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue)) {
|
| $psCmd = '& { Set-NetFirewallProfile -LogAllowed 0 -LogBlocked 0; ' +
|
| '$ts=(Get-Date).ToString("yyyyMMdd-HHmmss"); ' +
|
| "Rename-Item ''$logFile'' (" + '$ts' + " + ''.pfirewall.log''); " +
|
| "New-Item ''$logFile'' -ItemType File -Force | Out-Null; " +
|
| 'Set-NetFirewallProfile -LogAllowed 1 -LogBlocked 1 }'
|
| $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -Command $psCmd"
|
| $trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 03:00
|
| Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -RunLevel Highest -User.SYSTEM
|
| Write-Host "SUCCESS: Created weekly log rotation scheduled task '$taskName'" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: Log rotation task '$taskName' already exists" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to create log rotation task - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ──13. Account-lockout policy ──────────────────────────────────────────────
|
| Write-Host "`n=== Configuring Account Lockout Policy ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $result = & net.exe accounts /lockoutthreshold:10 /lockoutduration:15 /lockoutwindow:15 2>&1
|
| if ($LASTEXITCODE -eq 0) {
|
| Write-Host "SUCCESS: Configured account lockout policy:" -ForegroundColor Green
|
| Write-Host " Lockout threshold: 10 attempts" -ForegroundColor Cyan
|
| Write-Host " Lockout duration: 15 minutes" -ForegroundColor Cyan
|
| Write-Host " Lockout window: 15 minutes" -ForegroundColor Cyan
|
| } else {
|
| Write-Host "WARNING: Account lockout policy command completed with exit code $LASTEXITCODE" -ForegroundColor Yellow
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to configure account lockout policy - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ──14. Disable Remote Assistance rules ─────────────────────────────────────
|
| Write-Host "`n=== Disabling Remote Assistance Rules ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $raRules = Get-NetFirewallRule | Where-Object { $_.DisplayName -like '*Remote Assistance*' }
|
| if ($raRules) {
|
| $raRules | Disable-NetFirewallRule
|
| $raCount = ($raRules | Measure-Object).Count
|
| Write-Host "SUCCESS: Disabled $raCount Remote Assistance firewall rules" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: No Remote Assistance firewall rules found" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "ERROR: Failed to disable Remote Assistance rules - $($_.Exception.Message)" -ForegroundColor Red
|
| }
|
|
|
| # ──14b. Remove/Disable unwanted Built-in Rules ─────────────────────────────
|
|
|
| # 1) Disable entire rule-groups
|
| $groups = @(
|
| # Discovery / Multicast
|
| "mDNS",
|
| "Netzwerkerkennung", # contains LLMNR, SSDP, UPnP, WSD rules
|
| "DIAL-Protokollserver",
|
| "Cast to Device functionality", # original English group
|
| "Wiedergabe auf Gerät", # German group for Play-To
|
|
|
| # File & Print
|
| "Datei- und Druckerfreigabe",
|
|
|
| # Remote-Management
|
| "Windows-Remoteverwaltung",
|
|
|
| # Updates / Delivery
|
| "Übermittlungsoptimierung",
|
|
|
| # Media-Streaming
|
| "Microsoft Media Foundation-Netzwerkquelle",
|
|
|
| # IoT / Miracast
|
| "AllJoyn Router",
|
|
|
| # Server Features, if unnecessary
|
| "Dateiserver-Remoteverwaltung",
|
| "Dynamic Host Configuration-Protokoll" # only disable if static IP
|
| )
|
|
|
| foreach ($g in $groups) {
|
| try {
|
| Get-NetFirewallRule -DisplayGroup $g -ErrorAction SilentlyContinue |
|
| Disable-NetFirewallRule -ErrorAction Stop
|
| Write-Host "SUCCESS: Disabled group '$g'" -ForegroundColor Green
|
| } catch {
|
| Write-Host "WARNING: Group '$g' not found or could not be disabled" -ForegroundColor Yellow
|
| }
|
| }
|
|
|
| # 2) Disable individual rules (no group tag)
|
| $ruleNames = @(
|
| # Core-Networking special paths
|
| "Kernnetzwerk - IP-HTTPS (TCP eingehend)", # DirectAccess / IP-HTTPS
|
| "Kernnetzwerk - Teredo (UDP eingehend)" # Teredo tunneling
|
| )
|
|
|
| foreach ($n in $ruleNames) {
|
| try {
|
| Get-NetFirewallRule -DisplayName $n -ErrorAction SilentlyContinue |
|
| Disable-NetFirewallRule -ErrorAction Stop
|
| Write-Host "SUCCESS: Disabled rule '$n'" -ForegroundColor Green
|
| } catch {
|
| Write-Host "WARNING: Rule '$n' not found or could not be disabled" -ForegroundColor Yellow
|
| }
|
| }
|
|
|
| # ──14c. Catch-all for remaining Play-To rules by DisplayName ───────────────
|
| Write-Host "`n=== Disabling remaining 'Wiedergabe auf Gerät' rules by DisplayName pattern ===" -ForegroundColor Yellow
|
|
|
| try {
|
| $playTo = Get-NetFirewallRule -DisplayName '*Wiedergabe auf Gerät*' -ErrorAction SilentlyContinue
|
| if ($playTo) {
|
| $playTo | Disable-NetFirewallRule -ErrorAction Stop
|
| $count = ($playTo | Measure-Object).Count
|
| Write-Host "SUCCESS: Disabled $count rules matching '*Wiedergabe auf Gerät*'" -ForegroundColor Green
|
| } else {
|
| Write-Host "INFO: No more rules matching '*Wiedergabe auf Gerät*' found" -ForegroundColor Cyan
|
| }
|
| } catch {
|
| Write-Host "WARNING: Error disabling '*Wiedergabe auf Gerät*' rules – $($_.Exception.Message)" -ForegroundColor Yellow
|
| }
|
|
|
| # ──15. Finish ──────────────────────────────────────────────────────────────
|
| Write-Host "`n" + "="*60 -ForegroundColor Yellow
|
| Write-Host "HARDENING SCRIPT COMPLETED SUCCESSFULLY" -ForegroundColor Green
|
| Write-Host "="*60 -ForegroundColor Yellow
|
| Write-Host "Completion time: $(Get-Date)" -ForegroundColor Cyan
|
| Write-Host "Registry backup: $regBackup" -ForegroundColor Cyan
|
| Write-Host "Firewall backup: $backupFile" -ForegroundColor Cyan
|
| Write-Host "`nIMPORTANT: A system reboot is recommended to ensure all changes take effect." -ForegroundColor Yellow
|
| Write-Host "Automatic reboot is DISABLED. Please reboot manually when convenient." -ForegroundColor Yellow
|
| Write-Host "="*60 -ForegroundColor Yellow
|