Gab.ai

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] #*************************************************************************************************************#>

[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

Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.