Public/Get-WHOIS.ps1

<#
.SYNOPSIS
    Gets WHOIS information for a domain name by querying the WHOIS server for the domain name.
.DESCRIPTION
    Queries the WHOIS server for a domain name and returns summary information by default,
    the Registrar, Creation Date, Expiration Date, and DNS Name Servers.
    The WHOIS server is determined by the TLD of the domain name, unless specified.
.NOTES
    Author: Robert D. Biddle
    Date: 2023-10-18
    Get-WHOIS - PowerShell Module to lookup WHOIS information for a domain name.
    Copyright (C) 2023 Robert D. Biddle
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
 
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program. If not, see <https://www.gnu.org/licenses/>.
.LINK
    https://github.com/RobBiddle/Get-WHOIS/
.EXAMPLE
    Get-WHOIS -DomainName "whois-servers.net"
    This will return the following information:
 
    DomainName : whois-servers.net
    CreationDate : 1999-03-31 12:00:00 AM
    DaysUntilExpiration : 162
    ExpirationDate : 2024-03-31 12:00:00 AM
    NameServers : {UDNS1.ULTRADNS.NET, UDNS2.ULTRADNS.NET}
    Registrar : TucowsDomainsInc.
    WhoisServerName : whois.verisign-grs.com
.EXAMPLE
    Get-WHOIS -DomainName "whois-servers.net" -OutputFormat "detail"
    This will return the full WHOIS response for the domain name.
.PARAMETER DomainName
    The domain name to query in FQDN format.
.PARAMETER OutputFormat
    The output format for the WHOIS information. Valid values are "summary" and "detail".
     
    "summary" is the default value and returns a PSCustomObject with Properties:
    DomainName, CreationDate, DaysUntilExpiration, ExpirationDate, NameServers, Registrar, WhoisServerName
     
    "detail" returns the full WHOIS response.
.PARAMETER whoisServer
    The FQDN of a WHOIS server to query for the domain name.
    If not specified, the WHOIS server is determined by the TLD of the domain name.
.PARAMETER whoisPort
    The port to use when querying the WHOIS server.
    If not specified, the default port of 43 is used.
.OUTPUTS
    Selected.System.Management.Automation.PSCustomObject
#>

