PsDnsClient.psm1

<#
Module created by ModuleForge
     ModuleForge Version: 1.2.2
    BuildDate: 2025-12-16T10:17:18
#>

function Resolve-PsDnsName
{

    <#
        .SYNOPSIS
            Performs DNS lookups for one or more record types with clean, deduplicated output.
            
        .DESCRIPTION
            Resolve-PsDnsName is a PowerShell wrapper around the DnsClient.NET library that queries
            DNS servers for common record types such as A, AAAA, CNAME, MX, TXT, NS, PTR, SRV, and SOA.
            By default, it mimics the behaviour of Resolve-DnsName, returning CNAME, A, and AAAA records.
            
            The function supports specifying one or more record types, choosing a DNS server, disabling
            caching, and toggling between clean output (DomainName, RecordType, Data) and raw results
            for diagnostic purposes.

            This makes it useful both for quick checks (e.g. "what IPs does this host resolve to?")
            and deeper troubleshooting (e.g. "show me all MX and TXT records for a domain").

            Note that output is different to Resolve-DnsName

        .EXAMPLE
            resolve-psDnsName -Name 'google.com'

            #### DESCRIPTION
            Use defaults and do a simple Domain Resolution

            #### OUTPUT
            DomainName RecordType Data
            ---------- ---------- ----
            www.google.com. A 142.250.195.228
            www.google.com. AAAA 2404:6800:4006:812::2004

        .EXAMPLE
            resolve-psDnsName -Name 'www.google.com' -RawResult

            #### DESCRIPTION
            Same as above, but give the raw DNS results, useful if you want more comprehensive debugging, but note you may get duplicates

            #### OUTPUT

            Address : 142.250.195.132
            DomainName : www.google.com.
            RecordType : A
            RecordClass : IN
            TimeToLive : 178
            InitialTimeToLive : 178
            RawDataLength : 4

            Address : 2404:6800:4006:801::2004
            DomainName : www.google.com.
            RecordType : AAAA
            RecordClass : IN
            TimeToLive : 299
            InitialTimeToLive : 299
            RawDataLength : 16

        .EXAMPLE
            resolve-PsDnsName -Name 'google.com' -Server 8.8.8.8 -recordType 'A','AAAA','CNAME','TXT','MX','SOA' -NoCache

            #### DESCRIPTION
            Queries the default DNS server (1.1.1.1) for 'A','AAAA','CNAME','TXT','MX', and 'SOA' records of google.com.
            Returns a clean table of results similar to Resolve-DnsName.

            #### OUTPUT
            DomainName RecordType Data
            ---------- ---------- ----
            google.com. A 142.250.195.142
            google.com. AAAA 2404:6800:4006:812::200e
            google.com. TXT onetrust-domain-verification=de01ed21f2fa4d8781cbc3ffb89cf4ef
            google.com. TXT globalsign-smime-dv=CDYX+XFHUw2wml6/Gb8+59BsH31KzUr6c1l2BPvqKX8=
            google.com. TXT google-site-verification=4ibFUgB-wXLQ_S7vsXVomSTVamuOXBiVAzpR5IZ87D0
            google.com. TXT docusign=1b0a6754-49b1-4db5-8540-d2c12664b289
            google.com. TXT google-site-verification=TV9-DBe4R80X4v0M4U_bd_J9cpOJM0nikft0jAgjmsQ
            google.com. TXT MS=E4A68B9AB2BB9670BCE15412F62916164C0B20BB
            google.com. TXT facebook-domain-verification=22rm551cu4k0ab0bxsw536tlds4h95
            google.com. TXT apple-domain-verification=30afIBcvSuDV2PLX
            google.com. TXT docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e
            google.com. TXT v=spf1 include:_spf.google.com ~all
            google.com. TXT google-site-verification=wD8N7i1JTNTkezJ49swvWW48f8_9xveREV4oB-0Hf5o
            google.com. TXT cisco-ci-domain-verification=47c38bc8c4b74b7233e9053220c1bbe76bcc1cd33c7acf7acd36cd6a5332004b
            google.com. MX smtp.google.com.
            google.com. SOA ns1.google.com.
            
        .NOTES
            Author: Adrian Andersson
            
    #>


    [CmdletBinding()]
    PARAM(
        #DNS name or entry we are querying
        [Parameter(Mandatory)]
        [Alias('DnsName')]
        [string]$Name,
        #Record Type(s) we want to look up
        [Parameter()]
        [ValidateSet('ANY','A','AAAA','CNAME','MX','NS','PTR','SOA','SRV','TXT')]
        [Alias('Type')]
        [string[]]$RecordType = @('CNAME','A','AAAA'),
        #DNS Resolver to use.
        [Parameter()]
        [Alias('Server')]
        [String]$DnsServer,
        #Switch to bypass any local cache
        [switch]$NoCache,
        #Switch to return the raw records and not the clean looking summary version
        [switch]$RawResult
    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
        
        Import-DnsClientDll

        $Answers = [System.Collections.Generic.List[object]]::new()

        $CustomSelect = @(
            'DomainName'
            'RecordType'
            @{
                Name = 'Data'
                Expression = {
                    $recordType = $_.RecordType
                    $data = $_
                    switch ($recordType) {
                        'CNAME' {$data.CanonicalName}
                        'MX' {$data.Exchange}
                        'TXT' {$data.Text}
                        'SOA' {$data.MName}
                        Default {$data.Address}
                    }
                }
            }
        )
    }
    
    process{
        if ($DnsServer) {
            $options = [DnsClient.LookupClientOptions]::new([System.Net.IPAddress]::Parse($DnsServer))
        }else{
            $options = [DnsClient.LookupClientOptions]::new()
        }

        if ($NoCache) {
            Write-Verbose 'PsDnsClient.Resolve-PsDnsName: Setting noCache option'
            $options.UseCache = $false
        }

        $client = [DnsClient.LookupClient]::new($options)

        Write-Verbose "PsDnsClient.Resolve-PsDnsName: DNS Server $($client.NameServers[0].ToString())" 
        ForEach($Rtype in $RecordType)
        {
            Write-Verbose "PsDnsClient.Resolve-PsDnsName: Checking DNS Type: $RType"
            $queryType = [DnsClient.QueryType]::$Rtype
            try{
            $result = $client.Query($Name,$queryType)
            $result.Answers.foreach{
                $Answers.Add($_)
        
            }
            }catch{
                Write-Error "DNS Query failed: $_"
            }
        }

        if(!$Answers)
        {
            Throw 'PsDnsClient.Resolve-PsDnsName: No Answers from query. Check query name and try again'
        }

        if(!$RawResult)
        {
            $Answers.ToArray()|Select-Object $CustomSelect -Unique
        }else{
            $Answers.ToArray()|Select-Object -Unique
        }
        
    }
}
function Import-DnsClientDll
{

    <#
        .SYNOPSIS
            Private Helper Function to help check and load the DnsClient DLL file
            
        .DESCRIPTION
            Checks if the DnsClient library is already loaded.
            If it isn't loaded. Try to locate where the DLL is located and add it as a type.
            This function expects to be called either privately at the top of a function file, or with dot sourcing for development and testing.

            
        .NOTES
            Author: Adrian Andersson
            
    #>


    [CmdletBinding()]
    PARAM(

    )
    begin{
        #Return the script name when running verbose, makes it tidier
        write-verbose "===========Executing $($MyInvocation.InvocationName)==========="
        #Return the sent variables when running debug
        Write-Debug "BoundParams: $($MyInvocation.BoundParameters|Out-String)"
    }
    
    process{
        $Check = [AppDomain]::CurrentDomain.GetAssemblies().where{$_.Location -like '*DnsClient.dll'}
        if (!$Check)
        {
            Write-Verbose 'PsDnsClient.Import-DnsClientDll: Not Loaded. Need to import'
            if ($MyInvocation.MyCommand.ModuleName -like "*PsDnsClient*") {
                Write-Verbose 'PsDnsClient.Import-DnsClientDll: Running Inside Module'
                #In this scenario, we need to load from the module directory
                $ModulePath = $MyInvocation.MyCommand.Module.ModuleBase
                
                $dllFolderPath = Join-Path -Path (Join-Path $ModulePath -ChildPath 'resource') -ChildPath 'dnsClient'
                
            } else {
                Write-Verbose 'PsDnsClient.Import-DnsClientDll: Running in Stand-Alone Script'
                #In this scenario, we need to try and load it from the source directory
                #This is useful, as it allows us to maintain this function for pester testing or development
                $ScriptPath = $MyInvocation.MyCommand.Path
                if($ScriptPath)
                {
                    $Parent = Split-Path $ScriptPath -Parent
                    $dllFolderPath = Join-Path -Path (Join-Path $Parent -ChildPath 'resource') -ChildPath 'dnsClient'
                }else{
                    $ThisPath = $(Get-Item .).Fullname
                    Write-Verbose "PsDnsClient.Import-DnsClientDll: Current Path: $ThisPath"
                    $ChildItems = Get-ChildItem .
                    if($ChildItems.Name -contains 'source')
                    {
                        Write-Verbose 'PsDnsClient.Import-DnsClientDll: Found Source Folder'
                        $dllFolderPath = Join-Path -Path (Join-Path -Path (Join-Path $ThisPath -ChildPath 'source') -ChildPath 'resource')-ChildPath 'dnsClient'
                    }elseIf($ChildItems.Name -contains 'resource'){
                        Write-Verbose 'PsDnsClient.Import-DnsClientDll: Found resource Folder'
                        $dllFolderPath = Join-Path -Path (Join-Path -Path $ThisPath -ChildPath 'resource')-ChildPath 'dnsClient'
                    }else{
                        throw "PsDnsClient.Import-DnsClientDll: No reference point to find files"
                    }
                }
                

            }
            $dllPath = join-path -Path $dllFolderPath -ChildPath 'DnsClient.dll'
            Write-Verbose "PsDnsClient.Import-DnsClientDll: Attempt to Load DLL from: $dllPath"
            if(!(Test-Path $dllPath))
            {
                throw "PsDnsClient.Import-DnsClientDll: Unable to locate DnsClient.dll dependency. Looked here: $dllPath"
            }else{
                Add-Type -Path $dllPath
            }
        }else{
            Write-Verbose 'PsDnsClient.Import-DnsClientDll: Library already available. DLL Already loaded '
        }
    }
}