Modules/businessdev.ALbuild.Containers/Private/Get-BcContainerIpAddress.ps1

function Select-BcLanIpAddress {
    <#
    .SYNOPSIS
        Picks the reachable LAN IPv4 from a list of candidate addresses.
    .DESCRIPTION
        Drops loopback (127.*) and link-local/APIPA (169.254.*) and anything that is not a dotted IPv4.
        When both a routable LAN address and a Docker NAT address (172.16-31.x) are present, the routable
        one wins - a transparent container's DHCP LAN IP is what an external host must use, not the NAT IP.
        Returns $null when no usable address remains.
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param([string[]] $Candidates)

    $ips = @($Candidates | ForEach-Object { "$_".Trim() } | Where-Object {
            $_ -match '^\d{1,3}(\.\d{1,3}){3}$' -and $_ -ne '127.0.0.1' -and $_ -notlike '169.254.*'
        } | Select-Object -Unique)
    if ($ips.Count -eq 0) { return $null }
    # Prefer a routable LAN IP over Docker's NAT range (172.16.0.0-172.31.255.255).
    $lan = $ips | Where-Object { $_ -notmatch '^172\.(1[6-9]|2[0-9]|3[01])\.' } | Select-Object -First 1
    if ($lan) { return $lan }
    return $ips[0]
}

function Get-BcContainerIpAddress {
    <#
    .SYNOPSIS
        Resolves a Business Central container's reachable IPv4 address.
 
    .DESCRIPTION
        Reads the container IP robustly across network modes:
          1. 'docker inspect' NetworkSettings.Networks[].IPAddress - works for the default NAT/bridge
             network.
          2. Fallback for a TRANSPARENT (DHCP) network: on Docker-for-Windows the NetworkSettings IP is
             empty for a transparent/l2bridge network, so the DHCP-assigned LAN IP is read from INSIDE the
             container (docker exec -> Get-NetIPAddress). This is the fix for the empty webClientUrl/
             ipAddress bug on transparent containers.
        When several addresses are found, the routable LAN address wins over Docker's NAT range
        (see Select-BcLanIpAddress). Returns $null when no address can be resolved (e.g. the container is
        not running yet).
 
    .PARAMETER Name
        Container name.
 
    .PARAMETER DockerExecutable
        The Docker executable to use (default 'docker').
    #>

    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [Alias('ContainerName')] [string] $Name,
        [string] $DockerExecutable = 'docker'
    )

    $candidates = @()
    $inspect = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru -SuccessExitCodes @(0, 1) `
        -Arguments @('inspect', '-f', '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}', $Name)
    if ($inspect.Success) { $candidates += ("$($inspect.StdOut)" -split '\s+') }

    if (-not (Select-BcLanIpAddress -Candidates $candidates)) {
        # Transparent/DHCP: NetworkSettings has no IP; read the assigned IPv4 from inside the container.
        # Single-quoted so the host does not expand $_ - the script runs in the container's PowerShell.
        $inner = '(Get-NetIPAddress -AddressFamily IPv4 | Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.IPAddress -notlike "169.254.*" } | Select-Object -ExpandProperty IPAddress) -join " "'
        $exec = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru -SuccessExitCodes @(0, 1) `
            -Arguments @('exec', $Name, 'powershell', '-NoProfile', '-NonInteractive', '-Command', $inner)
        if ($exec.Success) { $candidates += ("$($exec.StdOut)" -split '\s+') }
    }

    return Select-BcLanIpAddress -Candidates $candidates
}