Core/Get-TestRuntime.ps1
|
function Get-TestRuntime { <# .SYNOPSIS Detects the PowerShell the module is running on and decides, once, which methods it uses .DESCRIPTION The module targets Windows PowerShell 5.1, because a freshly built domain controller has nothing else, and PowerShell 7.4, which the REST providers are better served by. The two differ in what Invoke-WebRequest can do and in what it gets wrong, and every place that cares used to test $PSVersionTable for itself. This is now the one place the edition is read, and what it returns is what the rest of the module acts on. Each capability is detected on the cmdlet, never inferred from a version number: a parameter that exists is one that works on whatever build this is, and a version check would be wrong the day a backport or a preview moved one. The result is computed on first use and cached in module scope, because it cannot change for the life of the session. What is decided here: - Http.ErrorBody: how the body of a failed response is read. 'SkipHttpErrorCheck' where Invoke-WebRequest has that switch (PowerShell 7), so a 4xx or 5xx comes back as a response and the body is read like any other; 'ResponseStream' on Windows PowerShell, where the cmdlet throws and the body has to be pulled from the exception's response stream, once, because the stream cannot be read twice. - Not HTTP/2, deliberately. -HttpVersion exists from PowerShell 7.3 and was tried: a core-tier PingOne seed measured 25 seconds with it and 25 without, twice each, so it is not requested and not listed. A capability that changes nothing is noise. - Http.Tls: 'Default' on PowerShell 7, whose HttpClient negotiates TLS 1.2 and 1.3 on its own; 'Tls12Added' on Windows PowerShell, which can still default to TLS 1.0 and has TLS 1.2 added to the enabled set, never removing any. - Http.Encoding and Http.Progress are the same on both editions and are listed so the object says everything the HTTP layer does: the body always goes out as UTF-8 bytes and the response is always decoded from its raw bytes, because Windows PowerShell corrupts both silently and PowerShell 7 merely hides that; and the progress bar is suppressed around every call, which on Windows PowerShell costs more than the call. - Parallel: 'RunspacePool' on both. Invoke-TestParallel runs a block over many items on a pool of runspaces with the module loaded, which is the mechanism ForEach-Object -Parallel is built on and is available on Windows PowerShell 5.1. - Preferred and Recommendation: whether this is the PowerShell the module is best run on, and one sentence saying so. PowerShell 7.4 or later is preferred for every provider; Windows PowerShell 5.1 is supported because a freshly built domain controller has nothing else, and the Active Directory provider is at home there. Said here, in the object, so a run on the slower edition can see it without reading the help. .PARAMETER Refresh Detect again rather than returning the cached result. For tests. .EXAMPLE PS> (Get-TestRuntime).Http.ErrorBody DESCRIPTION: Which way a failed response's body is read on this host OUTPUT: SkipHttpErrorCheck on PowerShell 7, ResponseStream on Windows PowerShell USE CASE: Called by Invoke-TestWebRequest on every call, from the cache .OUTPUTS PSCustomObject of type TestEnvironmentRuntime: Edition, Version, Platform, Preferred, Recommendation, Capability (one boolean per detected feature), Http, Parallel. .NOTES Author: Jeffrey Stuhr Blog: https://www.techbyjeff.net LinkedIn: https://www.linkedin.com/in/jeffrey-stuhr-034214aa/ #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [switch]$Refresh ) if ($script:TestEnvironmentRuntime -and -not $Refresh) { return $script:TestEnvironmentRuntime } # $PSVersionTable is a hashtable: a key it lacks reads as null, and Windows PowerShell 5.0 # lacks PSEdition altogether. $edition = if ($PSVersionTable['PSEdition']) { [string]$PSVersionTable['PSEdition'] } else { 'Desktop' } $version = [version]$PSVersionTable['PSVersion'] $platform = if ($edition -eq 'Desktop') { 'Windows' } elseif ($PSVersionTable['Platform'] -eq 'Win32NT') { 'Windows' } elseif ([string]$PSVersionTable['OS'] -match 'Darwin') { 'macOS' } elseif ($PSVersionTable['Platform'] -eq 'Unix') { 'Linux' } else { 'Unknown' } $webRequest = @((Get-Command -Name Invoke-WebRequest -CommandType Cmdlet -ErrorAction Stop).Parameters.Keys) $fromJson = @((Get-Command -Name ConvertFrom-Json -CommandType Cmdlet -ErrorAction Stop).Parameters.Keys) $capability = [PSCustomObject]@{ SkipHttpErrorCheck = ($webRequest -contains 'SkipHttpErrorCheck') HttpTimeouts = ($webRequest -contains 'ConnectionTimeoutSeconds') JsonAsHashtable = ($fromJson -contains 'AsHashtable') ModernTls = ($edition -eq 'Core') NativeUtf8 = ($edition -eq 'Core') } $http = [PSCustomObject]@{ ErrorBody = if ($capability.SkipHttpErrorCheck) { 'SkipHttpErrorCheck' } else { 'ResponseStream' } Tls = if ($capability.ModernTls) { 'Default' } else { 'Tls12Added' } Encoding = 'Utf8Bytes' Progress = 'Suppressed' } $preferred = ($edition -eq 'Core' -and $version -ge [version]'7.4') $recommendation = if ($preferred) { 'PowerShell 7.4 or later: the preferred PowerShell for every provider.' } elseif ($edition -eq 'Core') { 'PowerShell 7.4 or later is preferred; this build has the same HTTP paths and is supported.' } else { 'Windows PowerShell 5.1 is supported and is what a freshly built domain controller has; the Entra, Okta, Authentik, FreeIPA and PingOne providers are better served by PowerShell 7.4 or later.' } $script:TestEnvironmentRuntime = [PSCustomObject]@{ PSTypeName = 'TestEnvironmentRuntime' Edition = $edition Version = $version Platform = $platform Preferred = $preferred Recommendation = $recommendation Capability = $capability Http = $http Parallel = 'RunspacePool' } Write-Verbose ("Running on PowerShell $version ($edition, $platform): failed HTTP bodies read by " + "$($http.ErrorBody), TLS $($http.Tls)") return $script:TestEnvironmentRuntime } |