Set-DynamicIPDoHServer.psm1
|
# test if the session has administrator privileges # https://devblogs.microsoft.com/scripting/check-for-admin-credentials-in-a-powershell-script/ If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(` [Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Warning "You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!" Break } function Set-DynamicIPDoHServer { [Alias("set-ddoh")] [CmdletBinding( HelpURI = "https://github.com/HotCakeX/Set-DynamicIPDoHServer" ) ] param ( [Parameter(Mandatory = $true)][String]$DoHTemplate, [Parameter(Mandatory = $true)][String]$DoHDomain ) # DoH template must start with "HTTPS:// and needs a / after the TLD. the Add-DnsClientDohServerAddress cmdlet will fail if there is no / after the TLD" if ($dohTemplate -notmatch '^https\:\/\/.+\..+\/.*') { write-host "DNS over HTTPS (DoH) template starts with HTTPS:// and needs a / after the TLD" -ForegroundColor Magenta Break } # DoH domain must have a proper TLD if ($DohDomain -notmatch '^.+\..+') { write-host "DoH Domain isn't right" -ForegroundColor Magenta Break } # error handling for the entire function - to make sure there is no error before attempting to create the scheduled task try { # get the currently active network interface/adapter that is being used for Internet access # This gets the top most active adapter based on route metric $ActiveNetworkInterface = Get-NetRoute -DestinationPrefix '0.0.0.0/0', '::/0' | Sort-Object -Property { $_.InterfaceMetric + $_.RouteMetric } -Top 1 -PipelineVariable ActiveAdapter | Get-NetAdapter -ErrorAction SilentlyContinue | Where-Object { $_.ifIndex -eq $ActiveAdapter.ifIndex } # check if the top most active adapter that we got has an interface index # Windows built-in VPN client connections don't have interface index and don't appear in Get-Netadapter results if (!$ActiveNetworkInterface) { Write-Host "This adapter doesn't even exist in get-Netadapter results and doesn't have interface index, must be built-in Windows VPN client adapter" -ForegroundColor Blue # then we get the 2nd adapter from the top $ActiveNetworkInterface = Get-NetRoute -DestinationPrefix '0.0.0.0/0', '::/0' | Sort-Object -Property { $_.InterfaceMetric + $_.RouteMetric } -Top 2 | select-Object -skip 1 | select-Object -first 1 -PipelineVariable ActiveAdapter | Get-NetAdapter | Where-Object { $_.ifIndex -eq $ActiveAdapter.ifIndex } } # if the top most adapter that we got has an interface index else { # check if the detected active interface from the previous step is virtual, if it is, checks if it's an external virtual Hyper-V network adapter or VPN virtual network adapter if ((Get-NetAdapter | Where-Object { $_.InterfaceGuid -eq $ActiveNetworkInterface.InterfaceGuid }).Virtual) { Write-Host "Interface is virtual, trying to find out if it's a VPN virtual adapter or Hyper-V External virtual switch" -ForegroundColor DarkYellow # if it's an external virtual Hyper-V network adapter, it must be the correct adapter if ($ActiveNetworkInterface.InterfaceDescription -like "*Hyper-V Virtual Ethernet Adapter*" ) { Write-Host "The detected active network adapter is virtual, it's Hyper-V External switch" -ForegroundColor Blue $ActiveNetworkInterface = $ActiveNetworkInterface } # if the detected active network adapter is virtual but Not virtual external Hyper-V network adapter, which means it is VPN virtual network adapter (but not Windows built-in VPN client), # choose the second prioritized adapter/interface based on route metric # tested with Cloudflare WARP (that doesn't create a separate adapter), Wintun, TAP, OpenVPN and has been always successful in detecting the correct network adapter/interface else { write-host "Detected active network adapter is virtual but not virtual Hyper-V adapter, most likely a VPN virtual network adapter, choosing the second prioritized adapter/interface based on route metric" -ForegroundColor Cyan $ActiveNetworkInterface = Get-NetRoute -DestinationPrefix '0.0.0.0/0', '::/0' | Sort-Object -Property { $_.InterfaceMetric + $_.RouteMetric } -Top 2 | select-Object -skip 1 | select-Object -first 1 -PipelineVariable ActiveAdapter | Get-NetAdapter | Where-Object { $_.ifIndex -eq $ActiveAdapter.ifIndex } } } } write-host "This is the final detected network adapter this module is going to set Secure DNS for" -ForegroundColor DarkMagenta $ActiveNetworkInterface # luckily, it's not normally possible to change description of network interfaces/adapters # so it is a solid criteria for choosing our network adapter/interface # https://serverfault.com/questions/862065/changing-nic-interface-descriptions-in-windows#:~:text=You%20can%27t%20change%20the%20name%20of%20the%20NICs,you%27ll%20have%20to%20do%20lots%20of%20name%20swapping # check if there is any IP address already associated with "$DoHTemplate" template $oldIPs = (Get-DnsClientDohServerAddress | Where-Object { $_.dohTemplate -eq $DoHTemplate }).serveraddress # if there is, remove them if ($oldIPs) { $oldIPs | ForEach-Object { remove-DnsClientDohServerAddress -ServerAddress $_ } } # reset the network adapter's DNS servers back to default to take care of any IPv6 strays Set-DnsClientServerAddress -InterfaceIndex $ActiveNetworkInterface.ifIndex -ResetServerAddresses -ErrorAction Stop # only uncomment for debugging purposes # Write-Host "info about the selected network interface/adapter" -ForegroundColor Magenta # $ActiveNetworkInterface.Name # $ActiveNetworkInterface.InterfaceGuid # $ActiveNetworkInterface.ifIndex # Enables "TLS_CHACHA20_POLY1305_SHA256" Cipher Suite for Windows 11, if necessary, because it's disabled by default # cURL will need that cipher suite to perform encrypted DNS query, it uses Windows Schannel if (-NOT ((Get-TlsCipherSuite).name -contains "TLS_CHACHA20_POLY1305_SHA256")) { Enable-TlsCipherSuite -Name "TLS_CHACHA20_POLY1305_SHA256" } # delete all other previous DoH settings for ALL Interface - Windows behavior in settings when changing DoH settings is to delete all DoH settings for the interface we are modifying # but we need to delete all DoH settings for ALL interfaces in here because every time we virtualize a network adapter with external switch of Hyper-V, # Hyper-V assigns a new GUID to it, so it's better not to leave any leftover in the registry and clean up after ourselves remove-item "HKLM:System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\*" -Recurse # get the new IPv4s for $DoHDomain # we use --ssl-no-revoke because when system DNS is unreachable, CRL check will fail in cURL. # it is OKAY, we're using trusted Cloudflare and Google servers the certificates of which explicitly mention their IP addresses (in Subject Alternative Name) that we are using to connect to them $curlcmd = { param($url) $IPs = curl --ssl-no-revoke --max-time 10 --tlsv1.3 --tls13-ciphers TLS_CHACHA20_POLY1305_SHA256 --http2 -H "accept: application/dns-json" $url; $IPs = ( $IPs | ConvertFrom-Json).answer.data return $IPs } Write-Host "Using the main Cloudflare Encrypted API to resolve $DoHDomain" -ForegroundColor Green; $NewIPsV4 = &$curlcmd "https://1.1.1.1/dns-query?name=$dohdomain&type=A" if (!$NewIPsV4) { Write-Host "First try failed, now using the secondary Encrypted Cloudflare API to to get IPv4s for $DoHDomain" -ForegroundColor Blue; $NewIPsV4 = &$curlcmd "https://1.0.0.1/dns-query?name=$dohdomain&type=A" } if (!$NewIPsV4) { Write-Host "Second try failed, now using the main Encrypted Google API to to get IPv4s for $DoHDomain" -ForegroundColor Yellow; $NewIPsV4 = &$curlcmd "https://8.8.8.8/resolve?name=$dohdomain&type=A" } if (!$NewIPsV4) { Write-Host "Third try failed, now using the second Encrypted Google API to to get IPv4s for $DoHDomain" -ForegroundColor DarkRed; $NewIPsV4 = &$curlcmd "https://8.8.4.4/resolve?name=$dohdomain&type=A" } # loop through each IPv4 $NewIPsV4 | foreach-Object { # defining registry path for DoH settings of the $ActiveNetworkInterface based on its GUID for IPv4 $Path = "HKLM:System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$($ActiveNetworkInterface.InterfaceGuid)\DohInterfaceSettings\Doh\$_" # associating the new IPv4s with our DoH template in Windows DoH template predefined list Add-DnsClientDohServerAddress -ServerAddress $_ -DohTemplate $DoHTemplate -AllowFallbackToUdp $False -AutoUpgrade $True # add DoH settings for the specified Network adapter based on its GUID in registry # value 1 for DohFlags key means use automatic template for DoH, 2 means manual template, since we add our template to Windows, it's predefined so we use value 1 New-Item -Path $Path -Force | Out-Null New-ItemProperty -Path $Path -Name "DohFlags" -Value 1 -PropertyType Qword -Force } # get the new IPv6s for $DoHDomain # we use --ssl-no-revoke because when system DNS is unreachable, CRL check will fail in cURL. # it is OKAY, we're using trusted Cloudflare and Google servers the certificates of which explicitly mention their IP addresses (in Subject Alternative Name) that we are using to connect to them Write-Host "Using the main Cloudflare Encrypted API over $DoHTemplate to resolve $DoHDomain" -ForegroundColor Green; $NewIPsV6 = &$curlcmd "https://1.1.1.1/dns-query?name=$dohdomain&type=AAAA" if (!$NewIPsV6) { Write-Host "First try failed, now using the secondary Encrypted Cloudflare API to to get IPv6s for $DoHDomain" -ForegroundColor Blue; $NewIPsV6 = &$curlcmd "https://1.0.0.1/dns-query?name=$dohdomain&type=AAAA" } if (!$NewIPsV6) { Write-Host "Second try failed, now using the main Encrypted Google API to to get IPv6s for $DoHDomain" -ForegroundColor Yellow; $NewIPsV6 = &$curlcmd "https://8.8.8.8/resolve?name=$dohdomain&type=AAAA" } if (!$NewIPsV6) { Write-Host "Third try failed, now using the second Encrypted Google API to to get IPv6s for $DoHDomain" -ForegroundColor DarkRed; $NewIPsV6 = &$curlcmd "https://8.8.4.4/resolve?name=$dohdomain&type=AAAA" } # loop through each IPv6 $NewIPsV6 | foreach-Object { # defining registry path for DoH settings of the $ActiveNetworkInterface based on its GUID for IPv6 $Path = "HKLM:System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$($ActiveNetworkInterface.InterfaceGuid)\DohInterfaceSettings\Doh6\$_" # associating the new IPv6s with our DoH template in Windows DoH template predefined list Add-DnsClientDohServerAddress -ServerAddress $_ -DohTemplate $DoHTemplate -AllowFallbackToUdp $False -AutoUpgrade $True # add DoH settings for the specified Network adapter based on its GUID in registry # value 1 for DohFlags key means use automatic template for DoH, 2 means manual template, since we already added our template to Windows, it's considered predefined, so we use value 1 New-Item -Path $Path -Force | Out-Null New-ItemProperty -Path $Path -Name "DohFlags" -Value 1 -PropertyType Qword -Force } # gather IPv4s and IPv6s all in one place $NewIPs = $NewIPsV4 + $NewIPsV6 # $NewIPs = $NewIPs -join ',' # apparently that wasn't needed and it already works # this is responsible for making the changes in Windows settings UI > Network and internet > $ActiveNetworkInterface.Name Set-DnsClientServerAddress -ServerAddresses $NewIPs -InterfaceIndex $ActiveNetworkInterface.ifIndex -ErrorAction Stop # clear DNS client Cache Clear-DnsClientCache } catch { write-host "these errors occured after running the module" -ForegroundColor white $_ $ModuleErrors = $_ } # here we enable logging for the event log below (which is disabled by default) and set its log size from the default 1MB to 2MB $logName = 'Microsoft-Windows-DNS-Client/Operational' $log = New-Object System.Diagnostics.Eventing.Reader.EventLogConfiguration $logName $log.MaximumSizeInBytes = 2048000 $log.IsEnabled = $true $log.SaveChanges() if (!$ModuleErrors) { write-host "No errors occured when running the module, creating the scheduled task now if it's not already been created" -ForegroundColor green # create a scheduled task if (-NOT (Get-ScheduledTask -TaskName "Dynamic DoH Server IP check" -ErrorAction SilentlyContinue)) { $action = New-ScheduledTaskAction -Execute "pwsh.exe" -Argument "-executionPolicy bypass -command `"set-ddoh -DoHTemplate '$DoHTemplate' -DoHDomain '$DoHDomain'`"" $TaskPrincipal = New-ScheduledTaskPrincipal -LogonType S4U -UserId $env:USERNAME -RunLevel Highest # trigger 1 $CIMTriggerClass = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskEventTrigger $EventTrigger = New-CimInstance -CimClass $CIMTriggerClass -ClientOnly $EventTrigger.Subscription = @" <QueryList><Query Id="0" Path="Microsoft-Windows-DNS-Client/Operational"><Select Path="Microsoft-Windows-DNS-Client/Operational">*[System[Provider[@Name='Microsoft-Windows-DNS-Client'] and EventID=1013]]</Select></Query></QueryList> "@ $EventTrigger.Enabled = $True $EventTrigger.ExecutionTimeLimit = "PT1M" # trigger 2 $Time = New-ScheduledTaskTrigger ` -Once -At (Get-Date).AddHours(3) ` -RandomDelay (New-TimeSpan -Seconds 30) ` -RepetitionInterval (New-TimeSpan -Hours 6) ` # register the task Register-ScheduledTask -Action $action -Trigger $EventTrigger, $Time -Principal $TaskPrincipal -TaskPath "DDoH" -TaskName "Dynamic DoH Server IP check" -Description "Checks for New IPs of our Dynamic DoH server" # define advanced settings for the task $TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Compatibility Win8 -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 1) # add advanced settings we defined to the task Set-ScheduledTask -TaskPath "DDoH" -TaskName "Dynamic DoH Server IP check" -Settings $TaskSettings } } } |