<#************************************************************************************************************* # Script Name : Test-RestEndpoint.ps1 # Location : $root\Diagnostics\ (update to your repo path) # Purpose : Tests REST API endpoint health — DNS, TCP, TLS handshake, live API call, and latency. # Auto-detects PowerShell version and adjusts behavior (TLS 1.2/1.3 enforcement for 5.1, # native -SkipCertificateCheck for 7+, correct error-stream parsing per version). # Used for vRA/vRO workflow troubleshooting when REST integrations time out. # Notes : Practial Sequence - # 1. RDP to the PowerShell host # 2. Copy Test-RestEndpoint.ps1 to C:\Scripts\Diagnostics\ # 3. Run elevated: # .\Test-RestEndpoint.ps1 -Uri "https://cmdb.internal.example.com/api/v2/servers" -Token "xyz" # # Date : August 14, 2026 1:08:04 PM # - Initial Version # # Date : August 14, 2026 1:31:00 PM # - Updated : Adding PS version testing and TLS verification # Author : Patrick Burwell, [email protected] #*************************************************************************************************************#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Uri,
[Parameter(Mandatory = $false)]
[string]$Token,
[Parameter(Mandatory = $false)]
[switch]$UseCurrentUser,
[Parameter(Mandatory = $false)]
[int]$TimeoutSec = 15,
[Parameter(Mandatory = $false)]
[switch]$SkipTlsCheck
)
#PARAM blocks always go to the top
'Test-RestEndpoint.ps1'
<#*************************************************************************************************************
# Script Name : Test-RestEndpoint.ps1
# Location : $root\Diagnostics\ (update to your repo path)
# Purpose : Tests REST API endpoint health — DNS, TCP, TLS handshake, live API call, and latency.
# Auto-detects PowerShell version and adjusts behavior (TLS 1.2/1.3 enforcement for 5.1,
# native -SkipCertificateCheck for 7+, correct error-stream parsing per version).
# Used for vRA/vRO workflow troubleshooting when REST integrations time out.
# Notes : Practial Sequence -
# 1. RDP to the PowerShell host
# 2. Copy Test-RestEndpoint.ps1 to C:\Scripts\Diagnostics\
# 3. Run elevated:
# .\Test-RestEndpoint.ps1 -Uri "https://cmdb.internal.example.com/api/v2/servers" -Token "xyz"
#
# Date : August 14, 2026 1:08:04 PM
# - Initial Version
#
# Date : August 14, 2026 1:31:00 PM
# - Updated : Adding PS version testing and TLS verification
# Author : Patrick Burwell, [email protected]
#*************************************************************************************************************#>
#Include this on all scripts
if(!(Get-ExecutionPolicy ) -eq "Bypass"){
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass -Force | Out-Null
try {
Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy Bypass -Force -ErrorAction Stop | Out-Null
}
catch {
Write-Warning "LocalMachine execution policy not set (requires elevation). Continuing with CurrentUser scope."
}
}
$ErrorActionPreference = "Stop"
$results = @()
#=============================================================================================================
# PS VERSION DETECTION — adjusts behavior for 5.1 vs 7+
#=============================================================================================================
$psMajor = $PSVersionTable.PSVersion.Major
$isLegacyPS = ($psMajor -le 5)
Write-Host "Detected PowerShell version: $($PSVersionTable.PSVersion) $(if($isLegacyPS){'(legacy mode)'}else{'(modern mode)'})" -ForegroundColor DarkCyan
if ($isLegacyPS) {
# PS 5.1 defaults to SSL3/TLS1.0 — modern APIs (incl. vRA/vRO) will reject the handshake.
# Force TLS 1.2, and TLS 1.3 if the underlying .NET Framework supports it (4.8+).
try {
[System.Net.ServicePointManager]::SecurityProtocol = `
[System.Net.SecurityProtocolType]::Tls12 -bor `
[System.Net.SecurityProtocolType]::Tls13
}
catch {
# TLS 1.3 enum missing on older .NET — fall back to 1.2 only
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
}
# PS 5.1 certificate bypass must go through ServicePointManager (no -SkipCertificateCheck param)
if ($SkipTlsCheck) {
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
}
}
#=============================================================================================================
# STEP 1 — Parse target
#=============================================================================================================
$hostname = ([Uri]$Uri).Host
$port = ([Uri]$Uri).Port
if ($port -le 0) { $port = 443 } # Uri.Port returns -1 if not explicitly specified
Write-Host ""
Write-Host "=== TESTING: $Uri ===" -ForegroundColor Cyan
Write-Host ""
#=============================================================================================================
# STEP 2 — DNS Resolution
#=============================================================================================================
Write-Host "[1/5] Testing DNS resolution for $hostname..." -ForegroundColor Yellow
try {
$dnsResults = Resolve-DnsName -Name $hostname -Type A -ErrorAction Stop
$aRecords = $dnsResults | Where-Object { $_.IPAddress }
if (-not $aRecords) { throw "No A records found" }
$aRecords | ForEach-Object {
$results += "DNS OK: $hostname -> $($_.IPAddress)"
Write-Host " [+] $hostname resolves to $($_.IPAddress)" -ForegroundColor Green
}
}
catch {
$results += "DNS FAILED: $hostname not resolving"
Write-Host " [X] DNS lookup failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host ""
Write-Host "=== SUMMARY ===" -ForegroundColor Cyan
$results | ForEach-Object { Write-Host " $_" }
exit 1
}
Write-Host ""
#=============================================================================================================
# STEP 3 — TCP Connectivity
#=============================================================================================================
Write-Host "[2/5] Testing TCP connectivity to $hostname on port $port..." -ForegroundColor Yellow
try {
$tcpTest = Test-NetConnection -ComputerName $hostname -Port $port -WarningAction SilentlyContinue
if ($tcpTest.TcpTestSucceeded) {
$results += "TCP OK: $hostname : $port is reachable"
Write-Host " [+] TCP connection established to $hostname : $port" -ForegroundColor Green
}
else { throw "TCP connection failed" }
}
catch {
$results += "TCP FAILED: Cannot reach $hostname : $port"
Write-Host " [X] TCP connection failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host ""
Write-Host "=== SUMMARY ===" -ForegroundColor Cyan
$results | ForEach-Object { Write-Host " $_" }
Write-Host ""
Write-Host "Check firewall rules, routing, and whether the service is listening." -ForegroundColor Yellow
exit 1
}
Write-Host ""
#=============================================================================================================
# STEP 4 — TLS/SSL Handshake (raw socket — identical logic both PS versions)
#=============================================================================================================
Write-Host "[3/5] Testing TLS/SSL handshake..." -ForegroundColor Yellow
try {
$tcpClient = New-Object System.Net.Sockets.TcpClient($hostname, $port)
$sslStream = New-Object System.Net.Security.SslStream(
$tcpClient.GetStream(),
$false,
{ param($sender, $cert, $chain, $errors)
if ($SkipTlsCheck) { return $true }
return ($errors -eq [System.Net.Security.SslPolicyErrors]::None)
}
)
$sslStream.AuthenticateAsClient($hostname)
$results += "TLS OK: SSL handshake successful with $hostname"
Write-Host " [+] TLS handshake succeeded using $($sslStream.SslProtocol)" -ForegroundColor Green
Write-Host " Certificate subject: $($sslStream.RemoteCertificate.Subject)"
Write-Host " Certificate issuer: $($sslStream.RemoteCertificate.Issuer)"
$sslStream.Dispose()
$tcpClient.Close()
}
catch {
$results += "TLS FAILED: SSL handshake error"
Write-Host " [X] TLS handshake failed: $($_.Exception.Message)" -ForegroundColor Red
Write-Host " Likely causes: expired/self-signed cert, TLS version mismatch, or inspection device in path." -ForegroundColor Yellow
}
Write-Host ""
#=============================================================================================================
# STEP 5 — Live REST Call (version-specific parameter handling)
#=============================================================================================================
Write-Host "[4/5] Making test REST call to $Uri ..." -ForegroundColor Yellow
$headers = @{}
if ($Token) { $headers.Authorization = "Bearer $Token" }
# Use Invoke-WebRequest (not Invoke-RestMethod) so we capture HTTP status codes reliably on both versions
$webParams = @{
Uri = $Uri
Method = "GET"
Headers = $headers
TimeoutSec = $TimeoutSec
ErrorAction = "Stop"
}
if ($UseCurrentUser -or (-not $Token)) {
$webParams.UseDefaultCredentials = $true
}
if ($isLegacyPS) {
# PS 5.1: avoid IE engine dependency (fails on Server Core / no-IE machines)
$webParams.UseBasicParsing = $true
}
else {
# PS 7+: native cert skip parameter
if ($SkipTlsCheck) { $webParams.SkipCertificateCheck = $true }
}
try {
$response = Invoke-WebRequest @webParams
$results += "REST OK: API responded successfully (HTTP $($response.StatusCode))"
Write-Host " [+] REST call succeeded — HTTP $($response.StatusCode) $($response.StatusDescription)" -ForegroundColor Green
# Pretty-print a preview of the body
$preview = $response.Content
if ($preview.Length -gt 300) { $preview = $preview.Substring(0, 300) + "..." }
Write-Host " Response preview: $preview"
}
catch {
$results += "REST FAILED: API request error"
Write-Host " [X] REST call failed: $($_.Exception.Message)" -ForegroundColor Red
# Status code extraction differs between versions:
# PS 5.1 -> System.Net.HttpWebResponse | PS 7+ -> System.Net.Http.HttpResponseMessage
$statusCode = $null
if ($_.Exception.Response) {
if ($isLegacyPS) {
$statusCode = [int]$_.Exception.Response.StatusCode
}
else {
$statusCode = [int]$_.Exception.Response.StatusCode # works via enum cast in 7+
}
}
if ($statusCode) {
Write-Host " HTTP Status Code: $statusCode" -ForegroundColor Yellow
switch ($statusCode) {
401 { Write-Host " AUTH FAILURE — token invalid/expired, or credentials rejected." -ForegroundColor Red }
403 { Write-Host " FORBIDDEN — authenticated, but insufficient permissions." -ForegroundColor Red }
404 { Write-Host " NOT FOUND — endpoint path wrong, or API route changed (common after upgrades)." -ForegroundColor Red }
{ $_ -ge 500 } { Write-Host " SERVER-SIDE FAILURE — the API service itself is broken. Engage the app owner." -ForegroundColor Red }
}
}
elseif ($_.Exception.Message -match "timed out|timeout") {
Write-Host " TIMEOUT — endpoint accepted TCP but never responded. Check API app pool / service health." -ForegroundColor Red
}
}
Write-Host ""
#=============================================================================================================
# STEP 6 — Latency measurement
#=============================================================================================================
Write-Host "[5/5] Measuring response time (3 samples)..." -ForegroundColor Yellow
$latencies = @()
foreach ($i in 1..3) {
try {
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$null = Invoke-WebRequest @webParams
$sw.Stop()
$latencies += $sw.ElapsedMilliseconds
Write-Host " [+] Sample $i : $($sw.ElapsedMilliseconds) ms" -ForegroundColor Green
}
catch {
$sw.Stop()
Write-Host " [!] Sample $i failed after $($sw.ElapsedMilliseconds) ms" -ForegroundColor Yellow
}
}
if ($latencies.Count -gt 0) {
$avg = [math]::Round(($latencies | Measure-Object -Average).Average, 0)
$results += "LATENCY: avg ${avg} ms over $($latencies.Count) samples"
Write-Host " Average: $avg ms" -ForegroundColor $(if ($avg -gt 2000) { "Red" } elseif ($avg -gt 500) { "Yellow" } else { "Green" })
}
#=============================================================================================================
# SUMMARY + EVIDENCE FILE
#=============================================================================================================
Write-Host ""
Write-Host "=== CONNECTIVITY TEST SUMMARY ===" -ForegroundColor Cyan
Write-Host ""
$results | ForEach-Object {
if ($_ -match "OK|LATENCY") { Write-Host " [PASS] $_" -ForegroundColor Green }
else { Write-Host " [FAIL] $_" -ForegroundColor Red }
}
$outputFile = "$env:TEMP\RestEndpointTest_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
@"
Burwell.tech — REST Endpoint Diagnostic
Target : $Uri
Host : $env:COMPUTERNAME
User : $env:USERNAME
PSVersion : $($PSVersionTable.PSVersion)
Date : $(Get-Date -Format "MMMM d, yyyy h:mm:ss tt")
----------------------------------------
$($results -join "`r`n")
"@ | Out-File -FilePath $outputFile
Write-Host ""
Write-Host "Evidence file saved to: $outputFile (attach to ticket)" -ForegroundColor Cyan



Leave a Reply