Once I was working with another developer who indicated an approximate number of versions of documents to a Business Analyst (BA). This number was one that he had heard a previous manager mention that he had assumed correct. The BA then asked him how he had gotten that number and - to make a long story short - I ended up being tasked with looking into the number of versions of items across an entire web application.
In SharePoint, picking the right approach to begin with can be the most important step. So I chose PowerShell to generate a comma-separated values (CSV) file which I would then format with Excel, etc. This would be my deliverable. And PowerShell was fast and easy! I already had years of experience with PowerShell.
But at that time I was using C# most of all and did not understand PowerShell's zero, one or many behavior. So, when I used something like Where-Object I would think in LINQ and translate. And this is where I got into trouble.
So, I created a recursive function in the script that accepted an SPWebCollection ($webs) and iterated. Each SPWeb object had a nested iteration on its lists. Then, in order to get the count of versions, since only major versions were created, I used Where-Object to check the version number and count the number of items:
$webs | ForEach-Object {
$_.Lists | ForEach-Object{
for ($i = 1; $i -lt 90 ; $i++) {
$verNum = "" + $i + ".0"
$items = $_.Items | Where-Object {
$_.Version -eq $verNum
}
$itemsPerVersion = $items.Count
if ($itemsPerVersion -gt 0){
. . .
}
}
}
}
I thought this was working fine and I added a lot more output besides $itemsPerVersion and generated my data, formatted the report and checked it. PowerShell is easy!
Doh!
When I checked my report I had maximum version numbers that were far lower than expected. I spent hours rewriting the function, using an SPQuery object, trying internal field names, etc. No matter what I tried I wasn't getting all the data.
Then the light bulb appeared over my head.
I was using PowerShell, not C#, not LINQ. PowerShell’s pipeline output follows zero-one-many behavior: zero matching objects assign $null, one assigns the object itself, and multiple objects assign an array. So:
$items = $_.Items | Where-Object {
$_.Version -eq $verNum
}
$items contained one SPListItem, not a one-element collection. In the pre-PowerShell 3.0 environment I was using, the scalar SPListItem did not expose the collection .Count property my code expected. Consequently, $items.Count evaluated to $null, $itemsPerVersion -gt 0 was false, and that item was omitted from the report. So:
$itemsPerVersion = $items.Count
was setting $itemsPerVersion to Snull when there was only one list item with that version. In the comparison, that effectively meant zero—uncounted.
Then a little bit of the Oracle at Delphi (aka Google) and bam! - a three-character fix was all I needed. I could guarantee an array containing zero, one, or many results.
By wrapping the evaluated Where-Object part in @(. . .) I was using the array-subexpression operator to ensure that the result was an array.
$items = @(
$_.Items | Where-Object {
$_.Version -eq $verNum
}
)
PowerShell is not C#. The strict type and collection assumptions I brought from C# did not fit PowerShell. Particularly when it comes to collection semantics (like scalar,array and $null behavior).
Lesson Learned. Remember what your 8th grade English teacher said about "ASS-U-ME"? Paradigm flexibility restored.
Epilogue:
Beginning with PowerShell 3.0, this particular failure became less likely: most scalar objects expose .Count as 1, while $null.Count evaluates to 0. The underlying zero-one-many output behavior still exists. Using @(...) remains a simple way to guarantee consistent array semantics when downstream code depends on collection properties, indexing, or a stable result shape.