PowerShell ForEach / Piping confusion
Asked Answered
J

1

12

I am using the TFS PowerTools Cmdlets in PowerShell to try to get at some information about Changesets and related WorkItems from my server. I have boiled the problem down to behavior I don't understand and I am hoping it is not TFS specific (so someone out there might be able to explain the problem to me :) )

Here's the only command that I can get to work:

Get-TfsItemHistory C:\myDir -recurse -stopafter 5 | % { Write-Host $_.WorkItems[0]["Title"] }

It does what I expect - Get-TfsItemHistory returns a list of 5 ChangeSets, and it pipes those to a foreach that prints out the Title of the first associated WorkItem. So what's my problem? I am trying to write a large script, and I prefer to code things to look more like a C# program (powershell syntax makes me cry). Whenever I try to do the above written any other way, the WorkItems collection is null.

The following commands (which I interpret to be logically equivalent) do not work (The WorkItems collection is null):

$items = Get-TfsItemHistory C:\myDir -recurse -stopafter 5
$items | ForEach-Object { Write-Host $_.WorkItems[0]["Title"] }

The one I would really prefer:

$items = Get-TfsItemHistory C:\myDir -recurse -stopafter 5
foreach ($item in $items)
{
    $item.WorkItems[0]["Title"]
    # do lots of other stuff
}

I read an article about the difference between the 'foreach' operator and the ForEach-Object Cmdlet, but that seems to be more of a performance debate. This really appears to be an issue about when the piping is being used.

I'm not sure why all three of these approaches don't work. Any insight is appreciated.

Joanajoane answered 24/10, 2010 at 16:33 Comment(1)
I'm not sure what the problem is, but it's definitely specific to the TFS Cmdlets (they're rather horrible, imo). It appears that the cmdlet was doing lazy loading, and once the pipeline ends, the data context is gone and it's too late to load the data, but the design of the cmdlets is so convoluted that I couldn't track it that far in reflector.Manger
U
15

This is indeed confusing. For now a work-around is to grab the items like so:

$items = @(Get-TfsItemHistory . -r -Stopafter 25 | 
           Foreach {$_.WorkItems.Count > $null; $_})

This accesses the WorkItems collection which seems to cause this property to be populated (I know - WTF?). I tend to use @() to generate an array in cases where I want to use the foreach keyword. The thing with the foreach keyword is that it will iterate a scalar value including $null. So the if the query returns nothing, $items gets assigned $null and the foreach will iterate the loop once with $item set to null. Now PowerShell generally deals with nulls very nicely. However if you hand that value back to the .NET Framework, it usually isn't as forgiving. The @() will guarantee an array with with either 0, 1 or N elements in it. If it is 0 then the foreach loop will not execute its body at all.

BTW your last approach - foreach ($item in $items) { ... } - should work just fine.

Unclear answered 24/10, 2010 at 17:26 Comment(1)
Thanks, that works. I'm back on track and I'm not crazy! That is weird for sure, it'd be nice to know what that's all about but at this point I'm ready to chalk one more up to gremlins.Joanajoane

© 2022 - 2024 — McMap. All rights reserved.