Main/Set-CustomWinSecureDNS.psm1
function Set-CustomWinSecureDNS { [Alias('Set-CDOH')] [CmdletBinding()] [OutputType([System.String], [Microsoft.Management.Infrastructure.CimInstance])] param ( # checking to make sure the DoH template is valid and not one of the built-in ones [ValidatePattern('^https\:\/\/.+\..+\/.*', ErrorMessage = 'The value provided for the parameter DoHTemplate is not a valid DNS over HTTPS template. Please enter a valid DNS over HTTPS template that starts with https, has a TLD and a slash after it. E.g.: https://template.com/')] [ValidateScript({ $_ -notmatch 'https://(cloudflare-dns|dns\.google|dns\.quad9)\.com/dns-query' }, ErrorMessage = 'The DoH template you selected is one of the Windows built-in ones. Please select a different DoH template or use the Set-BuiltInWinSecureDNS cmdlet.')] [Parameter(Mandatory)][System.String]$DoHTemplate, [ValidateCount(1, 2)][System.Net.IPAddress[]]$IPV4s, [ValidateCount(1, 2)][System.Net.IPAddress[]]$IPV6s ) begin { # Detecting if Verbose switch is used $PSBoundParameters.Verbose.IsPresent ? ([System.Boolean]$Verbose = $true) : ([System.Boolean]$Verbose = $false) | Out-Null # Importing the $PSDefaultParameterValues to the current session, prior to everything else . "$WinSecureDNSMgrModuleRootPath\MainExt\PSDefaultParameterValues.ps1" # Importing the required sub-modules Import-Module -Name "$WinSecureDNSMgrModuleRootPath\Shared\Get-ActiveNetworkAdapterWinSecureDNS.psm1" -Force Import-Module -Name "$WinSecureDNSMgrModuleRootPath\Shared\Get-ManualNetworkAdapterWinSecureDNS.psm1" -Force Import-Module -Name "$WinSecureDNSMgrModuleRootPath\Shared\Select-Option.psm1" -Force Import-Module -Name "$WinSecureDNSMgrModuleRootPath\Shared\Get-IPv6DoHServerIPAddressWinSecureDNSMgr.psm1" -Force Import-Module -Name "$WinSecureDNSMgrModuleRootPath\Shared\Get-IPv4DoHServerIPAddressWinSecureDNSMgr.psm1" -Force [System.Boolean]$AutoDetectDoHIPs = $false # If IP addresses were provided manually by user, verify their version if ($IPV4s) { $IPV4s | ForEach-Object -Process { if ($_.AddressFamily -ne 'InterNetwork') { throw "The IP address $_ is not a valid IPv4 address." } } } if ($IPV6s) { $IPV6s | ForEach-Object -Process { if ($_.AddressFamily -ne 'InterNetworkV6') { throw "The IP address $_ is not a valid IPv6 address." } } } # if no IP addresses were provided manually by user, set the $AutoDetectDoHIPs variable to $True if (!$IPV4s -and !$IPV6s) { $AutoDetectDoHIPs = $True } # Detect the active network adapter automatically [Microsoft.Management.Infrastructure.CimInstance]$ActiveNetworkInterface = Get-ActiveNetworkAdapterWinSecureDNS # Display the detected network adapter and ask the user if it's correct $ActiveNetworkInterface switch (Select-Option -Options 'Yes', 'No - Select Manually', 'Cancel' -Message "`nIs the detected network adapter correct ?") { 'Yes' { $ActiveNetworkInterface = $ActiveNetworkInterface } 'No - Select Manually' { # Detect the active network adapter manually [Microsoft.Management.Infrastructure.CimInstance]$ActiveNetworkInterface = Get-ManualNetworkAdapterWinSecureDNS } 'Cancel' { Write-Host -Object 'Exiting...' -ForegroundColor Yellow [System.Boolean]$ShouldExit = $True return } } # if user chose to cancel the Get-ManualNetworkAdapterWinSecureDNS function, set the $shouldExit variable to $True and exit the function in the Process block if (!$ActiveNetworkInterface) { $ShouldExit = $True; return } # Detect the IP address(s) of the DoH domain automatically if not provided by the user if ($AutoDetectDoHIPs) { # Define the regex for extracting the domain name $DomainExtractionRegex = '(?<=https\:\/\/).+?(?=\/)' # Test if the input matches the regex $DoHTemplate -match $DomainExtractionRegex # Access the matched value $Domain = $Matches[0] Write-Verbose -Message "The extracted domain name is $Domain" # Get the IP addresses of the DoH domain $IPV4s = Get-IPv4DoHServerIPAddressWinSecureDNSMgr -Domain $Domain $IPV6s = Get-IPv6DoHServerIPAddressWinSecureDNSMgr -Domain $Domain # If no IP addresses were found for either versions, exit the function if (($null -eq $IPV4s) -and ($null -eq $IPV6s)) { Throw "No IP addresses were found for the domain $Domain. Please make sure the domain is valid and try again, alternatively you can use the Set-BuiltInWinSecureDNS cmdlet to set one of the built-in DoH templates." # Set the flag to indicate the subsequent blocks should be skipped [System.Boolean]$ShouldExit = $True return } } } process { # if the user selected Cancel, do not proceed with the process block if ($ShouldExit) { Return } # 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 -Process { Remove-DnsClientDohServerAddress -ServerAddress $_ } } Write-Verbose -Message 'Checking if the IP addresses of the currently selected DoH domain already exist and then deleting them' Get-DnsClientDohServerAddress | ForEach-Object -Process { if (($_.ServerAddress -in $IPV4s) -or ($_.ServerAddress -in $IPV6s)) { Remove-DnsClientDohServerAddress -ServerAddress $_.ServerAddress } } Write-Verbose -Message 'Resetting the network adapter DNS servers back to default to take care of any IPv6 strays' Set-DnsClientServerAddress -InterfaceIndex $ActiveNetworkInterface.ifIndex -ResetServerAddresses # 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 -Path 'Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\*' -Recurse if ($null -ne $IPV4s) { Write-Verbose -Message 'Adding the new IPv4 addresses to the DoH template in Windows DoH template predefined list' $IPV4s | ForEach-Object -Process { # defining registry path for DoH settings of the $ActiveNetworkInterface based on its GUID for IPv4 [System.String]$PathV4 = "Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$($ActiveNetworkInterface.InterfaceGuid)\DohInterfaceSettings\Doh\$_" Write-Verbose -Message 'Associating the new IPv4s with the selected DoH template in Windows DoH template predefined list' Add-DnsClientDohServerAddress -ServerAddress $_ -DohTemplate $DoHTemplate -AllowFallbackToUdp $False -AutoUpgrade $True | Out-Null # 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 $PathV4 -Force | Out-Null New-ItemProperty -Path $PathV4 -Name 'DohFlags' -Value '1' -PropertyType 'Qword' -Force | Out-Null } } # Making sure the DoH server supports and has IPv6 addresses if ($null -ne $IPV6s) { Write-Verbose -Message 'Adding the new IPv6 addresses to the DoH template in Windows DoH template predefined list' $IPV6s | ForEach-Object -Process { # defining registry path for DoH settings of the $ActiveNetworkInterface based on its GUID for IPv6 [System.String]$PathV6 = "Registry::HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Dnscache\InterfaceSpecificParameters\$($ActiveNetworkInterface.InterfaceGuid)\DohInterfaceSettings\Doh6\$_" Write-Verbose -Message 'Associating the new IPv6s with the selected DoH template in Windows DoH template predefined list' Add-DnsClientDohServerAddress -ServerAddress $_ -DohTemplate $DoHTemplate -AllowFallbackToUdp $False -AutoUpgrade $True | Out-Null # 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 $PathV6 -Force | Out-Null New-ItemProperty -Path $PathV6 -Name 'DohFlags' -Value '1' -PropertyType 'Qword' -Force | Out-Null } } # gather IPv4s and IPv6s all in one place [System.Net.IPAddress[]]$NewIPs = $IPV4s + $IPV6s # this is responsible for making the changes in Windows settings UI > Network and internet > $ActiveNetworkInterface.Name Set-DnsClientServerAddress -ServerAddresses $NewIPs -InterfaceIndex $ActiveNetworkInterface.ifIndex } end { if ($ShouldExit) { Return } Write-Verbose -Message 'Clearing the DNS client cache' Clear-DnsClientCache Write-Host -Object "DNS over HTTPS has been successfully configured for $($ActiveNetworkInterface.Name) using $DoHTemplate template." -ForegroundColor Green # Define the name and path of the scheduled task for DDoH [System.String]$TaskName = 'Dynamic DoH Server IP check' [System.String]$TaskPath = '\DDoH\' if (Get-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -ErrorAction SilentlyContinue) { Write-Verbose -Message 'Deleting the Dynamic DoH scheduled task because it is no longer needed as a new type of DoH is being used now' Unregister-ScheduledTask -TaskName $TaskName -TaskPath $TaskPath -Confirm:$false } } <# .SYNOPSIS This function is a wrapper around the official Microsoft methods to configure DNS over HTTPS in Windows .LINK https://github.com/HotCakeX/WinSecureDNSMgr .DESCRIPTION This script is a wrapper around the official Microsoft methods to configure DNS over HTTPS in Windows. f no IP address is provided for the DoH template, they will be detected automatically. .FUNCTIONALITY Using official Microsoft methods configures DNS over HTTPS in Windows .PARAMETER DoHProvider The name of the 3 built-in DNS over HTTPS providers: Cloudflare, Google and Quad9 .PARAMETER DoHTemplate Enter a custom DoH template URL that starts with https, has a TLD and a slash after it. E.g.: https://template.com/" .PARAMETER IPV4s Enter 1 or 2 IPv4 and/or IPv6 addresses separated by comma .PARAMETER IPV6s Enter 1 or 2 IPv4 and/or IPv6 addresses separated by comma .PARAMETER Verbose Switch to enable verbose output .EXAMPLE Set-CustomWinSecureDNS -DoHTemplate https://example.com/ Set-CDOH -DoHTemplate https://example.com -IPV4s 1.2.3.4 -IPV6s 2001:db8::8a2e:370:7334 .INPUTS System.String System.Net.IPAddress[] .OUTPUTS Microsoft.Management.Infrastructure.CimInstance System.String #> } |