in Uncategorized

Getting a distinct list of changed files from TFS using PowerShell

If you’re like me and need to do code-reviews of other people’s stuff or maybe you just want to see everything that’s changed during a certain period of a project, then here’s a nice PowerShell tip for you.

First, make sure you’ve downloaded the latest version of the Team Foundation Powertools. Starting with the October ‘08 release, the tools now include a PowerShell snap-in that add several commands which enable rich interaction with your Team Foundation Server. Of particular interest to us for this exercise though is the Get-TfsItemHistory command.

Now, let’s assume a scenario where we have a Team Project named $/Foo and we’ve been working on some patches in a v1.1 branch within that project. Now it’s time to review the work we’ve done in the current iteration which started on March 1st ‘09 and ended on March 31st ‘09. Here’s how we might start gathering those changes:

Get-TfsItemHistory "$/Foo/v1.1" -Version "D3/1/09~D3/31/09" -Recurse

Now, what this is gonna do is bring us back all the changesets that are related to any file underneath the v1.1 branch. While listing out the changesets is nice, it’s not going to tell exactly which source files I need to review. So the next thing we need to do is make sure we bring back all the changes in the changesets by adding the -IncludeItems parameter to the Get-TfsItemHistory call. We’ll also want to do is flatten out the list because, again, we’re interested in the indvidual changes, not the changesets themselves. So we use Select-Object’s -Expand parameter to flatten out the list:

Get-TfsItemHistory "$/Foo/v1.1" -Version "D3/1/09~D3/31/09" -Recurse -IncludeItems | Select-Object -Expand Changes

Great, so now we have a list of all the changes that were made to each file in this release, but this is still a little noisy. For starters, change types such as deletes, branches and merges are shown here. Well, if the file was deleted there’s not much to look at now, so… I don’t want those in my list. Also if a file was simply branched into the project from someplace else we don’t really care because wherever it came from already underwent a review. Merges are also questionable as, hopefully, the merged was reviewed for any conflicts at the time the merge occurred. Plus if there was a conflict that means they must have changed the file which will show up as a standalone edit anyway which means it will still end up on our list. So, how do we filter that noise out? Like so:

Get-TfsItemHistory "$/Foo/v1.1" -Version "D3/1/09~D3/31/09" -Recurse -IncludeItems | Select-Object -Expand Changes | Where-Object { ($_.ChangeType -band ([Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Delete -bor [Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Merge -bor [Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Branch)) -eq 0 }

Alright, almost there! Now the only problem is that if the same file is changed multiple times it’s going to be listed multilple times and really we just want the distinct names. This is a little tricky because the Change object contains the Item as a Note property, luckily there’s a Select-TfsItem command to help read what we’re interested in out. After that we just do a little grouping and sorting and we have a list we can work with:

Get-TfsItemHistory "$/Foo/v1.1" -Version "D3/1/09~D3/31/09" -Recurse -IncludeItems | Select-Object -Expand Changes | Where-Object { ($_.ChangeType -band ([Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Delete -bor [Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Merge -bor [Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Branch)) -eq 0 } | Select-TfsItem | Group-Object Path | Select-Object Name | Sort-Object

Finally, in true PowerShell fashion, this will write the paths out to the host (console or ISE), but naturally you could also Export-*, Out-*, etc as well.

Leave a comment


    • tfpt : Unrecognized command: history.
      At line:1 char:1
      + tfpt history /format:unified
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : NotSpecified: (Unrecognized command: history.:String) [], RemoteException
      + FullyQualifiedErrorId : NativeCommandError

        • Hi,

          Not sure why all the TFS related PS commands are no longer working for the latest TFS versions like TFS2017 or TFS2019?
          Example: By running the below command:

          Get-TfsChangeSet . -r | Where {$_.CheckinDate -gt (Get-Date).AddDays(-30)} |
          Select TargetServerItem > recentlyChangedFiles.txt

          is returning with the following error:

          “Get-TfsChangeSet : The term ‘Get-TfsChangeSet’ is not recognized as the name…….”

  1. Do you happen to know if this only works with the 2008 versions? It seems that with Visual Studio 2005 only the 2005 Power Tools work and this option isn’t available.Thanks, Toby

  2. When I run this Cmdlet, it says, I’m not authorized to access TFS server. Couldn’t find any parameter to authenticate myself. Moreover, with -Prompt, it gives me the login window and after providing the password, it shows the changeset in another Window but that is not the intention. I want the list in text/xml. any ideas?

  3. Thanks for this – very useful post as it would have taken me ages to figure out.

    However I had one small problem Sort-Object failed to sort the items alphanumerically, instead they were coming out in some random order.

    I think the problem is that the cmdlet is outputting objects of type ‘PsCustomObject’, and sort-object doesn’t sort these by name..??

    I added this in the pipeline before the sort, and it seems to work now:

    | % { $_.Name }

    So the whole thing looks like:

    Get-TfsItemHistory “$/Foo/v1.1” -Version “D3/1/09~D3/31/09” -Recurse -IncludeItems | Select-Object -Expand “Changes” | Where-Object { ($_.ChangeType -band ([Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Delete -bor [Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Merge -bor [Microsoft.TeamFoundation.VersionControl.Client.ChangeType]::Branch)) -eq 0 } | Select-TfsItem | Group-Object Path | Select-Object Name | % { $_.Name } | Sort-Object

    I’m a powershell newbie so there is probably a much better way, but it worked for me, so maybe it’ll help someone else…