PowerShell
PowerShell copied to clipboard
Set-Strictmode should not complain about COUNT & LENGTH properties on elements
One of the big problems we have had in the language has been not knowing whether the following expression was going to return a single element or an array:
$x = gps $name
We fixed this longstanding problem by masking over the issue allowing elements to be treated as arrays. e.g.
(gps -id 0).count (gps -id 0).length
When you enable strictmode - it complains about these and it shouldn't. I'm about to enable strictmode on a large collection of scripts and I'm sure that is going to find lots of these issue and modifying the code is not going to make the code any better - it will just cause a lot of work and reduce my reliability.
Bumping this because this is something I would also like to see
Just to provide some additional info:
-
The problem occurs with
Set-StrictMode -Version 2
or higher, where attempts to access non-existing properties result in a statement-terminating error (e.g.,The property 'Count' cannot be found on this object. Verify that the property exists.
).- Basically, PowerShell unexpectedly treats properties
.Count
and.Length
as non-existent, even though it normally adds them itself.
- Basically, PowerShell unexpectedly treats properties
-
Fortunately, the other feature related to unifying scalars and collections - indexing - works irrespective of what strict mode is in effect (e.g.,
(gps -id 0)[0]
).
Me too! I just added strict mode to my script to help me not call functions incorrectly and now I've lost the cool automatic Count property. Please fix this!
Any update on this? Just noticed, that it also applies for explicitly defined arrays, which is quite irritating:
Set-StrictMode -Version 7
[pscustomobject[]]$a = $null # e.g. returned by something like 1..5 | Where-Object {$false}
$a.Count
It is especially cumbersome, as checking for the count of elements cannot be done in the same manner as if it was 1 or 2 elements, so an if statement would become like this:
if (-not [bool](Get-Member -InputObject $a -Name "Count" -ErrorAction SilentlyContinue)) {
# Zero Elements
} elseif ($a.Count -eq 1) {
# One Element
} else {
# More than one element
}
It just throws the property not found exception, despite being an array and therefore having that attribute:
The property 'Count' cannot be found on this object. Verify that the property exists.
At line:1 char:1
+ $a.Count
+ ~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], PropertyNotFoundException
+ FullyQualifiedErrorId : PropertyNotFoundStrict
Edit: Also these forms work:
Set-StrictMode -Version 7
$myArray = @()
$myArray.Count
$myArray2 = New-Object int[] 0
$myArray2.Count
$list = New-Object Collections.Generic.List[Int]
$list.Count
But unintuitively this one doesn't
Set-StrictMode -Version 7
$a = 1,2,3,4 | Where-Object {$_ -eq 5}
$a.Count
# And even more confusingly:
function a {
[OutputType([Int64])]param($a)
return $a.Where{$_ -gt 5}
}
[Int64[]]$notWorking = 1..5 | a
$notWorking.Count
[pscustomobject[]]$butWorks = 1..5 | a
$butWorks.Count
For the last two it gets even stranger:
# And the really confusing part:
PS C:\> [pscustomobject[]]
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True PSObject[] System.Array
PS C:\> [Int64[]]
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int64[] System.Array
Therefore both variables should be of that type and behave as such and not the same as $null.
PowerShell lets you assign $null
or ([System.Management.Automation.Internal.AutomationNull]::Value
, which is what commands that produce no output technically return) to variables that are constrained to:
- a reference type, which includes arrays and collection types, in which case these values are stored as-is (e.g.,
[int[]] $a = $null
stores$null
in$a
) - a value type, if these values can be converted to that type (e.g,
[int] $i = $null
, which stores0
in$a
)
Operating on a reference-type-constrained variable set to $null
or [System.Management.Automation.Internal.AutomationNull]::Value
then operates on these values, which by definition have no properties - other than the engine-intrinsic .Count
(and its .Length
alias).
(Yes, actual array instances have a .Count
property via the ICollection
interface, but no instance exists in this case).
With strict mode off or at -Version 1
, $null.Count
and $null.Length
do work and return 0
, as expected.
With -Version 2
and above, we're again seeing the problem described in the OP:
PS> Set-StrictMode -Off; [int[]] $a = $null; $a.Count, $a.Length
0
0
PS> Set-StrictMode -Version 2; [int[]] $a = $null; $a.Count, $a.Length
PropertyNotFoundException: The property 'Count' cannot be found on this object. Verify that the property exists.
You can also do this now:
[int[]] $a = $null
$itemCount = ($a)?.Count ?? 0
Is that still experimental?
7.0.3 returns only this: You must provide a value expression following the '?' operator.
Edit: Yes it is and it's called PSNullConditionalOperators
Indeed, @agowa338; it's still an experimental feature in the current preview (7.1.0-preview.6.) as well.
To refocus on the original problem:
-
It is baffling that the PowerShell engine does not to recognize the properties it itself adds to all objects (that don't already have them) as available properties. True, these engine-added properties (termed intrinsic members) aren't bona fide properties of an object itself (in a .NET type sense), but what matters is that they are usable and have meaningful values.
-
Not doing so severely diminishes the utility of
Set-StrictMode -Version 2
and above; personally,Set-StrictMode -Version 1
is the highest version I ever use.
I just encountered this problem and came across this issue - +1. I'm using the following workaround for .Count
property for now:
$oCount = if ($o -is [array]) { ([array]$o).Count } else { 1 }
You can also do this now:
[int[]] $a = $null $itemCount = ($a)?.Count ?? 0
This will not help if it's not $null
$Var = 'Foo'
${Var}?.Count
Please fix, I was hoping to use StrictMode but with this it's not worth it
I just recently started using strictmode to validate my scripts and was getting some weird failures that turned out to be because of this problem. I (and many others) need .Count to tell us how many elements are in get-childitem, even when we have strictmode enable.
The best workaround I found so far is using Measure-Object
. That way, it'll also work with older versions of PowerShell (mainly the windows one, for cases where it is needed to use the native modules that are still incompatible with the newer versions...). That should work in all versions, even in Version 1.0
-
(Measure-Object -InputObject $a).Count
-
($a | Measure-Object).Count
Maybe that should even be the preferred solution, banning these dynamic count and length properties in strict mode entirely, as they're not sure to be there, or an object could have overwritten them with its custom Count property.
I came to that conclusion after having had to deal with countless issues because of the automatic unwrapping of arrays with only one element in PowerShell, which sometimes is handled very poorly, esp. by 3rd party modules... So if there is only one element, you get a string returned. But if there are more, you get an array of strings.
And then you may end up with stuff like (within the module, where you cannot quickly fix it by e.g., add a [pscustomobject[]]
):
$queue = Get-3rdPartyQueue
if ($queue.Length -gt 20) {
Do-Stuff
} else {
Wait-ForMoreStuff
}
And now, if only one element is returned by Get-3rdPartyQueue
PowerShell will unwrap it, and the .Length will not be called on an array but on the object itself (that should have been the array's element). And assuming that object has a .Length like, e.g., String does it'll cause logic errors...
Is this still open? Just hit that bug again in 7.3.7, but in a kinda "new and interesting way".
What works:
Set-StrictMode -Version latest
[string[]]$a = @()
$a.Count
What doesn't:
Set-StrictMode -Version latest
[string[]]$a = @() | Select-Object
$a.Count
Set-StrictMode -Version latest
[string[]]$a = @()
($a | Select-Object).Count
Something is very strange here, for some reason the Select-Object is changing the type...
I have closed the linked issue #16427 (Resolution-No Activity), assuming that this one stays open as it still exists in:
$PSVersionTable
Name Value
---- -----
PSVersion 7.3.9
PSEdition Core
GitCommitId 7.3.9
OS Microsoft Windows 10.0.19045
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Thanks @iRon7, yea the Resolution-No Activity
thingy of the bot is annoying. I just wasted valuable time c'n'p-ing a dummy message into a lot of tickets here. If I have to do that again I'll probably just create a throwaway github account and a bot that does it for me...