Skip to content

Detect the PowerShell edition once and read failed HTTP responses the way it allows - #49

Merged
fadwen merged 2 commits into
mainfrom
feat/runtime-detection
Sep 16, 2026
Merged

fadwen merged 2 commits into
mainfrom
feat/runtime-detection

Conversation

@fadwen

@fadwen fadwen commented Sep 16, 2026

Copy link
Copy Markdown
Owner

The module runs on Windows PowerShell 5.1, because a freshly built domain controller has nothing else, and on PowerShell 7.4, which the REST providers are better served by. This makes the edition something the module reads once and acts on, rather than something seven places tested for themselves.

Detection

Core/Get-TestRuntime.ps1 reads $PSVersionTable at import and detects each capability on the cmdlet that has it: SkipHttpErrorCheck, HttpTimeouts, JsonAsHashtable, ModernTls, NativeUtf8. A parameter that exists on Invoke-WebRequest is one that works, whatever the build, so nothing is inferred from a version number. The result is cached in module scope; Get-TestEnvironmentRuntime (new export, documented) shows it, so a run that behaves differently on two hosts can be explained by its first line.

What differs by edition today

Invoke-TestWebRequest is the one place:

  • PowerShell 7: -SkipHttpErrorCheck brings a 4xx or 5xx back as a response and its body is decoded like any other; TLS is left to negotiate.
  • Windows PowerShell: the cmdlet throws; the body is read once from the exception's response stream; TLS 1.2 is added.
  • Both: the caller receives one error shape, built by Core/New-TestWebRequestError.ps1: an integer Response.StatusCode, a Response.Headers hashtable with each value one string, the body in ErrorDetails. The four Get-<Provider>ErrorDetail helpers no longer read a stream, and the retry loops read one header table on both editions. A transport failure with no response propagates untouched.

Verified live against PingOne from both editions: the same 404 and the same validation refusal, word for word, headers present on both.

Measured and not kept

  • HTTP/2. -HttpVersion 2.0 exists from 7.3 and was tried: a core-tier PingOne seed took 25 s with it and 25 s without, twice each. Not requested; a capability that changes nothing is noise.
  • The AD user and device steps on the runspace pool. Tried, measured on the lab DC, reverted: users created in 10 s instead of 28, then 270 of 310 manager assignments failed with "invalid enumeration context"; devices took 5:30 instead of 0:33 with 148 failures. The RSAT module keeps one ADWS session per process and runspaces trample it. The steps stay on a process per batch, and CLAUDE.md now says why.

Lessons pinned in the suite

  • A WebHeaderCollection assigned from an if expression arrives as an array of its key names (a statement's output is enumerated on the way out).
  • Windows PowerShell rewraps a bare thrown exception and loses a member added to it; a test fake that needs Response throws an ErrorRecord.
  • .NET Framework's HttpStatusCode has no 429 member; a real response carries the unnamed value and is read as an integer.

Checks

  • Invoke-Pester ./Tests/Unit: 2,752 passed on PowerShell 7 (one skipped, the Desktop-only TLS test) and 2,753 passed on Windows PowerShell 5.1, none failed.
  • Invoke-ScriptAnalyzer -Recurse -Severity Error, Warning: clean.
  • ./Build/Build-Help.ps1 rebuilt; Publish-Module.ps1 -WhatIf passes.

…es the way it allows

Windows PowerShell 5.1 stays, because a freshly built domain controller has
nothing else; the REST providers are better served by PowerShell 7.4.
Get-TestRuntime in Core reads the edition at import and detects each
capability on the cmdlet that has it - a parameter that exists on
Invoke-WebRequest is one that works - and everything that differs by edition
reads that object instead of testing $PSVersionTable for itself.
Get-TestEnvironmentRuntime shows the decision.

Invoke-TestWebRequest asks for a failed response with -SkipHttpErrorCheck
where the cmdlet has it and reads the body like any other; without it the
cmdlet throws and the body is read once from the exception's response
stream. Either way the caller gets one shape, built by
New-TestWebRequestError: an integer Response.StatusCode, a Response.Headers
table with each value one string, the body in ErrorDetails. The four
Get-<Provider>ErrorDetail helpers no longer read a stream. A transport
failure with no response propagates untouched.

Measured and not kept: HTTP/2 (a core-tier PingOne seed took 25 s with it
and 25 s without, twice each), and the AD user and device steps on the
runspace pool (270 of 310 manager assignments failed and the device step
took ten times longer on the lab DC, because the RSAT module keeps one ADWS
session per process). Both findings are in CLAUDE.md and the changelog.
Preferred and Recommendation on Get-TestEnvironmentRuntime: 7.4 or later for
every provider, Windows PowerShell 5.1 supported because a freshly built
domain controller has nothing else. Said in the object so a run on the
slower edition sees it without reading the help; the docs page, the about
topic and the changelog say the same.
@fadwen
fadwen merged commit c261587 into main Sep 16, 2026
4 checks passed
@fadwen
fadwen deleted the feat/runtime-detection branch September 16, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant