Public/Get-PublicIP.ps1

function Get-PublicIP {
    <#
    .SYNOPSIS
        Retrieves the public IP address of the current machine.
 
    .DESCRIPTION
        Attempts to determine public IP using multiple methods with caching:
        1. DNS lookup via OpenDNS (fastest)
        2. Parallel HTTP requests to multiple endpoints
        Results are cached for 5 minutes to improve performance.
 
    .PARAMETER TimeoutSec
        Timeout in seconds for HTTP requests. Default is 3 seconds.
 
    .PARAMETER NoCache
        Skip cache and force fresh lookup.
 
    .PARAMETER CacheMinutes
        How long to cache the IP address. Default is 5 minutes.
 
    .EXAMPLE
        Get-PublicIP
        # Returns: 203.0.113.42
 
    .EXAMPLE
        Get-PublicIP -NoCache -TimeoutSec 5
        # Forces fresh lookup with 5 second timeout
 
    .NOTES
        Author: Lakshmanachari Panuganti
        Date: 11 December 2025
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [ValidateRange(1, 30)]
        [int]$TimeoutSec = 3,

        [Parameter()]
        [switch]$NoCache,

        [Parameter()]
        [ValidateRange(1, 60)]
        [int]$CacheMinutes = 5
    )

    # Return cached IP if available and not expired
    if (-not $NoCache -and
        $script:CachedPublicIP -and
        $script:CachedIPExpiry -and
        (Get-Date) -lt $script:CachedIPExpiry) {
        Write-Verbose "Using cached public IP: $script:CachedPublicIP (expires: $script:CachedIPExpiry)"
        return $script:CachedPublicIP
    }

    try {
        Write-Verbose "Attempting DNS lookup via OpenDNS..."
        $dnsResult = Resolve-DnsName -Name myip.opendns.com -Server resolver1.opendns.com -ErrorAction Stop -DnsOnly
        $ip = $dnsResult.Where({ $_.Type -eq "A" }, 'First').IPAddress

        if ($ip -match '^\d{1,3}(\.\d{1,3}){3}$') {
            $script:CachedPublicIP = $ip
            $script:CachedIPExpiry = (Get-Date).AddMinutes($CacheMinutes)
            Write-Verbose "Public IP retrieved via DNS: $ip (cached until $script:CachedIPExpiry)"
            return $ip
        }
    }
    catch {
        Write-Verbose "DNS lookup failed: $($_.Exception.Message)"
    }

    # 2️⃣ Fallback: Try multiple HTTP endpoints in parallel
    Write-Verbose "DNS failed, trying HTTP endpoints in parallel..."

    $endpoints = @(
        'https://checkip.amazonaws.com'
        'https://api.ipify.org'
        'https://icanhazip.com'
        'https://ifconfig.me/ip'
    )

    $jobs = $endpoints | ForEach-Object {
        $endpoint = $_
        Start-ThreadJob -ScriptBlock {
            param($url, $timeout)
            try {
                $response = Invoke-RestMethod -Uri $url -TimeoutSec $timeout -ErrorAction Stop
                return $response.Trim()
            }
            catch {
                return $null
            }
        } -ArgumentList $endpoint, $TimeoutSec
    }

    # Wait for first successful response
    $ip = $null
    $waitTime = 0
    $maxWait = $TimeoutSec * 1000  # Convert to milliseconds
    $checkInterval = 100  # Check every 100ms

    while ($waitTime -lt $maxWait -and -not $ip) {
        Start-Sleep -Milliseconds $checkInterval
        $waitTime += $checkInterval

        foreach ($job in $jobs) {
            if ($job.State -eq 'Completed') {
                $result = Receive-Job -Job $job -ErrorAction SilentlyContinue
                if ($result -match '^\d{1,3}(\.\d{1,3}){3}$') {
                    $ip = $result
                    Write-Verbose "Received valid IP from endpoint: $ip"
                    break
                }
            }
        }
    }

    # Cleanup all jobs
    $jobs | Stop-Job -PassThru | Remove-Job -Force

    if ($ip) {
        $script:CachedPublicIP = $ip
        $script:CachedIPExpiry = (Get-Date).AddMinutes($CacheMinutes)
        Write-Verbose "Public IP retrieved via HTTP: $ip (cached until $script:CachedIPExpiry)"
        return $ip
    }

    # All methods failed
    $errorMsg = "Unable to determine public IP address after trying DNS and $($endpoints.Count) HTTP endpoints"
    Write-Error $errorMsg
    throw $errorMsg
}