Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the Count property work in Powershell?

Tags:

powershell

I've made a most unfortunate typo costing me quite some precious time:

$errors.Count

This returns "0", even if there are errors, because the variable name should be singular. This does work:

$error.clear()                # To ensure a correct repro
Copy-Item asdf fdsa           # Will show an error
$error.Count                  # Will output "1"

However, I now want to know why $errors.Count gave me anything at all, and why it gave me "0". So I went on to do some testing, and got the following results:

$asdf.Count                   # Will output "0"
$nrOfEinsteinsInSpace.Count   # Will output "0"
$a = 0; $a.Count;             # Will output "1"
$b = 2; $a.Count;             # Will output "1"
$x = 1,2,3; $x.Count;         # Will output "3"

And gathering even more data to be able to ask a sensible question here I did:

$true.Count                   # Will output "1"
$false.Count                  # Will output "1"

So we have the following different cases:

  1. Array(like) variables, where .Count will output the number of items.
  2. Non-existent variables, where .Count will output "0".
  3. Declared variables, where .Count will output "1".
  4. Built-in variables, where .Count will output "1".

Case 2, 3, and 4 don't make any sense to me (yet). What is going on here? Where is this documented? How does the .Count property work?

like image 678
Jeroen Avatar asked Oct 22 '14 09:10

Jeroen


People also ask

How do you use the count function in PowerShell?

Use the Count Method in PowerShell to Count Objects Then, add a period ( . ), followed by the count. For example, we wanted to know how many files were in a folder. The initial step to counting files in a folder is to use the Get-ChildItem cmdlet to return the files.

How do I count items in PowerShell?

You can use Measure-Object to count objects or count objects with a specified Property. You can also use Measure-Object to calculate the Minimum, Maximum, Sum, StandardDeviation and Average of numeric values. For String objects, you can also use Measure-Object to count the number of lines, words, and characters.

How do you use properties in PowerShell?

To get the properties of an object, use the Get-Member cmdlet. For example, to get the properties of a FileInfo object, use the Get-ChildItem cmdlet to get the FileInfo object that represents a file. Then, use a pipeline operator ( | ) to send the FileInfo object to Get-Member .


3 Answers

Starting in PowerShell V3, the properties Count and Length received very special treatment not related to extended type data (also known as ETS or extended type system).

If the instance has a Count/Length property, then everything continues to work like it did in V1/V2 - the value from the instance is returned.

If the instance does not have a Count/Length property, starting in V3, instead of that being an error, you'll get back 1. And if the instance is $null, you'll get back 0. If you have turned on strict mode, you'll get an error like in V2.

I'll admit this is a bit strange, but it solves a common problem when a cmdlet returns 0, 1, or multiple objects.

Often, you'll iterate through those results via the pipeline or with a foreach statement. For example:

dir nosuchfile* | % { $_ }
foreach ($item in dir nosuchfile*) { $_ }

In the foreach case above, V2 would actually enter the loop if the command didn't return any values. That was changed in V3 to better match peoples expectations, but that also meant that:

foreach ($item in $null) { $_ }

also never enters the loop.

The for statement is another way to iterate through results, e.g.

$results = dir nosuchfile*
for ($i = 0; $i -lt $results.Count; $i++) { $results[$i] }

In this example, it doesn't matter how many objects are in $result, the loop works just fine. Note that in V1/V2, you would have written:

$results = @(dir nosuchfile*)

This ensures $results is an array, but this syntax is ugly and many folks would forget it, hence the change to support Count/Length in a slightly more forgiving way.

like image 90
Jason Shirk Avatar answered Oct 07 '22 10:10

Jason Shirk


To complement Paul's answer, this might be related to extended type data. To quote the relevant part of the documentation:

Extended type data defines additional properties and methods ("members") of object types in Windows PowerShell. You can extend any type that is supported by Windows PowerShell and use the added properties and methods in the same way that you use the properties that are defined on the object types.

And:

There are three sources of extended type data in Windows PowerShell sessions. The Types.ps1xml files in the Windows PowerShell installation directory are loaded automatically into every Windows PowerShell session.

If you open that Types.ps1xml file (in $pshome), you'll see this at the beginning of the file:

   <Type>
        <Name>System.Array</Name>
        <Members>
            <AliasProperty>
                <Name>Count</Name>
                <ReferencedMemberName>Length</ReferencedMemberName>
            </AliasProperty>
        </Members>
    </Type>

So my guess is that by providing the ".Count" property, PowerShell assumes this is an array.

like image 22
David Brabant Avatar answered Oct 07 '22 09:10

David Brabant


Here is how i think it works:

Case 1: In Arrays the .Count Property actually links to the .Length property which shows the number of Items in the Array

Case 2: Non-exitent variables get automatically created by powershell and initialized with value $null

Case 3 / 4: On this one i am not exactly sure why it happens but since neither String nor Int or boolean Objects have a .Count property i could imagine that the Property is inherited by a parent-object.

The behaviour suggests that the variable is treated as array so with 1 Value assigned the output will be 1, without a value the result will be 0.

Edit: For the sake of completeness here is the Link to the Documentation: Technet, thanks @David Brabant

like image 3
Paul Avatar answered Oct 07 '22 08:10

Paul