DnsManager.psm1

#
# DnsManager.psm1
#
# This file contains the functions for the DnsManager PowerShell module.

# Get the module's root directory to locate the DnsPresets.psd1 file
$script:ModuleRoot = $PSScriptRoot
$script:DnsPresetsPath = Join-Path $script:ModuleRoot 'DnsPresets.psd1'

# Load DNS presets from the separate data file
function Get-DnsPresetsData {
    [CmdletBinding()]
    param()
    
    if (-not (Test-Path $script:DnsPresetsPath)) {
        Write-Error "DNS presets file not found at: $script:DnsPresetsPath"
        return $null
    }
    
    try {
        $presets = Import-PowerShellDataFile -Path $script:DnsPresetsPath
        return $presets
    } catch {
        Write-Error "Failed to load DNS presets: $($_.Exception.Message)"
        return $null
    }
}

<#
.SYNOPSIS
    Retrieves and displays the DNS presets from the DnsPresets.psd1 data file.
 
.DESCRIPTION
    This command-line tool fetches a list of predefined DNS server configurations,
    such as Google or Cloudflare, from the DnsPresets.psd1 data file.
 
.EXAMPLE
    Get-DnsPresets
    Displays a table of all available DNS presets with their primary
    and secondary server addresses.
#>

function Get-DnsPresets {
    [CmdletBinding()]
    param()

    $presets = Get-DnsPresetsData

    if ($null -eq $presets) {
        Write-Warning "No DNS presets found in the data file."
        return
    }

    # Create a custom object for each preset and return without formatting
    $presets.Keys | ForEach-Object {
        $presetName = $_
        $presetData = $presets[$presetName]
        [PSCustomObject]@{
            Name = $presetName
            Primary = $presetData.Primary
            Secondary = $presetData.Secondary
        }
    }
}

# This is a private helper function and will not be exported.
# It's used by Set-Dns to resolve a preset name to its IP addresses.
function Get-DnsPreset {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$PresetName
    )

    $presets = Get-DnsPresetsData
    
    if ($null -eq $presets) {
        return $null
    }
    
    if ($presets.ContainsKey($PresetName)) {
        return $presets[$PresetName]
    } else {
        Write-Error "Preset '$PresetName' not found."
        return $null
    }
}

# This is a private helper function and will not be exported.
# It's used by the ArgumentCompleter for tab completion.
function Get-PresetNames {
    param(
        [string]$wordToComplete = ''
    )
    
    $presets = Get-DnsPresetsData
    if ($null -eq $presets) {
        return @()
    }
    
    # Get preset names and filter based on what user has typed
    $presetNames = $presets.Keys
    
    # Filter results based on what the user has typed so far
    if ($wordToComplete) {
        $presetNames | Where-Object { $_ -like "$wordToComplete*" }
    } else {
        $presetNames
    }
}

<#
.SYNOPSIS
    Gets DNS settings for network interfaces.
 
.DESCRIPTION
    This tool retrieves the current DNS server addresses for one or more network
    interfaces on your computer. By default, it lists all active interfaces.
 
.EXAMPLE
    Get-Dns
    Displays the DNS servers for all active network interfaces.
 
.EXAMPLE
    Get-Dns -Interface 'Ethernet'
    Displays the DNS servers for the 'Ethernet' interface only.
 
.PARAMETER Interface
    Specifies the name of the network interface for which to retrieve DNS information.
#>

