# Haven — protect this Windows device (device-level protective DNS) # 1. Sets family-safe DNS on every active network adapter # 2. Locks SafeSearch and YouTube Restricted Mode on in every browser # Run as Administrator. Nothing is installed; both steps are reversible. $dns = @('1.1.1.3', '1.0.0.3') $dns6 = @('2606:4700:4700::1113', '2606:4700:4700::1003') $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Host 'Haven needs an administrator window. Right-click Start, choose "Terminal (Admin)", and run this again.' exit 1 } # One adapter that cannot take DNS settings must not abandon the rest of the # machine. A Windows box with Hyper-V, WSL, Docker, VirtualBox or a VPN client # reports adapters that are "Up" but have no DNS binding at all, and setting # one throws. Unhandled, the script died there — leaving some adapters # unprotected and never reaching the safe-search block below, after telling # the parent it was protecting things. $protected = 0 Get-NetAdapter | Where-Object { $_.Status -eq 'Up' } | ForEach-Object { # Held before the try: inside a catch, $_ is the error record, not the # adapter, so $($_.Name) there prints nothing and the parent cannot tell # which adapter was skipped. $adapter = $_.Name try { Set-DnsClientServerAddress -InterfaceIndex $_.ifIndex -ServerAddresses ($dns + $dns6) -ErrorAction Stop $protected++ Write-Host "Protected: $adapter" } catch { Write-Host "Skipped $adapter - this adapter does not accept DNS settings" } } if ($protected -eq 0) { Write-Host '' Write-Host 'Haven could not set DNS on any adapter. Nothing has been changed.' exit 1 } # --- Lock search and video safety on ------------------------------------- $targets = @( @{ Name = 'Google Search & Images'; Target = 'forcesafesearch.google.com'; Hosts = @('www.google.com', 'google.com', 'www.google.co.uk', 'www.google.ca', 'www.google.com.au') }, @{ Name = 'YouTube'; Target = 'restrict.youtube.com'; Hosts = @('www.youtube.com', 'm.youtube.com', 'youtube.com', 'youtubei.googleapis.com', 'youtube.googleapis.com') }, @{ Name = 'Bing'; Target = 'strict.bing.com'; Hosts = @('www.bing.com', 'bing.com') }, @{ Name = 'DuckDuckGo'; Target = 'safe.duckduckgo.com'; Hosts = @('duckduckgo.com', 'www.duckduckgo.com') } ) $hostsFile = "$env:SystemRoot\System32\drivers\etc\hosts" $lines = @('# BEGIN HAVEN SAFE SEARCH') foreach ($t in $targets) { try { $ip = (Resolve-DnsName -Name $t.Target -Type A -ErrorAction Stop | Where-Object { $_.IPAddress } | Select-Object -First 1).IPAddress } catch { $ip = $null } if ($ip) { Write-Host "Locking safety on: $($t.Name)" foreach ($h in $t.Hosts) { $lines += "$ip`t$h" } } else { Write-Host "Skipped $($t.Name) — could not look up $($t.Target)" } } $lines += '# END HAVEN SAFE SEARCH' $existing = @() if (Test-Path $hostsFile) { $existing = Get-Content $hostsFile } $kept = @(); $skipping = $false foreach ($line in $existing) { if ($line -eq '# BEGIN HAVEN SAFE SEARCH') { $skipping = $true; continue } if ($line -eq '# END HAVEN SAFE SEARCH') { $skipping = $false; continue } if (-not $skipping) { $kept += $line } } Set-Content -Path $hostsFile -Value ($kept + $lines) -Encoding ASCII Clear-DnsClientCache Write-Host '' Write-Host 'Done. Return to the Haven setup page and run the verification check.'