Skip to content

Commit e695984

Browse files
nohwndCopilot
andauthored
Remove code specifically for PS 2.0-5.0 (#2735)
Remove version guards that are always true on PS 5.1+ (-ge 3, -ge 4, -ge 5) and their dead else branches. This includes: - Coverage.ps1: remove -ge 4/-ge 5 guards around DSC, class AST, and base constructor checks; remove pre-PS5 DSC keyword detection path - Mock.ps1: remove -ge 3 guards around PositionalBinding, parameter validation, and alias repair; remove -ge 5 guard and pre-PS5 fallback in EscapeSingleQuotedStringContent; remove pre-5.0.10586 reflection path for dynamic parameters - Output.ps1: simplify HostSupportsOutput to always true (PS 5+ uses InformationRecords) - Pester.Types.ps1, TestDrive.ps1, Environment.ps1: tighten -ge 6 to -ge 7 (PS 6.x is EOL, Pester 6 supports PS 5.1 and PS 7+) Fixes #2510 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 76f6e99 commit e695984

6 files changed

Lines changed: 61 additions & 139 deletions

File tree

src/Pester.Types.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ if ($null -ne $configurationType) {
1212
}
1313
}
1414

15-
if ($PSVersionTable.PSVersion.Major -ge 6) {
15+
if ($PSVersionTable.PSVersion.Major -ge 7) {
1616
$path = "$PSScriptRoot/bin/net8.0/Pester.dll"
1717
# PESTER_BUILD
1818
if ((Get-Variable -Name "PESTER_BUILD" -ValueOnly -ErrorAction Ignore)) {

src/functions/Coverage.ps1

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,7 @@ function Test-CommandInScope {
307307
$classResult = !$Class
308308
$functionResult = !$Function
309309
for ($ast = $Command; $null -ne $ast; $ast = $ast.Parent) {
310-
if (!$classResult -and $PSVersionTable.PSVersion.Major -ge 5) {
311-
# Classes have been introduced in PowerShell 5.0
310+
if (!$classResult) {
312311
$classAst = $ast -as [System.Management.Automation.Language.TypeDefinitionAst]
313312
if ($null -ne $classAst -and $classAst.Name -like $Class) {
314313
$classResult = $true
@@ -412,20 +411,18 @@ function IsIgnoredCommand {
412411
return $true
413412
}
414413

415-
if ($PSVersionTable.PSVersion.Major -ge 4) {
416-
if ($Command.Extent.Text -eq 'Configuration') {
417-
# More DSC voodoo. Calls to "configuration" generate breakpoints, but their HitCount
418-
# stays zero (even though they are executed.) For now, ignore them, unless we can come
419-
# up with a better solution.
420-
return $true
421-
}
414+
if ($Command.Extent.Text -eq 'Configuration') {
415+
# More DSC voodoo. Calls to "configuration" generate breakpoints, but their HitCount
416+
# stays zero (even though they are executed.) For now, ignore them, unless we can come
417+
# up with a better solution.
418+
return $true
419+
}
422420

423-
if (IsChildOfHashtableDynamicKeyword -Command $Command) {
424-
# The lines inside DSC resource declarations don't trigger their breakpoints when executed,
425-
# just like the "configuration" keyword itself. I don't know why, at this point, but just like
426-
# configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk.
427-
return $true
428-
}
421+
if (IsChildOfHashtableDynamicKeyword -Command $Command) {
422+
# The lines inside DSC resource declarations don't trigger their breakpoints when executed,
423+
# just like the "configuration" keyword itself. I don't know why, at this point, but just like
424+
# configuration, we'll ignore it so it doesn't clutter up the coverage analysis with useless junk.
425+
return $true
429426
}
430427

431428
if ($Command.Extent.Text -match '^{?& \$wrappedCmd @PSBoundParameters ?}?$' -and
@@ -443,12 +440,10 @@ function IsIgnoredCommand {
443440
return $true
444441
}
445442

446-
if ($PSVersionTable.PSVersion.Major -ge 5) {
447-
if ($Command -is [System.Management.Automation.Language.CommandExpressionAst] -and
448-
$Command.Expression[0] -is [System.Management.Automation.Language.BaseCtorInvokeMemberExpressionAst]) {
449-
# Calls to inherited "base(...)" constructor does not trigger breakpoint or tracer hit, ignore.
450-
return $true
451-
}
443+
if ($Command -is [System.Management.Automation.Language.CommandExpressionAst] -and
444+
$Command.Expression[0] -is [System.Management.Automation.Language.BaseCtorInvokeMemberExpressionAst]) {
445+
# Calls to inherited "base(...)" constructor does not trigger breakpoint or tracer hit, ignore.
446+
return $true
452447
}
453448

454449
return $false
@@ -458,21 +453,9 @@ function IsChildOfHashtableDynamicKeyword {
458453
param ([System.Management.Automation.Language.Ast] $Command)
459454

460455
for ($ast = $Command.Parent; $null -ne $ast; $ast = $ast.Parent) {
461-
if ($PSVersionTable.PSVersion.Major -ge 5) {
462-
# The ast behaves differently for DSC resources with version 5+. There's a new DynamicKeywordStatementAst class,
463-
# and they no longer are represented by CommandAst objects.
464-
465-
if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and
466-
$ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst]) {
467-
return $true
468-
}
469-
}
470-
else {
471-
if ($ast -is [System.Management.Automation.Language.CommandAst] -and
472-
$null -ne $ast.DefiningKeyword -and
473-
$ast.DefiningKeyword.BodyMode -eq [System.Management.Automation.Language.DynamicKeywordBodyMode]::Hashtable) {
474-
return $true
475-
}
456+
if ($ast -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -and
457+
$ast.CommandElements[-1] -is [System.Management.Automation.Language.HashtableAst]) {
458+
return $true
476459
}
477460
}
478461

@@ -500,14 +483,10 @@ function IsClosingLoopCondition {
500483
function Get-ParentClassName {
501484
param ([System.Management.Automation.Language.Ast] $Ast)
502485

503-
if ($PSVersionTable.PSVersion.Major -ge 5) {
504-
# Classes have been introduced in PowerShell 5.0
505-
506-
$parent = $Ast.Parent
486+
$parent = $Ast.Parent
507487

508-
while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.TypeDefinitionAst]) {
509-
$parent = $parent.Parent
510-
}
488+
while ($null -ne $parent -and $parent -isnot [System.Management.Automation.Language.TypeDefinitionAst]) {
489+
$parent = $parent.Parent
511490
}
512491

513492
if ($null -eq $parent) {

src/functions/Environment.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
}
55

66
function GetPesterOs {
7-
# Prior to v6, PowerShell was solely on Windows. In v6, the $IsWindows variable was introduced.
8-
if ((GetPesterPsVersion) -lt 6) {
7+
# Prior to v7, PowerShell was solely on Windows. In v6, the $IsWindows variable was introduced.
8+
if ((GetPesterPsVersion) -lt 7) {
99
'Windows'
1010
}
1111
elseif (& $SafeCommands['Get-Variable'] -Name 'IsWindows' -ErrorAction 'Ignore' -ValueOnly ) {

src/functions/Mock.ps1

Lines changed: 34 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,7 @@ function New-MockBehavior {
4646
}
4747

4848
function EscapeSingleQuotedStringContent ($Content) {
49-
if ($global:PSVersionTable.PSVersion.Major -ge 5) {
50-
[System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($Content)
51-
}
52-
else {
53-
$Content -replace "['‘’‚‛]", '$&$&'
54-
}
49+
[System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($Content)
5550
}
5651

5752
function Create-MockHook ($contextInfo, $InvokeMockCallback) {
@@ -83,7 +78,7 @@ function Create-MockHook ($contextInfo, $InvokeMockCallback) {
8378
}
8479
}
8580
$cmdletBinding = [Management.Automation.ProxyCommand]::GetCmdletBindingAttribute($metadata)
86-
if ($global:PSVersionTable.PSVersion.Major -ge 3 -and $contextInfo.Command.CommandType -eq 'Cmdlet') {
81+
if ($contextInfo.Command.CommandType -eq 'Cmdlet') {
8782
if ($cmdletBinding -ne '[CmdletBinding()]') {
8883
$cmdletBinding = $cmdletBinding.Insert($cmdletBinding.Length - 2, ',')
8984
}
@@ -1499,8 +1494,7 @@ function Get-DynamicParametersForCmdlet {
14991494
[string] $CmdletName,
15001495

15011496
[ValidateScript( {
1502-
if ($PSVersionTable.PSVersion.Major -ge 3 -and
1503-
$null -ne $_ -and
1497+
if ($null -ne $_ -and
15041498
$_.GetType().FullName -ne 'System.Management.Automation.PSBoundParametersDictionary') {
15051499
throw 'The -Parameters argument must be a PSBoundParametersDictionary object ($PSBoundParameters).'
15061500
}
@@ -1525,77 +1519,29 @@ function Get-DynamicParametersForCmdlet {
15251519
return
15261520
}
15271521

1528-
if ('5.0.10586.122' -lt $PSVersionTable.PSVersion) {
1529-
# Older version of PS required Reflection to do this. It has run into problems on occasion with certain cmdlets,
1530-
# such as ActiveDirectory and AzureRM, so we'll take advantage of the newer PSv5 engine features if at all possible.
1531-
1532-
if ($null -eq $Parameters) {
1533-
$paramsArg = @()
1534-
}
1535-
else {
1536-
$paramsArg = @($Parameters)
1537-
}
1538-
1539-
$command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg)
1540-
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
1541-
1542-
foreach ($param in $command.Parameters.Values) {
1543-
if (-not $param.IsDynamic) {
1544-
continue
1545-
}
1546-
if ($Parameters.ContainsKey($param.Name)) {
1547-
continue
1548-
}
1549-
1550-
$dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
1551-
$paramDictionary.Add($param.Name, $dynParam)
1552-
}
1553-
1554-
return $paramDictionary
1522+
if ($null -eq $Parameters) {
1523+
$paramsArg = @()
15551524
}
15561525
else {
1557-
if ($null -eq $Parameters) {
1558-
$Parameters = @{ }
1559-
}
1560-
1561-
$cmdlet = ($command.ImplementingType)::new()
1562-
1563-
$flags = [System.Reflection.BindingFlags]'Instance, Nonpublic'
1564-
$context = $ExecutionContext.GetType().GetField('_context', $flags).GetValue($ExecutionContext)
1565-
[System.Management.Automation.Cmdlet].GetProperty('Context', $flags).SetValue($cmdlet, $context, $null)
1566-
1567-
foreach ($keyValuePair in $Parameters.GetEnumerator()) {
1568-
$property = $cmdlet.GetType().GetProperty($keyValuePair.Key)
1569-
if ($null -eq $property -or -not $property.CanWrite) {
1570-
continue
1571-
}
1572-
1573-
$isParameter = [bool]($property.GetCustomAttributes([System.Management.Automation.ParameterAttribute], $true))
1574-
if (-not $isParameter) {
1575-
continue
1576-
}
1577-
1578-
$property.SetValue($cmdlet, $keyValuePair.Value, $null)
1579-
}
1580-
1581-
try {
1582-
# This unary comma is important in some cases. On Windows 7 systems, the ActiveDirectory module cmdlets
1583-
# return objects from this method which implement IEnumerable for some reason, and even cause PowerShell
1584-
# to throw an exception when it tries to cast the object to that interface.
1585-
1586-
# We avoid that problem by wrapping the result of GetDynamicParameters() in a one-element array with the
1587-
# unary comma. PowerShell enumerates that array instead of trying to enumerate the goofy object, and
1588-
# everyone's happy.
1526+
$paramsArg = @($Parameters)
1527+
}
15891528

1590-
# Love the comma. Don't delete it. We don't have a test for this yet, unless we can get the AD module
1591-
# on a Server 2008 R2 build server, or until we write some C# code to reproduce its goofy behavior.
1529+
$command = $ExecutionContext.InvokeCommand.GetCommand($CmdletName, [System.Management.Automation.CommandTypes]::Cmdlet, $paramsArg)
1530+
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
15921531

1593-
, $cmdlet.GetDynamicParameters()
1532+
foreach ($param in $command.Parameters.Values) {
1533+
if (-not $param.IsDynamic) {
1534+
continue
15941535
}
1595-
catch [System.NotImplementedException] {
1596-
# Some cmdlets implement IDynamicParameters but then throw a NotImplementedException. I have no idea why. Ignore them.
1536+
if ($Parameters.ContainsKey($param.Name)) {
1537+
continue
15971538
}
1539+
1540+
$dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
1541+
$paramDictionary.Add($param.Name, $dynParam)
15981542
}
1543+
1544+
return $paramDictionary
15991545
}
16001546

16011547
function Get-DynamicParametersForMockedFunction {
@@ -1852,31 +1798,29 @@ function New-BlockWithoutParameterAliases {
18521798
$Block
18531799
)
18541800
try {
1855-
if ($PSVersionTable.PSVersion.Major -ge 3) {
1856-
$params = $Metadata.Parameters.Values
1857-
$ast = Get-ScriptBlockAST $Block
1858-
$blockText = $ast.Extent.Text
1859-
$variables = [array]($Ast.FindAll( { param($ast) $ast -is [System.Management.Automation.Language.VariableExpressionAst] }, $true))
1860-
[array]::Reverse($variables)
1801+
$params = $Metadata.Parameters.Values
1802+
$ast = Get-ScriptBlockAST $Block
1803+
$blockText = $ast.Extent.Text
1804+
$variables = [array]($Ast.FindAll( { param($ast) $ast -is [System.Management.Automation.Language.VariableExpressionAst] }, $true))
1805+
[array]::Reverse($variables)
18611806

1862-
foreach ($var in $variables) {
1863-
$varName = $var.VariablePath.UserPath
1864-
$length = $varName.Length
1807+
foreach ($var in $variables) {
1808+
$varName = $var.VariablePath.UserPath
1809+
$length = $varName.Length
18651810

1866-
foreach ($param in $params) {
1867-
if ($param.Aliases -contains $varName) {
1868-
$startIndex = $var.Extent.StartOffset - $ast.Extent.StartOffset + 1 # move one position after the dollar sign
1811+
foreach ($param in $params) {
1812+
if ($param.Aliases -contains $varName) {
1813+
$startIndex = $var.Extent.StartOffset - $ast.Extent.StartOffset + 1 # move one position after the dollar sign
18691814

1870-
$blockText = $blockText.Remove($startIndex, $length).Insert($startIndex, $param.Name)
1815+
$blockText = $blockText.Remove($startIndex, $length).Insert($startIndex, $param.Name)
18711816

1872-
break # It is safe to stop checking for further params here, since aliases cannot be shared by parameters
1873-
}
1817+
break # It is safe to stop checking for further params here, since aliases cannot be shared by parameters
18741818
}
18751819
}
1876-
1877-
$Block = [scriptblock]::Create($blockText)
18781820
}
18791821

1822+
$Block = [scriptblock]::Create($blockText)
1823+
18801824
$Block
18811825
}
18821826
catch {

src/functions/Output.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function Write-PesterHostMessage {
7474

7575
begin {
7676
# Custom PSHosts without UI will fail with Write-Host. Works in PS5+ due to use of InformationRecords
77-
$HostSupportsOutput = $null -ne $host.UI.RawUI.ForegroundColor -or $PSVersionTable.PSVersion.Major -ge 5
77+
$HostSupportsOutput = $true # Pester 6 requires PS 5.1+ which always supports InformationRecords
7878
if (-not $HostSupportsOutput) { return }
7979

8080
# Source https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#text-formatting

src/functions/TestDrive.ps1

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,10 @@ function Remove-TestDriveSymbolicLinks ([String] $Path) {
170170

171171
# issue 621 was fixed before PowerShell 6.1
172172
# now there is an issue with calling the Delete method in recent (6.1) builds of PowerShell
173-
if ((GetPesterPSVersion) -ge 6) {
173+
if ((GetPesterPSVersion) -ge 7) {
174174
return
175175
}
176176

177-
# powershell 2-compatible
178177
$reparsePoint = [System.IO.FileAttributes]::ReparsePoint
179178
& $SafeCommands['Get-ChildItem'] -Recurse -Path $Path |
180179
& $SafeCommands['Where-Object'] { ($_.Attributes -band $reparsePoint) -eq $reparsePoint } |

0 commit comments

Comments
 (0)