Public/Functions/Test-WebConnection.ps1

function Test-WebConnection
{
    <#
    .SYNOPSIS
    Tests web connectivity to a target URI using a live TCP connection and HTTP HEAD request.
 
    .DESCRIPTION
    Opens a live TCP connection and sends HTTP HEAD requests to the specified
    URI, returning $true when the request succeeds and $false otherwise. If a URI
    is provided without a scheme, both https:// and http:// are tested.
 
    .PARAMETER Uri
    URI to test. Values from the pipeline are supported.
 
    .EXAMPLE
    Test-WebConnection -Uri 'http://example.com'
    Returns $true when the target responds to an HTTP HEAD request.
 
    .EXAMPLE
    'google.com' | Test-WebConnection
    Tests a bare URI supplied from the pipeline by checking both HTTPS and HTTP.
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-16 - Moved help block inside function and improved request handling
    2026-07-19 - Improved terminating error handling and verbose diagnostics
    2026-07-19 - Added HTTPS and HTTP checks for bare URI values
    2026-07-20 - Added live TCP validation before HTTP HEAD to avoid cached success responses
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        # Uri to test
        [System.Uri]
        $Uri = 'http://www.google.com'
    )
    process {
        $InvokeWebRequestCommand = Get-Command Invoke-WebRequest
        $RequestUris = if ($Uri.IsAbsoluteUri) {
            @($Uri)
        }
        else {
            @(
                [System.Uri]::new("https://$($Uri.OriginalString)")
                [System.Uri]::new("http://$($Uri.OriginalString)")
            )
        }

        $Results = foreach ($RequestUri in $RequestUris) {
            if ($RequestUri.Scheme -in @('http','https')) {
                $RequestPort = $RequestUri.Port
                if ($RequestUri.IsDefaultPort) {
                    if ($RequestUri.Scheme -eq 'https') {
                        $RequestPort = 443
                    }
                    else {
                        $RequestPort = 80
                    }
                }

                $TcpClient = New-Object System.Net.Sockets.TcpClient
                try {
                    $ConnectTask = $TcpClient.ConnectAsync($RequestUri.DnsSafeHost, $RequestPort)
                    if (-not $ConnectTask.Wait(15000) -or -not $TcpClient.Connected) {
                        throw "TCP connection failed."
                    }
                }
                catch {
                    Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Test-WebConnection TCP FAIL: $RequestUri"
                    [pscustomobject]@{
                        Uri     = $RequestUri
                        Success = $false
                    }
                    continue
                }
                finally {
                    $TcpClient.Close()
                }
            }

            $Params = @{
                Method      = 'Head'
                Uri         = $RequestUri
                Headers     = @{'Cache-Control'='no-cache, no-store'; 'Pragma'='no-cache'}
                ErrorAction = 'Stop'
            }

            if ($InvokeWebRequestCommand.Parameters.ContainsKey('UseBasicParsing')) {
                $Params['UseBasicParsing'] = $true
            }
            if ($InvokeWebRequestCommand.Parameters.ContainsKey('TimeoutSec')) {
                $Params['TimeoutSec'] = 15
            }
            if ($InvokeWebRequestCommand.Parameters.ContainsKey('DisableKeepAlive')) {
                $Params['DisableKeepAlive'] = $true
            }

            $PreviousCachePolicy = [System.Net.WebRequest]::DefaultCachePolicy
            try {
                [System.Net.WebRequest]::DefaultCachePolicy = New-Object System.Net.Cache.RequestCachePolicy ([System.Net.Cache.RequestCacheLevel]::NoCacheNoStore)
                Invoke-WebRequest @Params | Out-Null
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Test-WebConnection OK: $RequestUri"
                [pscustomobject]@{
                    Uri     = $RequestUri
                    Success = $true
                }
            }
            catch {
                Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Test-WebConnection FAIL: $RequestUri"
                [pscustomobject]@{
                    Uri     = $RequestUri
                    Success = $false
                }
            }
            finally {
                [System.Net.WebRequest]::DefaultCachePolicy = $PreviousCachePolicy
            }
        }

        if (-not $Uri.IsAbsoluteUri) {
            $HttpsResult = $Results | Where-Object {$_.Uri.Scheme -eq 'https'} | Select-Object -First 1
            $HttpResult = $Results | Where-Object {$_.Uri.Scheme -eq 'http'} | Select-Object -First 1

            if (-not $HttpsResult.Success -and $HttpResult.Success) {
                Write-Warning "HTTPS connection failed for $($HttpsResult.Uri), but HTTP connection succeeded for $($HttpResult.Uri). Check the system date and time."
            }
        }

        [bool]($Results | Where-Object {$_.Success} | Select-Object -First 1)
    }
}