function Get-Dns {
    [CmdletBinding()]
    param(
        [Parameter(Position=0)]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
            # Get available network interfaces
            try {
                $interfaces = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -ExpandProperty Name
                
                # Filter based on what user has typed
                $results = if ($wordToComplete) {
                    $interfaces | Where-Object { $_ -like "$wordToComplete*" }
                } else {
                    $interfaces
                }
                
                # Return as CompletionResult objects
                return $results | ForEach-Object {
                    [System.Management.Automation.CompletionResult]::new(
                        "'$_'",           # CompletionText (quoted to handle spaces)
                        $_,               # ListItemText
                        'ParameterValue', # ResultType
                        "Network Interface: $_"  # ToolTip
                    )
                }
            } catch {
                # If Get-NetAdapter fails, return empty array
                return @()
            }
        })]
        [string]$Interface
    )

    # Use Get-NetAdapter to find interfaces, filtering if a name is specified
    $adapters = Get-NetAdapter | Where-Object { $_.Status -eq "Up" }

    if ($PSBoundParameters.ContainsKey('Interface')) {
        $adapters = $adapters | Where-Object { $_.Name -eq $Interface }
    }

    if (-not $adapters) {
        Write-Warning "No active network interfaces found."
        return
    }

    $adapters | ForEach-Object {
        $currentDns = (Get-DnsClientServerAddress -InterfaceAlias $_.Name -ErrorAction SilentlyContinue).ServerAddresses
        $dnsString = if ($currentDns) { $currentDns -join ', ' } else { 'DHCP' }

        [pscustomobject]@{
            InterfaceName = $_.Name
            ConnectionStatus = $_.Status
            CurrentDnsServers = $dnsString
        }
    } # | Format-Table -AutoSize
}

<#
.SYNOPSIS
    Sets DNS settings for a network interface.
 
.DESCRIPTION
    This tool modifies the DNS server settings for a network interface.
    Administrative privileges are required. Run PowerShell as Administrator before using this command.
 
.EXAMPLE
    Set-Dns -Interface 'Ethernet' -DnsServer '8.8.8.8', '8.8.4.4'
    Sets Google DNS for the 'Ethernet' interface.
 
.EXAMPLE
    Set-Dns -Interface 'Wi-Fi' -Dhcp
    Reverts the 'Wi-Fi' interface to use DNS from DHCP (automatic).
     
.EXAMPLE
    Set-Dns -Interface 'Ethernet' -DnsPreset 'Google'
    Sets DNS using the built-in 'Google' preset. Use Get-DnsPresets to see all available presets.
 
.PARAMETER Interface
    The name of the network interface to be modified.
 
.PARAMETER DnsServer
    One or more IP addresses for the new DNS servers. The first is primary, others are secondary.
    This parameter is part of the 'ManualIP' parameter set.
 
.PARAMETER DnsPreset
    The name of a built-in DNS preset such as 'Google', 'Cloudflare', or 'AdGuard'.
    This parameter is part of the 'PresetName' parameter set. Use Get-DnsPresets to see all available options.
 
.PARAMETER Dhcp
    A switch to revert the interface to use DNS from DHCP (automatic).
    This parameter is part of the 'Dhcp' parameter set.
#>

