Skip to content

Instantly share code, notes, and snippets.

@tcartwright
Last active August 10, 2026 15:44
Show Gist options
  • Select an option

  • Save tcartwright/2df67c5847704a6009cfa0b354ccb949 to your computer and use it in GitHub Desktop.

Select an option

Save tcartwright/2df67c5847704a6009cfa0b354ccb949 to your computer and use it in GitHub Desktop.
POWERSHELL: Sync Time From Internet: Gets the current UTC time from an internet time source and resets this machine's LOCAL clock to the correct current local time. It NEVER changes the time zone and NEVER touches the "adjust for DST automatically" switch.
<#
.SYNOPSIS
Sets this machine's clock to real internet time, correctly, on boxes where auto-DST is off.
.DESCRIPTION
Manual counterpart to timeset.cfm, for fixing a cycle server by hand - and the reference
implementation of the DST-safe conversion, because the obvious way to write it is wrong.
The trap
--------
Converting UTC to local time with [datetimeoffset]::LocalDateTime relies on
TimeZoneInfo.Local carrying the zone's DST rules. Windows' "automatically adjust clock
for daylight saving time" setting, when OFF, sets DynamicDaylightTimeDisabled=1 and .NET
then reports the zone with NO adjustment rules. An August instant resolves as EST (-5)
instead of EDT (-4), so the clock is set an hour behind - and an hour is far more than
Azure DevOps allows when validating an agent's OAuth tokens, so the agent stays offline
while every visible symptom says the date is correct.
Observed on a cycle server: TimeZoneInfo.Local had 0 adjustment rules, and
([datetimeoffset]'2026-08-10T15:03:32Z').LocalDateTime returned 10:03 rather than 11:03.
This script uses FindSystemTimeZoneById instead, which reads the zone definition straight
from the registry and carries the DST rules regardless of that switch. It reports both
answers so the discrepancy is visible on affected machines.
Corroboration
-------------
Time is taken from several sources and at least two must agree before anything is
changed, so one unreachable or lying source cannot move a production clock. NTP is tried
first; if UDP 123 is blocked the plain-HTTP endpoints timeset.cfm uses are the fallback -
those work on port 80 and need no certificate, which matters when the clock is already
too wrong for HTTPS.
The timezone and the auto-DST setting are never touched. Only the clock.
.PARAMETER WhatIf
Report everything and change nothing. Worth running first.
.EXAMPLE
.\Reset-ClockFromInternet.ps1 -WhatIf
.EXAMPLE
.\Reset-ClockFromInternet.ps1
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[string[]] $NtpServer = @('time.windows.com', 'pool.ntp.org', 'time.nist.gov'),
[string[]] $HttpTimeSource = @('www.msftconnecttest.com', 'detectportal.firefox.com', 'connectivitycheck.gstatic.com'),
[int] $TimeoutMs = 4000,
[int] $MaxDisagreementSeconds = 300
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
function Get-NtpUtc {
<# Minimal SNTP client. Reads the transmit timestamp at bytes 40..47, which is seconds
and fractional seconds since 1900-01-01 UTC. #>
param([Parameter(Mandatory)][string] $Server, [int] $Timeout = 4000)
$packet = New-Object byte[] 48
$packet[0] = 0x1B # LI = 0, version = 3, mode = 3 (client)
$socket = New-Object Net.Sockets.Socket(
[Net.Sockets.AddressFamily]::InterNetwork,
[Net.Sockets.SocketType]::Dgram,
[Net.Sockets.ProtocolType]::Udp)
try {
$socket.ReceiveTimeout = $Timeout
$socket.SendTimeout = $Timeout
$address = @([Net.Dns]::GetHostAddresses($Server) | Where-Object { $_.AddressFamily -eq 'InterNetwork' })[0]
if (-not $address) { throw "no IPv4 address for $Server" }
$socket.Connect((New-Object Net.IPEndPoint($address, 123)))
[void]$socket.Send($packet)
[void]$socket.Receive($packet)
}
finally { $socket.Close() }
$seconds = ([int64]$packet[40] -shl 24) + ([int64]$packet[41] -shl 16) + ([int64]$packet[42] -shl 8) + [int64]$packet[43]
$fraction = ([int64]$packet[44] -shl 24) + ([int64]$packet[45] -shl 16) + ([int64]$packet[46] -shl 8) + [int64]$packet[47]
if ($seconds -eq 0) { throw "empty response from $Server" }
$epoch = New-Object DateTime(1900, 1, 1, 0, 0, 0, [DateTimeKind]::Utc)
$epoch.AddMilliseconds(($seconds * 1000.0) + ($fraction * 1000.0 / 4294967296.0))
}
function Get-HttpUtc {
param([Parameter(Mandatory)][string] $HostName, [int] $Timeout = 4000)
$request = [Net.HttpWebRequest]::Create(("http://{0}/" -f $HostName))
$request.Method = 'HEAD'
$request.Timeout = $Timeout
$request.AllowAutoRedirect = $false
$response = $request.GetResponse()
try { $header = $response.Headers['Date'] } finally { $response.Close() }
if (-not $header) { throw "no Date header from $HostName" }
[DateTime]::Parse($header, [Globalization.CultureInfo]::InvariantCulture,
[Globalization.DateTimeStyles]::AdjustToUniversal -bor [Globalization.DateTimeStyles]::AssumeUniversal)
}
# ------------------------------------------------------------------------------ gather time
$readings = @()
foreach ($server in $NtpServer) {
try {
$utc = Get-NtpUtc -Server $server -Timeout $TimeoutMs
$readings += [pscustomobject]@{ Source = "ntp:$server"; Utc = $utc }
Write-Host (" {0,-34} {1:yyyy-MM-dd HH:mm:ss}" -f "ntp:$server", $utc)
} catch {
Write-Host (" {0,-34} unavailable ({1})" -f "ntp:$server", $_.Exception.Message.Split([Environment]::NewLine)[0])
}
}
# Only fall back when NTP could not corroborate itself - HTTP is second-granularity, which is
# fine for a five-minute tolerance but worse than NTP when NTP is available.
if ($readings.Count -lt 2) {
Write-Host ' (fewer than two NTP replies - falling back to plain-HTTP Date headers)'
foreach ($h in $HttpTimeSource) {
try {
$utc = Get-HttpUtc -HostName $h -Timeout $TimeoutMs
$readings += [pscustomobject]@{ Source = "http:$h"; Utc = $utc }
Write-Host (" {0,-34} {1:yyyy-MM-dd HH:mm:ss}" -f "http:$h", $utc)
} catch {
Write-Host (" {0,-34} unavailable" -f "http:$h")
}
}
}
if ($readings.Count -lt 2) {
throw ("Only {0} time source(s) answered. Refusing to set the clock on a single unverified reading - one hijacked or broken source should not be able to move this machine's clock." -f $readings.Count)
}
# --------------------------------------------------------------------------- corroborate
$agreed = $null
for ($i = 0; $i -lt $readings.Count -and -not $agreed; $i++) {
for ($j = $i + 1; $j -lt $readings.Count; $j++) {
$delta = [Math]::Abs((($readings[$i].Utc) - ($readings[$j].Utc)).TotalSeconds)
if ($delta -le $MaxDisagreementSeconds) {
$agreed = $readings[$i]
Write-Host ("`n taking {0}, corroborated by {1} (agree within {2:N1}s)" -f $agreed.Source, $readings[$j].Source, $delta)
break
}
}
}
if (-not $agreed) {
throw ("No two sources agree within {0}s: {1}" -f $MaxDisagreementSeconds,
(($readings | ForEach-Object { "$($_.Source)=$($_.Utc.ToString('HH:mm:ss'))" }) -join ', '))
}
$realUtc = $agreed.Utc
# ------------------------------------------------------- inspect what the OS believes
# $zone is the zone as DEFINED (rules read from the registry, ignoring the auto-DST switch).
# TimeZoneInfo.Local is the zone as the OS APPLIES it. When auto-DST is off those differ, and
# the gap between them is the whole problem.
$zone = [TimeZoneInfo]::FindSystemTimeZoneById([TimeZoneInfo]::Local.Id)
$osOffset = [TimeZoneInfo]::Local.GetUtcOffset($realUtc)
$trueOffset = $zone.GetUtcOffset($realUtc)
$offsetGap = $trueOffset - $osOffset
$ddtd = $null
try {
$ddtd = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation' `
-Name DynamicDaylightTimeDisabled -ErrorAction Stop).DynamicDaylightTimeDisabled
} catch { }
$before = Get-Date
$beforeUtc = $before.ToUniversalTime()
$utcSkew = [Math]::Round(($beforeUtc - $realUtc).TotalSeconds, 3)
Write-Host ''
Write-Host '=== clock ==='
Write-Host (" time zone : {0}" -f $zone.Id)
Write-Host (" auto-DST : {0}" -f $(if ($ddtd -eq 1) { 'DISABLED' } else { 'enabled' }))
Write-Host (" DST rules the OS uses : {0}" -f ([TimeZoneInfo]::Local.GetAdjustmentRules()).Count)
Write-Host (" DST rules defined : {0}" -f ($zone.GetAdjustmentRules()).Count)
Write-Host (" DST active right now : {0}" -f $zone.IsDaylightSavingTime($realUtc))
Write-Host (" offset the OS applies : {0}" -f $osOffset)
Write-Host (" offset that is correct: {0}" -f $trueOffset)
Write-Host ''
Write-Host (" machine local time : {0:yyyy-MM-dd HH:mm:ss.fff}" -f $before)
Write-Host (" machine time (UTC) : {0:yyyy-MM-dd HH:mm:ss.fff} <- what Azure DevOps validates" -f $beforeUtc)
Write-Host (" internet time (UTC) : {0:yyyy-MM-dd HH:mm:ss.fff}" -f $realUtc)
Write-Host (" UTC skew : {0} seconds ({1:N1} min, {2:N2} days)" -f $utcSkew, ($utcSkew / 60), ($utcSkew / 86400))
# ------------------------------------------------------------------------------------ apply
$identity = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
if (-not $identity.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw 'This needs an elevated session (SeSystemTimePrivilege, and the timezone setting). Re-run as administrator.'
}
# --- step 1: make the OS apply the right offset -----------------------------------------
# Windows stores system time as UTC and renders local time as UTC + offset. So a machine can
# show the correct WALL CLOCK and still have the wrong UTC, if the offset it applies is wrong
# - which is exactly what a DST-disabled box in summer does. Setting the clock alone cannot
# fix that: correct the offset first, or correct local time keeps producing wrong UTC.
if ($offsetGap -ne [TimeSpan]::Zero) {
Write-Host ''
Write-Host (" !! The OS applies {0} but this zone is really {1} at this instant - a {2:N0}h error." -f $osOffset, $trueOffset, $offsetGap.TotalHours)
Write-Host ' Because Windows keeps time internally as UTC, this makes UTC wrong even when the'
Write-Host ' wall clock looks right. Setting the clock without fixing this just moves the error.'
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, ("enable automatic DST for '{0}'" -f $zone.Id))) {
# tzutil is the supported way. Re-selecting the zone without the _dstoff suffix clears
# DynamicDaylightTimeDisabled; poking the registry directly leaves other components
# holding the old value.
& tzutil.exe /s $zone.Id
if ($LASTEXITCODE -ne 0) { throw ("tzutil /s '{0}' failed with exit code {1}" -f $zone.Id, $LASTEXITCODE) }
# .NET caches the local zone for the life of the process.
[TimeZoneInfo]::ClearCachedData()
$nowOffset = [TimeZoneInfo]::Local.GetUtcOffset($realUtc)
Write-Host (" automatic DST enabled - the OS now applies {0}" -f $nowOffset)
if ($nowOffset -ne $trueOffset) {
throw ("Offset is still {0}, expected {1}. Fix the timezone by hand before setting the clock." -f $nowOffset, $trueOffset)
}
}
}
# --- step 2: set the clock ---------------------------------------------------------------
if ([Math]::Abs($utcSkew) -lt 1 -and $offsetGap -eq [TimeSpan]::Zero) {
Write-Host ''
Write-Host ' UTC is already within a second of correct and the offset is right - nothing to do.'
return
}
# Derived from UTC, not from local time, so the result is correct regardless of what the
# offset was when this script started.
$elapsed = (Get-Date).ToUniversalTime() - $beforeUtc
$targetUtc = $realUtc.Add($elapsed)
$correctLocal = [TimeZoneInfo]::ConvertTimeFromUtc($targetUtc, [TimeZoneInfo]::Local)
if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME, ("set clock to {0:yyyy-MM-dd HH:mm:ss} local ({1:HH:mm:ss} UTC)" -f $correctLocal, $targetUtc))) {
Set-Date -Date $correctLocal | Out-Null
# Verify against UTC. Local time looking right is what made this problem invisible in the
# first place, so the check that matters is the one on UTC.
$afterUtc = (Get-Date).ToUniversalTime()
$residual = [Math]::Round(($afterUtc - $realUtc.Add((Get-Date).ToUniversalTime() - $beforeUtc)).TotalSeconds, 3)
Write-Host ''
Write-Host (" local time is now : {0:yyyy-MM-dd HH:mm:ss} {1}" -f (Get-Date), [TimeZoneInfo]::Local.GetUtcOffset((Get-Date)))
Write-Host (" UTC is now : {0:yyyy-MM-dd HH:mm:ss}" -f $afterUtc)
Write-Host (" residual UTC error : {0} seconds" -f $residual)
if ([Math]::Abs($residual) -gt 60) {
Write-Host ' !! Still more than a minute out. Do not expect the agent to authenticate.'
} else {
Write-Host ' UTC is correct. Azure DevOps will accept this machine''s tokens.'
}
Write-Host ''
Write-Host ' An agent that failed to authenticate while the clock was wrong does not always retry'
Write-Host ' cleanly. Restart it:'
Write-Host ' Restart-Service vstsagent.* -Force'
}
<#
.SYNOPSIS
Tests whether outbound HTTPS works from this machine, and says which layer failed.
.DESCRIPTION
Written to diagnose an Azure DevOps deployment agent that shows offline on a machine
that otherwise looks healthy. "HTTPS is broken" has several very different causes and
they are easy to confuse, so each is tested separately and reported separately:
DNS - does the name resolve at all
TCP - does port 443 accept a connection (firewall, proxy, service down)
TLS - does the handshake complete, and if not, is the CLOCK the reason
HTTP - does the server answer, and how far off is our clock from its own
The clock check is the point. A machine whose clock is wrong by years fails TLS because
the certificate reads as not-yet-valid or expired; a machine wrong by an hour passes TLS
but still cannot authenticate to Azure DevOps, because OAuth tokens carry nbf/exp claims
validated with only a few minutes of tolerance. Those two look identical from the
outside ("agent offline") and need completely different fixes, so both are reported with
the actual numbers rather than a pass/fail.
Read-only. Changes nothing, needs no elevation.
.PARAMETER Target
Hosts to test. Defaults to the endpoints an ADO agent and this repo's deploy scripts
actually need.
.EXAMPLE
.\Test-OutboundHttps.ps1
.EXAMPLE
.\Test-OutboundHttps.ps1 -Target dev.azure.com, myinternalhost -Port 443
#>
[CmdletBinding()]
param(
[string[]] $Target = @('dev.azure.com', 'vsrm.dev.azure.com', 'login.microsoftonline.com'),
[int] $Port = 443,
[int] $TimeoutMs = 8000,
# Plain-HTTP hosts used only to read a trustworthy Date header. These are the same
# captive-portal endpoints timeset.cfm uses: no certificate to validate, so they still
# answer when the clock is too wrong for HTTPS to work at all.
[string[]] $TimeSource = @('www.msftconnecttest.com', 'detectportal.firefox.com')
)
Clear-Host
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue'
function Write-Result {
param([string] $Layer, [bool] $Ok, [string] $Detail)
$tag = if ($Ok) { 'ok ' } else { 'FAIL' }
Write-Host (" {0} {1,-5} {2}" -f $tag, $Layer, $Detail)
}
# ---------------------------------------------------------------------------- environment
Write-Host ''
Write-Host '=== this machine ==='
Write-Host (" hostname : {0}" -f $env:COMPUTERNAME)
Write-Host (" local time : {0}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff K'))
Write-Host (" local time (UTC) : {0}" -f (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss'))
Write-Host (" time zone : {0}" -f [TimeZoneInfo]::Local.Id)
# Auto-DST off means .NET drops the zone's adjustment rules, so any UTC->local conversion
# lands an hour out during summer. That silently breaks anything that sets the clock.
$ddtd = $null
try {
$ddtd = (Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation' `
-Name DynamicDaylightTimeDisabled -ErrorAction Stop).DynamicDaylightTimeDisabled
} catch { }
$autoDst = if ($ddtd -eq 1) { 'DISABLED' } else { 'enabled' }
Write-Host (" auto-DST : {0}" -f $autoDst)
Write-Host (" DST rules on Local : {0} (0 means UTC->local conversion will be wrong in summer)" -f ([TimeZoneInfo]::Local.GetAdjustmentRules()).Count)
$proxy = [System.Net.WebRequest]::DefaultWebProxy
$proxyFor = $null
try { $proxyFor = $proxy.GetProxy([Uri]("https://{0}" -f $Target[0])) } catch { }
if ($proxyFor -and $proxyFor.AbsoluteUri -notlike ("*{0}*" -f $Target[0])) {
Write-Host (" proxy : {0}" -f $proxyFor.AbsoluteUri)
} else {
Write-Host ' proxy : none (direct)'
}
Write-Host (" .NET SecurityProtocol: {0}" -f [Net.ServicePointManager]::SecurityProtocol)
# ------------------------------------------------------------------- real time, over HTTP
# Established before the TLS tests so a clock verdict is available even if every HTTPS
# attempt fails.
$realUtc = $null
foreach ($ts in $TimeSource) {
try {
$req = [Net.HttpWebRequest]::Create(("http://{0}/" -f $ts))
$req.Method = 'HEAD'
$req.Timeout = $TimeoutMs
$req.AllowAutoRedirect = $false
$before = [DateTime]::UtcNow
$resp = $req.GetResponse()
$after = [DateTime]::UtcNow
$hdr = $resp.Headers['Date']
$resp.Close()
if ($hdr) {
# Round-trip midpoint, so our own latency does not read as skew.
$realUtc = [DateTime]::Parse($hdr, [Globalization.CultureInfo]::InvariantCulture,
[Globalization.DateTimeStyles]::AdjustToUniversal -bor [Globalization.DateTimeStyles]::AssumeUniversal)
$localMid = $before.AddTicks((($after - $before).Ticks / 2))
$skew = [int]($localMid - $realUtc).TotalSeconds
break
}
} catch { }
}
Write-Host ''
Write-Host '=== clock ==='
if ($null -eq $realUtc) {
Write-Host ' could not reach any plain-HTTP time source - cannot judge the clock'
$skew = $null
} else {
Write-Host (" real UTC (internet) : {0}" -f $realUtc.ToString('yyyy-MM-dd HH:mm:ss'))
Write-Host (" this machine (UTC) : {0}" -f (Get-Date).ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss'))
$absSkew = [Math]::Abs($skew)
Write-Host (" skew : {0} s ({1:N1} min, {2:N2} days)" -f $skew, ($skew / 60), ($skew / 86400))
if ($absSkew -le 60) {
Write-Host ' verdict : clock is fine'
} elseif ($absSkew -le 300) {
Write-Host ' verdict : WARNING - beyond a minute. Borderline for OAuth token validation.'
} else {
Write-Host ' verdict : PROBLEM - too far out for Azure DevOps to accept this machine''s tokens.'
Write-Host ' TLS may still succeed, so the agent can look "connected but unauthorised".'
}
}
# ------------------------------------------------------------------------ per-target tests
foreach ($t in $Target) {
Write-Host ''
Write-Host ("=== {0}:{1} ===" -f $t, $Port)
# --- DNS
$addresses = @()
try {
$addresses = @([Net.Dns]::GetHostAddresses($t) | Where-Object { $_.AddressFamily -eq 'InterNetwork' } | ForEach-Object { $_.IPAddressToString })
Write-Result 'DNS' ($addresses.Count -gt 0) ($addresses -join ', ')
} catch {
Write-Result 'DNS' $false $_.Exception.Message
continue
}
if (-not $addresses) { continue }
# --- TCP
$tcp = New-Object Net.Sockets.TcpClient
try {
$iar = $tcp.BeginConnect($t, $Port, $null, $null)
if (-not $iar.AsyncWaitHandle.WaitOne($TimeoutMs)) {
Write-Result 'TCP' $false ("no response within {0} ms - firewall or nothing listening" -f $TimeoutMs)
$tcp.Close(); continue
}
$tcp.EndConnect($iar)
Write-Result 'TCP' $true 'connected'
} catch {
Write-Result 'TCP' $false $_.Exception.Message
$tcp.Close(); continue
}
# --- TLS, validating the certificate ourselves so the reason is visible
$chainErrors = 'none'
$tlsOk = $false
$protocol = 'unknown'
$script:certInfo = $null
$script:capturedErrors = $null
try {
$callback = [Net.Security.RemoteCertificateValidationCallback] {
param($senderObj, $certificate, $chain, $errors)
# Copy the values out HERE. The certificate's native handle is released when the
# SslStream closes, and reading its dates afterwards throws "m_safeCertContext is
# an invalid handle" - which previously left the dates empty and made the clock
# verdict below fire on a machine whose clock was perfectly correct.
try {
$c2 = New-Object Security.Cryptography.X509Certificates.X509Certificate2 $certificate
$script:certInfo = [pscustomobject]@{
Subject = $c2.Subject
NotBefore = $c2.NotBefore
NotAfter = $c2.NotAfter
}
} catch { }
$script:capturedErrors = $errors
return $true # accept, so the handshake completes and we can report the reason
}
$ssl = New-Object Net.Security.SslStream($tcp.GetStream(), $false, $callback)
# The protocols must be named explicitly. AuthenticateAsClient(host) on its own uses
# SslProtocols.Default, which on Windows PowerShell 5.1 / .NET Framework means
# SSL3 + TLS 1.0 - and dev.azure.com requires 1.2 or better, so the handshake fails
# with "the client and server cannot communicate, because they do not possess a common
# algorithm". That reads exactly like a broken box and is purely an artefact of the
# default. Ask for 1.2 and 1.3, falling back if this runtime has no 1.3.
$protocols = [Net.SecurityProtocolType]::Tls12
try { $protocols = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 } catch { }
$ssl.AuthenticateAsClient($t, $null, [Security.Authentication.SslProtocols]$protocols, $false)
$tlsOk = $true
$protocol = $ssl.SslProtocol
if ($script:capturedErrors -and $script:capturedErrors -ne 'None') { $chainErrors = $script:capturedErrors }
$ssl.Close()
} catch {
Write-Result 'TLS' $false $_.Exception.Message
}
$cert = $script:certInfo
if ($tlsOk) {
Write-Result 'TLS' ($chainErrors -eq 'none') ("protocol={0} chainStatus={1}" -f $protocol, $chainErrors)
if ($null -eq $cert) {
Write-Host ' certificate details unavailable - skipping the clock-vs-cert check rather than guessing'
} else {
$nb = $cert.NotBefore
$na = $cert.NotAfter
$now = Get-Date
Write-Host (" cert valid : {0:yyyy-MM-dd} .. {1:yyyy-MM-dd} subject={2}" -f $nb, $na, $cert.Subject)
# The decisive test: is OUR clock inside the certificate's validity window?
if ($now -lt $nb) {
Write-Host (" >>> THE CLOCK IS WHY TLS FAILS: this machine reads {0:yyyy-MM-dd}, before the certificate becomes valid." -f $now)
} elseif ($now -gt $na) {
Write-Host (" >>> THE CLOCK IS WHY TLS FAILS: this machine reads {0:yyyy-MM-dd}, after the certificate expired." -f $now)
} else {
Write-Host ' clock is inside the certificate validity window, so TLS is not clock-blocked'
}
}
}
$tcp.Close()
# --- HTTP, only meaningful if TLS worked
if ($tlsOk) {
try {
$req = [Net.HttpWebRequest]::Create(("https://{0}/" -f $t))
$req.Method = 'HEAD'
$req.Timeout = $TimeoutMs
$req.AllowAutoRedirect = $false
$resp = $req.GetResponse()
Write-Result 'HTTP' $true ("{0} {1}" -f [int]$resp.StatusCode, $resp.StatusCode)
$resp.Close()
} catch [Net.WebException] {
if ($_.Exception.Response) {
$code = [int]$_.Exception.Response.StatusCode
# Any HTTP status means the round trip worked; 401/403 on a root path is normal.
Write-Result 'HTTP' $true ("{0} - reachable (status is not the point here)" -f $code)
} else {
Write-Result 'HTTP' $false $_.Exception.Message
}
} catch {
Write-Result 'HTTP' $false $_.Exception.Message
}
}
}
Write-Host ''
Write-Host '=== how to read this ==='
Write-Host ' TCP fails -> firewall, proxy, or nothing listening. Not a clock problem.'
Write-Host ' TLS fails + clock outside cert -> fix the clock; nothing else will work until you do.'
Write-Host ' TLS fine but skew over ~5 min -> the agent will connect and still fail to authenticate.'
Write-Host ' all layers ok but agent offline -> not connectivity. Read the agent''s _diag log next:'
Write-Host ' Get-ChildItem C:\azagent\A1\_diag\Agent_*.log | Sort LastWriteTime | Select -Last 1 | Get-Content -Tail 60'
Write-Host ''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment