Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell ForEach / Piping confusion

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.

like image 323
hexate Avatar asked Oct 24 '10 16:10

hexate


1 Answers

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.

like image 174
Keith Hill Avatar answered Oct 03 '22 03:10

Keith Hill