function Set-Dns {
    [CmdletBinding(DefaultParameterSetName='ManualIP')]
    param(
        [Parameter(Mandatory=$true, Position=0, HelpMessage='The name of the network interface.')]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
            # Get available network interfaces
            try {
                $interfaces = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -ExpandProperty Name
                
                # Filter based on what user has typed
                $results = if ($wordToComplete) {
                    $interfaces | Where-Object { $_ -like "$wordToComplete*" }
                } else {
                    $interfaces
                }
                
                # Return as CompletionResult objects
                return $results | ForEach-Object {
                    [System.Management.Automation.CompletionResult]::new(
                        "'$_'",           # CompletionText (quoted to handle spaces)
                        $_,               # ListItemText
                        'ParameterValue', # ResultType
                        "Network Interface: $_"  # ToolTip
                    )
                }
            } catch {
                # If Get-NetAdapter fails, return empty array
                return @()
            }
        })]
        [string]$Interface,
        
        [Parameter(ParameterSetName='ManualIP', Mandatory=$false)]
        [string[]]$DnsServer,
        
        [Parameter(ParameterSetName='PresetName', Mandatory=$true)]
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
            # Load presets from the data file
            $presetsPath = Join-Path $PSScriptRoot 'DnsPresets.psd1'
            if (-not (Test-Path $presetsPath)) {
                return @()
            }
            
            try {
                $presets = Import-PowerShellDataFile -Path $presetsPath
                $presetNames = $presets.Keys
                
                # Filter and return completion results
                $results = if ($wordToComplete) {
                    $presetNames | Where-Object { $_ -like "$wordToComplete*" }
                } else {
                    $presetNames
                }
                
                # Return as CompletionResult objects to override file completion
                return $results | ForEach-Object {
                    [System.Management.Automation.CompletionResult]::new(
                        $_,           # CompletionText
                        $_,           # ListItemText
                        'ParameterValue', # ResultType
                        "DNS Preset: $_"  # ToolTip
                    )
                }
            } catch {
                return @()
            }
        })]
        [ValidateScript({
            $presetsPath = Join-Path $PSScriptRoot 'DnsPresets.psd1'
            if (Test-Path $presetsPath) {
                try {
                    $presets = Import-PowerShellDataFile -Path $presetsPath
                    return $presets.ContainsKey($_)
                } catch {
                    return $false
                }
            }
            return $false
        })]
        [string]$DnsPreset,
        
        [Parameter(ParameterSetName='Dhcp', Mandatory=$true)]
        [switch]$Dhcp
    )
    
    # Check if a preset name was provided and get the IPs
    if ($PSCmdlet.ParameterSetName -eq 'PresetName') {
        $presetData = Get-DnsPreset -PresetName $DnsPreset
        if ($null -eq $presetData) {
            # Error message is handled by Get-DnsPreset
            return
        }
        $DnsServer = @($presetData.Primary, $presetData.Secondary)
    }
    
    # Validate DNS server IP addresses if provided
    if ($DnsServer) {
        foreach ($dns in $DnsServer) {
            if (-not [System.Net.IPAddress]::TryParse($dns, [ref]$null)) {
                Write-Error "Invalid IP address: $dns"
                return
            }
        }
    }
    
    # Check for administrative privileges
    if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
        Write-Warning "Access denied. This operation requires administrative privileges."
        Write-Host "Please run PowerShell as Administrator and try again." -ForegroundColor Yellow
        return
    }

    # Verify that the specified interface exists
    $adapter = Get-NetAdapter -Name $Interface -ErrorAction SilentlyContinue
    if (-not $adapter) {
        Write-Error "Interface '$Interface' not found."
        return
    }

    # Handle the Dhcp switch
    if ($PSCmdlet.ParameterSetName -eq 'Dhcp') {
        Write-Host "Reverting '$Interface' to automatic DNS..."
        try {
            Set-DnsClientServerAddress -InterfaceAlias $Interface -ResetServerAddresses
            [PSCustomObject]@{
                Interface = $Interface
                Action = "Reset to DHCP"
                DnsServers = "Automatic"
                Status = "Success"
            }
        } catch {
            Write-Error "Failed to reset DNS for interface '$Interface': $($_.Exception.Message)"
            return
        }
        return
    }
    
    # Handle the case where no DnsServer is provided (in the manual set)
    if ($null -eq $DnsServer -and $PSCmdlet.ParameterSetName -eq 'ManualIP') {
        Write-Host "No DNS servers were provided. No changes have been made."
        return
    }

    # Handle setting new DNS servers
    Write-Host "Setting DNS for '$Interface' to: $($DnsServer -join ', ')"
    try {
        Set-DnsClientServerAddress -InterfaceAlias $Interface -ServerAddresses $DnsServer
        [PSCustomObject]@{
            Interface = $Interface
            Action = "Set DNS"
            DnsServers = $DnsServer -join ', '
            Status = "Success"
        }
    } catch {
        Write-Error "Failed to set DNS for interface '$Interface': $($_.Exception.Message)"
        return
    }
}

