Private/Resolve-SystemNetWebRequest.ps1

function Resolve-SystemNetWebRequest {
    <#
        .SYNOPSIS
            Resolve a URL and return response metadata without reading the response body.

        .DESCRIPTION
            Follows redirects and returns response metadata, including ResponseUri.
            Uses HEAD first and retries with GET only for HTTP 405 responses.
            The GET request is aborted after receiving headers, without reading the body.
            Some body bytes may still arrive in network buffers during a GET request.
            Verbose output includes request settings, response status and elapsed time.
    #>

    [OutputType([System.Management.Automation.PSObject])]
    [CmdletBinding(SupportsShouldProcess = $false)]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [System.String] $Uri,

        [Parameter(Position = 1)]
        [System.String] $UserAgent = (Get-EvergreenUserAgent),

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [System.Int32] $MaximumRedirection = 3
    )

    $webResponse = $null
    $httpWebRequest = $null
    $ProxyObj = $null
    $Method = "HEAD"
    $Timer = [System.Diagnostics.Stopwatch]::StartNew()

    try {
        do {
            $httpWebRequest = [System.Net.WebRequest]::Create($Uri)
            $httpWebRequest.Method = $Method
            $httpWebRequest.MaximumAutomaticRedirections = $MaximumRedirection
            $httpWebRequest.AllowAutoRedirect = $true

            # Don't add a UserAgent if it's not provided
            if ([System.String]::IsNullOrEmpty($UserAgent)) {}
            else {
                $httpWebRequest.UserAgent = $UserAgent
            }

            if (Test-ProxyEnv) {
                $ProxyObj = New-Object -TypeName "System.Net.WebProxy"
                $ProxyObj.Address = $script:EvergreenProxy
                $ProxyObj.UseDefaultCredentials = $true
                $httpWebRequest.Proxy = $ProxyObj

                if (Test-ProxyEnv -Creds) {
                    $ProxyObj.UseDefaultCredentials = $false
                    $ProxyObj.Credentials = $script:EvergreenProxyCreds
                    $httpWebRequest.UseDefaultCredentials = $false
                    $httpWebRequest.Proxy = $ProxyObj
                }
            }
            else {
                $httpWebRequest.UseDefaultCredentials = $true
            }

            Write-Verbose -Message "$($MyInvocation.MyCommand): Request: $Method [$Uri]; response body will not be read."
            Write-Verbose -Message "$($MyInvocation.MyCommand): Maximum automatic redirects: [$MaximumRedirection]; follows redirects to the final response."
            Write-Verbose -Message "$($MyInvocation.MyCommand): User agent supplied: [$(-not [System.String]::IsNullOrEmpty($UserAgent))]; proxy configured: [$($null -ne $ProxyObj)]."
            Write-Verbose -Message "$($MyInvocation.MyCommand): Waiting for response headers; request timeout: [$($httpWebRequest.Timeout)] ms."
            try {
                $webResponse = $httpWebRequest.GetResponse()
            }
            catch {
                $RequestException = $_.Exception
                while ($RequestException -isnot [System.Net.WebException] -and $null -ne $RequestException.InnerException) {
                    $RequestException = $RequestException.InnerException
                }
                if ($RequestException -is [System.Net.WebException]) {
                    $webResponse = $RequestException.Response
                }
                if ($Method -ne "HEAD" -or $null -eq $webResponse -or [int]$webResponse.StatusCode -ne 405) {
                    throw
                }
                Write-Verbose -Message "$($MyInvocation.MyCommand): HEAD rejected with HTTP [405]; retrying with GET and aborting after response headers."
                $httpWebRequest.Abort()
                $webResponse.Dispose()
                $webResponse = $null
                $Method = "GET"
            }
        } while ($null -eq $webResponse)
        Write-Verbose -Message "$($MyInvocation.MyCommand): Response: HTTP [$([int]$webResponse.StatusCode)] from [$($webResponse.ResponseUri.AbsoluteUri)]."
        Write-Verbose -Message "$($MyInvocation.MyCommand): Advertised Content-Length: [$($webResponse.ContentLength)] bytes (-1 means unknown); this is not bytes downloaded."
        Write-Verbose -Message "$($MyInvocation.MyCommand): Resolved to: [$($webResponse.ResponseUri.AbsoluteUri)]."

        # Construct the output; Return the custom object to the pipeline
        $PSObject = [PSCustomObject] @{
            LastModified  = $webResponse.LastModified
            ContentLength = $webResponse.ContentLength
            Headers       = $webResponse.Headers
            ResponseUri   = $webResponse.ResponseUri
            StatusCode    = $webResponse.StatusCode
        }
        if ($Method -eq "GET") {
            # Abort before disposal to avoid draining the unread response body.
            $httpWebRequest.Abort()
            Write-Verbose -Message "$($MyInvocation.MyCommand): GET aborted after headers; response body was not read."
        }
        Write-Output -InputObject $PSObject
    }
    catch {
        $ErrorResponse = $webResponse
        if ($null -ne $ErrorResponse) {
            Write-Verbose -Message "$($MyInvocation.MyCommand): Resolution failed: HTTP [$([int]$ErrorResponse.StatusCode)]."
        }
        else {
            Write-Verbose -Message "$($MyInvocation.MyCommand): Resolution failed before an HTTP response was received."
        }
        Write-Warning -Message "$($MyInvocation.MyCommand): Return code: [$($webResponse.StatusCode)], with: $Uri."
        Write-Warning -Message "$($MyInvocation.MyCommand): For troubleshooting steps see: $($script:resourceStrings.Uri.Info)."
        throw $_
    }
    finally {
        $Timer.Stop()
        Write-Verbose -Message "$($MyInvocation.MyCommand): Resolution finished in [$($Timer.ElapsedMilliseconds)] ms."
        if ($null -ne $httpWebRequest) {
            $httpWebRequest.Abort()
        }
        if ($null -ne $webResponse) {
            $webResponse.Dispose()
        }
    }
}