function Get-WHOIS {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateScript({
                if ($_ -match "^\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b$") {
                    $true
                }
                else {
                    throw "Invalid FQDN format."
                }
            })]
        [string]$DomainName,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateSet("summary", "detail")]
        [string]$OutputFormat = "summary",

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string]$whoisServer,

        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [int]$whoisPort = 43
    )

    begin {
        # Import the Find-WhoisServer function
        . "$PSScriptRoot\..\Private\Find-WhoisServer.ps1"
    }
    process {
        if (-NOT $whoisServer) {
            # Set the appropriate WHOIS server and port based on the TLD of the domain name
            $whoisDnsServers = Find-WhoisServer -DomainName $DomainName
            $wServer = $whoisDnsServers | Where-Object IP4Address | Select-Object -first 1
        }
        else {
            $wServer = Resolve-DnsName $whoisServer | Where-Object IP4Address | Select-Object -first 1
        }

        # Get the IP address of the WHOIS server
        $wServerIP = ($wServer | Where-Object IP4Address).IP4Address | Select-Object -first 1
        # Get the name of the WHOIS server
        $wServerName = ($wServer | Where-Object IP4Address | Select-Object -first 1).Name
        # Query the WHOIS server for the domain name
        $whoisQuery = $DomainName + "`r`n"
        $whoisSocket = New-Object System.Net.Sockets.TcpClient($wServerIP, $whoisPort)
        $whoisStream = $whoisSocket.GetStream()
        $whoisWriter = New-Object System.IO.StreamWriter($whoisStream)
        $whoisWriter.WriteLine($whoisQuery)
        $whoisWriter.Flush()
        $whoisReader = New-Object System.IO.StreamReader($whoisStream)
        $whoisResponse = $whoisReader.ReadToEnd()
        $whoisReader.Close()
        $whoisWriter.Close()
        $whoisSocket.Close()
        if ($OutputFormat -eq "summary") {
            $parsedResponse = $whoisResponse -split "\r?\n" | Where-Object { $_ -match ":" }
            $properties = @{}
            
            foreach ($line in $parsedResponse) {
                if ($line -match '^(.*?):\s*(.*)') {
                    $name = $matches[1].Trim()
                    $value = $matches[2].Trim()
            
                    # Remove special characters from the property name, except for spaces
                    # $name = $name -replace '[^a-zA-Z0-9\s]', ''
            
                    # Remove special characters from the property name, including spaces
                    $name = $name -replace '[^a-zA-Z0-9]', ''
            
                    # Remove leading and trailing spaces
                    $name = $name.Trim()
            
                    $matchFound = $false
                    $DesiredPropertyNames = @("Created", "Creation", "Updated", "Expiration", "Expires", "Expiry", "Date", "Email", "Server", "Status", "Registrant", "Registrar", "Registry", "Domain", "IANA")
                    foreach ($DesiredPropertyName in $DesiredPropertyNames) {
                        if ($name -imatch $DesiredPropertyName) {
                            $matchFound = $true
                        }
                    }
            
                    if (-not $matchFound) {
                        continue
                    }
            
                    $IgnoredStringFound = $false
                    $IgnoredNameStrings = @("NOTICE", "TERMS OF USE", "https")
                    foreach ($IgnoredString in $IgnoredNameStrings) {
                        if ($name -imatch $IgnoredString) {
                            # Write-Verbose "The line contains the ignored string: $IgnoredString"
                            $IgnoredStringFound = $true
                        }
                    }
            
                    if ($IgnoredStringFound) {
                        continue
                    }
            
                    if ($properties.ContainsKey($name)) {
                        if ($properties[$name] -isnot [array]) {
                            $properties[$name] = @($properties[$name])
                        }
                        $properties[$name] += $value
                    }
                    else {
                        $properties[$name] = $value
                    }
                }
            }
            
            # Calculate Days Until Expiration
            # Define a list of possible property names for expiration dates
            $expirationPropertyNames = @("ExpirationDate", "ExpiryDate", "Expires", "RegistryExpiryDate")
            
            # Initialize variable to store the days until expiration
            $DaysUntilExpiration = $null
            
            # Loop through each property name to find the first one that exists and calculate the days until expiration
            foreach ($propertyName in $expirationPropertyNames) {
                if ($properties[$propertyName]) {
                    $DaysUntilExpiration = (New-TimeSpan -Start (Get-Date) -End $properties[$propertyName]).Days
                    $properties["DaysUntilExpiration"] = $DaysUntilExpiration
                    break # Exit the loop once the first valid expiration date is found and processed
                }
            }

            # Add the WHOIS server name to the properties
            $properties["WhoisServerName"] = $wServerName

            # Create a new ordered dictionary to hold sorted properties
            $sortedProperties = [ordered]@{}
            
            # Sort the properties and add them to the sorted ordered dictionary
            $properties.GetEnumerator() | Sort-Object Name | ForEach-Object {
                $sortedProperties[$_.Key] = $_.Value
            }
            
            # Convert the sorted ordered dictionary directly to a PSCustomObject
            $whoisObject = [PSCustomObject]$sortedProperties

            # Add alias property for NameServers
            # This is being done to avoid creating a breaking change
            if ($whoisObject.NameServer -and (-not $whoisObject.NameServers)) {
                $whoisObject | Add-Member -MemberType AliasProperty -Name NameServers -Value NameServer -Force
            }

            # Add property for WhoisLookupServiceUsed
            if ($whoisDnsServers.FoundVia) {
                $whoisObject | Add-Member -MemberType NoteProperty -Name WhoisLookupService -Value ($whoisDnsServers.FoundVia | Select-Object -Unique) -Force
            }

            # Output the object
            $whoisObject
        }
        else {
            $whoisResponse
        }
    }
}