# Preset functions for common DNS providers
function Set-GoogleDns {
    [CmdletBinding()]
    param(
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
            # Get available network interfaces
            try {
                $interfaces = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -ExpandProperty Name
                
                # Filter based on what user has typed
                $results = if ($wordToComplete) {
                    $interfaces | Where-Object { $_ -like "$wordToComplete*" }
                } else {
                    $interfaces
                }
                
                # Return as CompletionResult objects
                return $results | ForEach-Object {
                    [System.Management.Automation.CompletionResult]::new(
                        "'$_'",           # CompletionText (quoted to handle spaces)
                        $_,               # ListItemText
                        'ParameterValue', # ResultType
                        "Network Interface: $_"  # ToolTip
                    )
                }
            } catch {
                # If Get-NetAdapter fails, return empty array
                return @()
            }
        })]
        [string]$Interface = "Wi-Fi"
    )
    Set-Dns -Interface $Interface -DnsPreset "Google"
}

function Set-CloudflareDns {
    [CmdletBinding()]
    param(
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
            # Get available network interfaces
            try {
                $interfaces = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -ExpandProperty Name
                
                # Filter based on what user has typed
                $results = if ($wordToComplete) {
                    $interfaces | Where-Object { $_ -like "$wordToComplete*" }
                } else {
                    $interfaces
                }
                
                # Return as CompletionResult objects
                return $results | ForEach-Object {
                    [System.Management.Automation.CompletionResult]::new(
                        "'$_'",           # CompletionText (quoted to handle spaces)
                        $_,               # ListItemText
                        'ParameterValue', # ResultType
                        "Network Interface: $_"  # ToolTip
                    )
                }
            } catch {
                # If Get-NetAdapter fails, return empty array
                return @()
            }
        })]
        [string]$Interface = "Wi-Fi"
    )
    Set-Dns -Interface $Interface -DnsPreset "Cloudflare"
}

function Set-AdBlockDns {
    [CmdletBinding()]
    param(
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
            # Get available network interfaces
            try {
                $interfaces = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -ExpandProperty Name
                
                # Filter based on what user has typed
                $results = if ($wordToComplete) {
                    $interfaces | Where-Object { $_ -like "$wordToComplete*" }
                } else {
                    $interfaces
                }
                
                # Return as CompletionResult objects
                return $results | ForEach-Object {
                    [System.Management.Automation.CompletionResult]::new(
                        "'$_'",           # CompletionText (quoted to handle spaces)
                        $_,               # ListItemText
                        'ParameterValue', # ResultType
                        "Network Interface: $_"  # ToolTip
                    )
                }
            } catch {
                # If Get-NetAdapter fails, return empty array
                return @()
            }
        })]
        [string]$Interface = "Wi-Fi"
    )
    Set-Dns -Interface $Interface -DnsPreset "AdGuard"
}

function Reset-Dns {
    [CmdletBinding()]
    param(
        [ArgumentCompleter({
            param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
            
            # Get available network interfaces
            try {
                $interfaces = Get-NetAdapter | Where-Object { $_.Status -eq "Up" } | Select-Object -ExpandProperty Name
                
                # Filter based on what user has typed
                $results = if ($wordToComplete) {
                    $interfaces | Where-Object { $_ -like "$wordToComplete*" }
                } else {
                    $interfaces
                }
                
                # Return as CompletionResult objects
                return $results | ForEach-Object {
                    [System.Management.Automation.CompletionResult]::new(
                        "'$_'",           # CompletionText (quoted to handle spaces)
                        $_,               # ListItemText
                        'ParameterValue', # ResultType
                        "Network Interface: $_"  # ToolTip
                    )
                }
            } catch {
                # If Get-NetAdapter fails, return empty array
                return @()
            }
        })]
        [string]$Interface = "Wi-Fi"
    )
    Set-Dns -Interface $Interface -Dhcp
}

# Create convenient aliases
New-Alias -Name "dns-google" -Value "Set-GoogleDns"
New-Alias -Name "dns-cf" -Value "Set-CloudflareDns" 
New-Alias -Name "dns-adblock" -Value "Set-AdBlockDns"
New-Alias -Name "dns-reset" -Value "Reset-Dns"