Public/network/Get-PublicIPAddress.ps1

function Get-PublicIPAddress {
    <#
    .SYNOPSIS
        Retrieves the public IP address of the local or remote computer.
 
    .DESCRIPTION
        Queries external HTTP APIs to determine the public-facing IPv4 and IPv6
        addresses. Uses ipify.org as primary provider with ifconfig.me as fallback.
 
        For remote computers, the query is executed via Invoke-Command so the
        result reflects each machine's own public IP.
 
    .PARAMETER ComputerName
        One or more computer names to query. Defaults to the local machine.
        Accepts pipeline input.
 
    .PARAMETER Credential
        Optional credential for remote computer connections.
 
    .PARAMETER TimeoutSec
        HTTP request timeout in seconds. Defaults to 10. Valid range: 1–60.
 
    .PARAMETER IPv6
        Also attempt to resolve the public IPv6 address. Disabled by default
        because many networks do not have IPv6 connectivity.
 
    .EXAMPLE
        Get-PublicIPAddress
 
        Returns the public IP of the local machine.
 
    .EXAMPLE
        Get-PublicIPAddress -ComputerName 'SRV01' -Credential (Get-Credential)
 
        Returns the public IP of remote server SRV01.
 
    .EXAMPLE
        'SRV01', 'SRV02', 'SRV03' | Get-PublicIPAddress -IPv6
 
        Returns IPv4 and IPv6 public addresses for three servers via pipeline.
 
    .NOTES
        Author : Franck SALLET
        Requires : PowerShell 5.1+ / Windows only
        API : https://api.ipify.org (primary), https://ifconfig.me (fallback)
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 60)]
        [int]$TimeoutSec = 10,

        [Parameter(Mandatory = $false)]
        [switch]$IPv6
    )

    begin {
        # ScriptBlock executed locally or remotely to query public IP
        $queryBlock = {
            param ([int]$Timeout, [bool]$IncludeIPv6)

            $ipv4Address = $null
            $ipv6Address = $null
            $provider = $null

            # --- IPv4 ---
            # Primary: ipify
            try {
                $response = Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -TimeoutSec $Timeout -ErrorAction Stop
                $ipv4Address = $response.ip
                $provider = 'ipify.org'
            } catch {
                # Fallback: ifconfig.me
                try {
                    $ipv4Address = (Invoke-WebRequest -Uri 'https://ifconfig.me/ip' -TimeoutSec $Timeout -UseBasicParsing -ErrorAction Stop).Content.Trim()
                    $provider = 'ifconfig.me'
                } catch {
                    $provider = 'Unavailable'
                }
            }

            # --- IPv6 (optional) ---
            if ($IncludeIPv6) {
                try {
                    $response6 = Invoke-RestMethod -Uri 'https://api64.ipify.org?format=json' -TimeoutSec $Timeout -ErrorAction Stop
                    $ipv6Address = $response6.ip
                    # If api64 returned an IPv4, it means no IPv6 connectivity
                    if ($ipv6Address -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$') {
                        $ipv6Address = $null
                    }
                } catch {
                    $ipv6Address = $null
                }
            }

            [PSCustomObject]@{
                IPv4Address = $ipv4Address
                IPv6Address = $ipv6Address
                Provider    = $provider
            }
        }
    }

    process {
        foreach ($targetComputer in $ComputerName) {
            try {
                $isLocal = ($targetComputer -eq $env:COMPUTERNAME) -or
                ($targetComputer -eq 'localhost') -or
                ($targetComputer -eq '.')

                if ($isLocal) {
                    $rawResult = & $queryBlock -Timeout $TimeoutSec -IncludeIPv6 $IPv6.IsPresent
                } else {
                    $invokeParams = @{
                        ComputerName = $targetComputer
                        ScriptBlock  = $queryBlock
                        ArgumentList = @($TimeoutSec, $IPv6.IsPresent)
                        ErrorAction  = 'Stop'
                    }
                    if ($Credential) {
                        $invokeParams['Credential'] = $Credential
                    }
                    $rawResult = Invoke-Command @invokeParams
                }

                [PSCustomObject]@{
                    PSTypeName   = 'PSWinOps.PublicIPAddress'
                    ComputerName = $targetComputer
                    IPv4Address  = $rawResult.IPv4Address
                    IPv6Address  = $rawResult.IPv6Address
                    Provider     = $rawResult.Provider
                    Timestamp    = Get-Date -Format 'o'
                }
            } catch {
                Write-Error "[$($MyInvocation.MyCommand)] Failed on '$targetComputer': $_"
            }
        }
    }
}