private/core-operatingsystem/Test-OperatingSystemCloudObject.ps1
|
function Test-OperatingSystemCloudObject { <# .SYNOPSIS Tests whether an OSDCore operating system object is available online and ready to downloaded. .DESCRIPTION Reads the operating system download URL from the supplied object, or from $global:OSDCoreOperatingSystemCloudObject when no object is supplied, and returns $true when a live TCP connection and HTTP HEAD request can reach it. The function supports both module-native shapes: OSDCloud objects use FilePath and OSD objects use Url. Returns $false when the object is missing, the resolved URL property is empty, or the URL test fails. HTTP and HTTPS are both tested for host-only web URLs so systems with an invalid date can still detect basic network reachability over HTTP. Specific absolute file URLs are tested exactly as supplied. .PARAMETER OperatingSystemCloudObject Operating system object containing the download URL to test. OSDCloud uses FilePath and OSD uses Url. .EXAMPLE Test-OperatingSystemCloudObject Tests the resolved FilePath/Url property on $global:OSDCoreOperatingSystemCloudObject. .EXAMPLE Test-OperatingSystemCloudObject -OperatingSystemCloudObject $global:OSDCoreOperatingSystemCloudObject Tests the resolved FilePath/Url property on the supplied operating system object. .LINK https://github.com/OSDeploy/OSD/tree/master/docs .NOTES Author: David Segura - Recast Software 2026-07-19 - Initial private helper created 2026-07-19 - Removed Test-WebConnection dependency 2026-07-19 - Preserved supplied scheme for specific file URLs 2026-07-20 - Added live TCP validation before HTTP HEAD to avoid cached success responses 2026-08-06 - Added OSDCloud FilePath fallback for cross-module compatibility #> [CmdletBinding()] [OutputType([System.Boolean])] param ( [Parameter(ValueFromPipeline)] [psobject] $OperatingSystemCloudObject = $global:OSDCoreOperatingSystemCloudObject ) process { $Error.Clear() # The caller may pass an object explicitly or rely on the global OSDCore selection. if ($null -eq $OperatingSystemCloudObject) { Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] OSDCoreOperatingSystemCloudObject is not set." return $false } # Resolve the module-native URL property: OSDCloud uses FilePath and OSD uses Url. if (-not [string]::IsNullOrWhiteSpace([string]$OperatingSystemCloudObject.FilePath)) { $OperatingSystemCloudObjectUrl = [string]$OperatingSystemCloudObject.FilePath } else { $OperatingSystemCloudObjectUrl = [string]$OperatingSystemCloudObject.Url } # A missing URL means there is nothing useful to test, so return the Boolean failure state. if ([string]::IsNullOrWhiteSpace($OperatingSystemCloudObjectUrl)) { Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] OSDCoreOperatingSystemCloudObject FilePath/Url is not set." return $false } try { # Build the exact set of URLs to probe before making any web requests. $OperatingSystemCloudObjectUri = [System.Uri]$OperatingSystemCloudObjectUrl $IsSpecificAbsoluteUrl = $OperatingSystemCloudObjectUri.IsAbsoluteUri -and -not [string]::IsNullOrWhiteSpace($OperatingSystemCloudObjectUri.AbsolutePath) -and $OperatingSystemCloudObjectUri.AbsolutePath -ne '/' if ($OperatingSystemCloudObjectUri.IsAbsoluteUri -and ($OperatingSystemCloudObjectUri.Scheme -notin @('http','https') -or $IsSpecificAbsoluteUrl)) { # Non-web absolute URIs and specific file URLs are tested as provided. $RequestUris = @($OperatingSystemCloudObjectUri) } else { if ($OperatingSystemCloudObjectUri.IsAbsoluteUri) { # Host-only web URLs are checked over HTTPS first, then HTTP as a fallback for bad system time. $UriBuilder = New-Object System.UriBuilder $OperatingSystemCloudObjectUri $UriBuilder.Scheme = 'https' $UriBuilder.Port = -1 $HttpsUri = $UriBuilder.Uri $UriBuilder.Scheme = 'http' $UriBuilder.Port = -1 $HttpUri = $UriBuilder.Uri } else { # Bare URLs are expanded to both HTTPS and HTTP targets. $HttpsUri = [System.Uri]::new("https://$($OperatingSystemCloudObjectUri.OriginalString)") $HttpUri = [System.Uri]::new("http://$($OperatingSystemCloudObjectUri.OriginalString)") } $RequestUris = @($HttpsUri, $HttpUri) } } catch { Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] OSDCoreOperatingSystemCloudObject Url is not valid: $OperatingSystemCloudObjectUrl" return $false } $InvokeWebRequestCommand = Get-Command Invoke-WebRequest $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)] OSDCoreOperatingSystemCloudObject TCP FAIL: $RequestUri" [pscustomobject]@{ Uri = $RequestUri Success = $false } continue } finally { $TcpClient.Close() } } # Use HEAD to validate reachability without downloading the operating system payload. $Params = @{ Method = 'Head' Uri = $RequestUri Headers = @{'Cache-Control'='no-cache, no-store'; 'Pragma'='no-cache'} ErrorAction = 'Stop' } # Keep the request compatible across Windows PowerShell and newer PowerShell versions. 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)] OSDCoreOperatingSystemCloudObject URL OK: $RequestUri" [pscustomobject]@{ Uri = $RequestUri Success = $true } } catch { Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] OSDCoreOperatingSystemCloudObject URL FAIL: $RequestUri" [pscustomobject]@{ Uri = $RequestUri Success = $false } } finally { [System.Net.WebRequest]::DefaultCachePolicy = $PreviousCachePolicy } } # If only HTTP succeeds, the network may be reachable but HTTPS can fail because the clock is wrong. $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 ($HttpsResult -and $HttpResult -and -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." } # Return true when any attempted URL succeeds. [bool]($Results | Where-Object {$_.Success} | Select-Object -First 1) } } |