Last active
September 8, 2026 20:10
-
-
Save shannonfritz/4c9f1cf800f3406729a58417639736f3 to your computer and use it in GitHub Desktop.
Test network connectivity to endpoints used by Windows 365 Cloud PCs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| $ScriptName = 'Test-Windows365Endpoints' | |
| $ScriptVer = 'v0.09' | |
| # Test network connectivity to Windows 365 Services (including AVD and Intune) | |
| # There are now two ways to use this script: | |
| # 1. Run from the Cloud PC or a VM connected to an Azure VNet where CPCs will be provisioned | |
| # 2. Run from the Client PC or a device connected to the Network where the client device is used | |
| # Run this script directly from this gist using the command below | |
| # powershell -ex bypass "iex (irm https://aka.ms/testw365vnet)" | |
| # Changelog | |
| # v0.09 - 2026-Aug-27 | |
| # - Added: GPU Cloud PC host checks for download.microsoft.com and go.microsoft.com | |
| # - Added: download tests for the NVIDIA and AMD GPU driver resource files | |
| # v0.08 - 2026-Apr-30 | |
| # - Fixed: missing comma in client endpoint list (caissuers/www.microsoft.com were concatenated) | |
| # - Fixed: removed duplicate www.microsoft.com:80 entry in client list | |
| # - Fixed: *.events.data.microsoft.com port corrected from 80 to 443 (client list) | |
| # - Fixed: *.microsoftaik.azure.net port corrected to 80 (client list) | |
| # - Fixed: OCSP comment typo (was OSCP) | |
| # - Added: port 32526 for 168.63.129.16 (WireServer) per updated AVD docs | |
| # - Added: UDP/STUN connectivity test for TURN relay endpoint | |
| # - Added: 51.5.0.0/16 TURN relay (UDP/3478) for both host and client lists | |
| # - Added: www.msftconnecttest.com:80 to AVD host list | |
| # - Added: wildcard override testing with known candidate subdomains | |
| # - Added: DNS resolution fallback for remaining untestable wildcards | |
| # - Added: yellow notes for Azure-only and untestable endpoints | |
| # - Added: try/catch for Intune endpoint list fetch | |
| # - Added: summary (pass/fail/wildcard counts) at end of run | |
| # - Removed: dead emdl.ws.microsoft.com override (retired endpoint) | |
| # - Updated: endpoint list reference dates to 2026-Apr-30 | |
| # v0.07 - 2026-Mar-10 | |
| # - Added: client endpoint list for end-user device testing (test method 2) | |
| # - Added: certificate (AIA/CRL/OCSP) hosts to client list per Azure CA docs | |
| # - Added: wildcard overrides for *.windows.cloud.microsoft and *.aikcertaia.microsoft.com | |
| # - Added: AVD endpoints for certificates, CTL, aka.ms, and *.service/windows.cloud.microsoft | |
| # - Removed: *.cmdagent.trafficmanager.net (retired) | |
| # - Removed: cdp.geotrust.com and status.geotrust.com (no longer in Azure CA docs) | |
| # - Updated: endpoint list reference dates to 2026-Mar-10 | |
| # - Updated: certificate hosts with explicit :80 port numbers | |
| # Host/Ports were taken from the link below on 2026-Apr-30 - Check for newer lists and update as necessary | |
| # https://learn.microsoft.com/en-us/windows-365/enterprise/requirements-network?tabs=enterprise%2Cent#windows-365-service | |
| $endpoints_w365 = @( | |
| '*.infra.windows365.microsoft.com', | |
| 'login.microsoftonline.com', | |
| 'login.live.com', | |
| 'enterpriseregistration.windows.net', | |
| 'global.azure-devices-provisioning.net:443,5671', | |
| 'hm-iot-in-prod-prap01.azure-devices.net:443,5671', | |
| 'hm-iot-in-prod-prau01.azure-devices.net:443,5671', | |
| 'hm-iot-in-prod-preu01.azure-devices.net:443,5671', | |
| 'hm-iot-in-prod-prna01.azure-devices.net:443,5671', | |
| 'hm-iot-in-prod-prna02.azure-devices.net:443,5671', | |
| 'hm-iot-in-2-prod-preu01.azure-devices.net:443,5671', | |
| 'hm-iot-in-2-prod-prna01.azure-devices.net:443,5671', | |
| 'hm-iot-in-3-prod-preu01.azure-devices.net:443,5671', | |
| 'hm-iot-in-3-prod-prna01.azure-devices.net:443,5671', | |
| 'hm-iot-in-4-prod-prna01.azure-devices.net:443,5671' | |
| ) | |
| # URLs were taken from the link below on 2026-Aug-27 - Check for newer lists and update as necessary | |
| # https://learn.microsoft.com/en-us/windows-365/enterprise/gpu-cloud-pc#allowlist | |
| $endpoints_gpu = @( | |
| 'download.microsoft.com', | |
| 'go.microsoft.com' | |
| ) | |
| $downloadUrls_gpu = @( | |
| 'https://raw.githubusercontent.com/Azure/azhpc-extensions/master/NvidiaGPU/resources.json', | |
| 'https://raw.githubusercontent.com/Azure/azhpc-extensions/master/AmdGPU/resources.json' | |
| ) | |
| # Host/Ports were taken from the link below on 2026-Apr-30 - Check for newer lists and update as necessary | |
| $clientendpoints_w365 = @( | |
| # https://learn.microsoft.com/en-us/azure/virtual-desktop/required-fqdn-endpoint?tabs=azure#end-user-devices | |
| 'login.microsoftonline.com', | |
| '51.5.0.0/16:udp/3478', | |
| '*.wvd.microsoft.com', | |
| '*.servicebus.windows.net', | |
| 'go.microsoft.com', | |
| 'aka.ms', | |
| 'learn.microsoft.com', | |
| 'privacy.microsoft.com', | |
| '*.cdn.office.net', | |
| 'graph.microsoft.com', | |
| 'windows.cloud.microsoft', | |
| 'windows365.microsoft.com', | |
| 'ecs.office.com', | |
| '*.events.data.microsoft.com:443', | |
| '*.microsoftaik.azure.net:80', | |
| 'www.microsoft.com:80', | |
| '*.aikcertaia.microsoft.com:80', | |
| 'azcsprodeusaikpublish.blob.core.windows.net:80', | |
| # https://learn.microsoft.com/en-us/azure/security/fundamentals/azure-ca-details?tabs=root-and-subordinate-cas-list#certificate-downloads-and-revocation-lists | |
| # AIA hosts | |
| 'cacerts.digicert.com:80', | |
| 'cacerts.digicert.cn:80', | |
| 'cacerts.geotrust.com:80', | |
| 'caissuers.microsoft.com:80', | |
| # CRL hosts | |
| 'crl3.digicert.com:80', | |
| 'crl4.digicert.com:80', | |
| 'crl.digicert.cn:80', | |
| # OCSP hosts | |
| 'ocsp.digicert.com:80', | |
| 'ocsp.digicert.cn:80', | |
| 'oneocsp.microsoft.com:80' | |
| ) | |
| # Host/Ports were taken from the link below on 2026-Apr-30 - Check for newer lists and update as necessary | |
| # https://learn.microsoft.com/en-us/azure/virtual-desktop/safe-url-list?tabs=azure#session-host-virtual-machines | |
| $endpoints_avd = @( | |
| 'login.microsoftonline.com:443', | |
| '51.5.0.0/16:udp/3478', | |
| '*.wvd.microsoft.com:443', | |
| 'catalogartifact.azureedge.net:443', | |
| '*.prod.warm.ingest.monitor.core.windows.net:443', | |
| 'gcs.prod.monitoring.core.windows.net:443', | |
| 'azkms.core.windows.net:1688', | |
| 'mrsglobalsteus2prod.blob.core.windows.net:443', | |
| 'wvdportalstorageblob.blob.core.windows.net:443', | |
| '169.254.169.254:80', | |
| '168.63.129.16:80,32526', | |
| 'oneocsp.microsoft.com:80', | |
| 'www.microsoft.com:80', | |
| '*.aikcertaia.microsoft.com:80', | |
| 'azcsprodeusaikpublish.blob.core.windows.net:80', | |
| '*.microsoftaik.azure.net:80', | |
| 'ctldl.windowsupdate.com:80', | |
| 'aka.ms:443', | |
| 'www.msftconnecttest.com:80', | |
| '*.service.windows.cloud.microsoft:443', | |
| '*.windows.cloud.microsoft:443', | |
| '*.windows.static.microsoft:443' | |
| ) | |
| function Test-HostPortList { | |
| param ( | |
| [string]$Hostname, | |
| [string]$PortList = '' | |
| ) | |
| # Manually override Hostname to test for certain hosts... | |
| # Endpoints only reachable from Azure-hosted VMs | |
| $azureOnlyHosts = @('169.254.169.254', '168.63.129.16', 'azkms.core.windows.net') | |
| if ($azureOnlyHosts -contains $Hostname) { | |
| Write-Host "Note $Hostname is only expected to work from an Azure-hosted VM" -ForegroundColor DarkYellow | |
| } | |
| # Endpoints known to not respond to standard connectivity tests | |
| $untestableHosts = @('ekcert.spserv.microsoft.com') | |
| if ($untestableHosts -contains $Hostname) { | |
| Write-Host "Note $Hostname is known to not respond to standard connectivity tests" -ForegroundColor DarkYellow | |
| } | |
| # Map wildcard domains to known candidate subdomains for testing | |
| $wildcardOverrides = @{ | |
| '*.windows.cloud.microsoft' = 'windows.cloud.microsoft' | |
| '*.aikcertaia.microsoft.com' = 'aikcertaia.microsoft.com' | |
| '*.wvd.microsoft.com' = 'rdweb.wvd.microsoft.com' | |
| '*.cdn.office.net' = 'res.cdn.office.net' | |
| '*.events.data.microsoft.com' = 'v10.events.data.microsoft.com' | |
| '*.infra.windows365.microsoft.com' = 'cpcsaamssa1prodprap01.infra.windows365.microsoft.com' | |
| '*.microsoftaik.azure.net' = 'www.microsoftaik.azure.net' | |
| '51.5.0.0/16' = '51.5.0.1' | |
| } | |
| if ($wildcardOverrides.ContainsKey($Hostname)) { | |
| $originalHost = $Hostname | |
| $Hostname = $wildcardOverrides[$Hostname] | |
| Write-Host "Wildcard $originalHost -> testing candidate $Hostname (not 100% deterministic)" -ForegroundColor DarkYellow | |
| $script:WildcardCount++ | |
| } | |
| # For remaining wildcards, attempt DNS resolution as a basic connectivity check | |
| if ($Hostname.StartsWith('*')) { | |
| $baseDomain = $Hostname -replace '^\*\.', '' | |
| Write-Host "Wildcard $Hostname -> DNS lookup $baseDomain" -ForegroundColor DarkYellow | |
| Write-Host -NoNewline "Testing $baseDomain" | |
| Write-Host -NoNewline " ...(DNS) " | |
| try { | |
| $null = Resolve-DnsName $baseDomain -ErrorAction Stop 2>$null | |
| Write-Host "OK" -ForegroundColor Green | |
| $script:WildcardCount++ | |
| } catch { | |
| Write-Host "FAIL" -ForegroundColor Red | |
| $script:FailCount++ | |
| } | |
| return | |
| } | |
| # Manually override port for certain hosts | |
| $portOverrides = @{ | |
| 'time.windows.com' = '80' | |
| } | |
| if ($PortList -eq '' -and $portOverrides.ContainsKey($Hostname)) { | |
| $PortList = $portOverrides[$Hostname] | |
| } | |
| # Use 443 when port is NOT specified | |
| if ($PortList -eq '') { | |
| $PortList = "443" | |
| } | |
| Write-Host -NoNewline "Testing $Hostname" | |
| foreach ($TestPort in $PortList.split(',')) { | |
| Write-Host -NoNewline " ...($TestPort) " | |
| $testResult = $false | |
| if ($TestPort.StartsWith('udp/')) { | |
| # UDP test using a STUN binding request (RFC 5389) | |
| $udpPort = [int]$TestPort.Substring(4) | |
| try { | |
| $udp = New-Object System.Net.Sockets.UdpClient | |
| $udp.Client.ReceiveTimeout = 5000 | |
| $txnId = [byte[]]::new(12) | |
| (New-Object System.Security.Cryptography.RNGCryptoServiceProvider).GetBytes($txnId) | |
| $stunMsg = [byte[]](0x00,0x01,0x00,0x00,0x21,0x12,0xA4,0x42) + $txnId | |
| $null = $udp.Send($stunMsg, $stunMsg.Length, $Hostname, $udpPort) | |
| $remoteEP = New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any, 0) | |
| $null = $udp.Receive([ref]$remoteEP) | |
| $testResult = $true | |
| } catch { | |
| $testResult = $false | |
| } finally { | |
| $udp.Close() | |
| } | |
| } else { | |
| $testResult = Test-NetConnection $Hostname -Port $TestPort -InformationLevel Quiet -WarningAction SilentlyContinue | |
| } | |
| if ($testResult) { | |
| Write-Host -NoNewline "OK" -ForegroundColor Green | |
| $script:PassCount++ | |
| } | |
| else { | |
| Write-Host -NoNewline "FAIL" -ForegroundColor Red | |
| $script:FailCount++ | |
| } | |
| } | |
| Write-Host '' | |
| } | |
| function Test-UrlDownload { | |
| param ( | |
| [string]$Url | |
| ) | |
| Write-Host -NoNewline "Downloading $Url ... " | |
| try { | |
| $response = Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 30 -ErrorAction Stop | |
| if ([int]$response.StatusCode -ge 200 -and [int]$response.StatusCode -lt 300) { | |
| Write-Host "OK" -ForegroundColor Green | |
| $script:PassCount++ | |
| } | |
| else { | |
| Write-Host "FAIL (HTTP $($response.StatusCode))" -ForegroundColor Red | |
| $script:FailCount++ | |
| } | |
| } catch { | |
| Write-Host "FAIL ($($_.Exception.Message))" -ForegroundColor Red | |
| $script:FailCount++ | |
| } | |
| } | |
| ########################### | |
| $script:PassCount = 0 | |
| $script:FailCount = 0 | |
| $script:WildcardCount = 0 | |
| Write-Host "" | |
| Write-Host "$ScriptName $ScriptVer" -ForegroundColor Blue | |
| Write-Host "" | |
| Write-Host "Enter the type of Network Connectivity to test" -ForegroundColor Yellow | |
| Write-Host " 1 - Test from the Host network (from the Cloud PC)" | |
| Write-Host " 2 - Test from the Client network (from the User Device)" | |
| Write-Host " 3 - Test Both" | |
| $DefaultMethod = 1 | |
| $TestMethod = Read-Host "Default [$($DefaultMethod)]" | |
| $TestMethod = ($DefaultMethod,$TestMethod)[[bool]$TestMethod] | |
| Write-Host "" | |
| if (($TestMethod -eq 3) -or ($TestMethod -eq 1)) { | |
| Write-Host "Testing from the Host network" | |
| Write-Host "Loading Windows 365 host list" -ForegroundColor Cyan | |
| foreach ($hostport in $endpoints_w365) { | |
| $hostport = $hostport.split(':'); | |
| Test-HostPortList -Hostname $hostport[0] -PortList $hostport[1] | |
| } | |
| Write-Host "Loading GPU Cloud PC host list" -ForegroundColor Cyan | |
| foreach ($hostport in $endpoints_gpu) { | |
| $hostport = $hostport.split(':'); | |
| Test-HostPortList -Hostname $hostport[0] -PortList $hostport[1] | |
| } | |
| Write-Host "Testing GPU Cloud PC resource downloads" -ForegroundColor Cyan | |
| foreach ($url in $downloadUrls_gpu) { | |
| Test-UrlDownload -Url $url | |
| } | |
| Write-Host "Loading AVD host list" -ForegroundColor Cyan | |
| foreach ($hostport in $endpoints_avd) { | |
| $hostport = $hostport.split(':'); | |
| Test-HostPortList -Hostname $hostport[0] -PortList $hostport[1] | |
| } | |
| Write-Host "Loading Intune host list" -ForegroundColor Cyan | |
| try { | |
| $intuneUrls = (Invoke-RestMethod -Uri ("https://endpoints.office.com/endpoints/WorldWide?ServiceAreas=MEM`&clientrequestid=" + ([GUID]::NewGuid()).Guid)) | Where-Object { $_.ServiceArea -eq "MEM" -and $_.urls } | Select-Object -unique -ExpandProperty urls | |
| foreach ($hostport in $intuneUrls) { | |
| Test-HostPortList -Hostname $hostport | |
| } | |
| } catch { | |
| Write-Host "FAILED to fetch Intune endpoint list: $($_.Exception.Message)" -ForegroundColor Red | |
| $script:FailCount++ | |
| } | |
| } | |
| if (($TestMethod -eq 3) -or ($TestMethod -eq 2)) { | |
| Write-Host "Testing from the Client network" | |
| Write-Host "Loading Windows 365 host list for Clients" -ForegroundColor Cyan | |
| foreach ($hostport in $clientendpoints_w365) { | |
| $hostport = $hostport.split(':'); | |
| Test-HostPortList -Hostname $hostport[0] -PortList $hostport[1] | |
| } | |
| } | |
| # Summary | |
| Write-Host "" | |
| Write-Host "--- Summary ---" -ForegroundColor Blue | |
| Write-Host " Passed: $($script:PassCount)" -ForegroundColor Green | |
| Write-Host " Failed: $($script:FailCount)" -ForegroundColor $(if ($script:FailCount -gt 0) { 'Red' } else { 'Green' }) | |
| Write-Host " Wildcard: $($script:WildcardCount) (DNS or candidate only)" -ForegroundColor DarkYellow | |
| Write-Host "Done." -ForegroundColor Blue |
Hi Shannon! Hope you’re doing well. Thanks for sharing this script. It’s been a huge help over the past couple of years. Just wondering if there are any plans to update it for 2026?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is great, thank you!