InCycle Software's Application Modernization and DevOps Blog

PowerShell script to get list of modified files from a Bug / PBI in TFS across changesets

Written by Leo Vildosola | Jul 5, 2012 11:20:17 PM

Sometimes, when we need to quickly determine which files were changed across multiple changesets, we just want a list. We don’t want to have to open one work item check its changesets then go to another, etc. The following PowerShell script does just that.

Let me set the scenario. We use the Microsoft Visual Studio Scrum 1.0process template. In fact, now that we’re using TFS 2012 RC we upgraded our Team Project to using the Visual Studio Scrum 2.0process template. During our day-to-day we do work against Tasks that are associated to Bugs or Product Backlog Items. So, when we want to get a list of changed files, we want to simply point to the Bug or PBI. In the script below you just need to specify the $tfsUrl and $wiId attributes and execute the it.

[Reflection.Assembly]::Load(<span style="color: #006080">&quot;Microsoft.TeamFoundation.Client, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a&quot;</span>)
[Reflection.Assembly]::Load(<span style="color: #006080">&quot;Microsoft.TeamFoundation.VersionControl.Client, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a&quot;</span>)
[Reflection.Assembly]::Load(<span style="color: #006080">&quot;Microsoft.TeamFoundation.WorkItemTracking.Client, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a&quot;</span>)
&#160;
cls
&#160;
$tfsUrl = <span style="color: #006080">&quot;http://&lt;SERVER&gt;[PORT, e.g. :8080]/tfs/&lt;COLLECTION&gt;&quot;</span>
$wiId = <span style="color: #006080">&quot;9999&quot;</span>
&#160;
try {
    $tfs = New-Object Microsoft.TeamFoundation.Client.TfsTeamProjectCollection $tfsUrl
    $vcs = $tfs.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer]);
    $store = New-Object Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore $tfs
    $wi = $store.GetWorkItem($wiId)
    
    <span style="color: #0000ff">if</span> ($wi <span style="color: #cc6633">-ne</span> $null) {
        $changesets = <span style="color: #006080">&quot;&quot;</span>
&#160;
        $wi.Links | <span style="color: #0000ff">foreach</span> {
            $relLink = [Microsoft.TeamFoundation.WorkItemTracking.Client.RelatedLink] $null
            try { $relLink = [Microsoft.TeamFoundation.WorkItemTracking.Client.RelatedLink] $_; } catch {}
            <span style="color: #0000ff">if</span> ($relLink <span style="color: #cc6633">-ne</span> $null) {
                $wiChild = $store.GetWorkItem($relLink.RelatedWorkItemId)
                <span style="color: #0000ff">if</span> ($wiChild <span style="color: #cc6633">-ne</span> $null) {
                    $wiChild.Links | <span style="color: #0000ff">foreach</span> {
                        $extLink = [Microsoft.TeamFoundation.WorkItemTracking.Client.ExternalLink] $null
                        try { $extLink = [Microsoft.TeamFoundation.WorkItemTracking.Client.ExternalLink] $_; } catch {}
                        <span style="color: #0000ff">if</span> ($extLink <span style="color: #cc6633">-ne</span> $null) {
                            $artifact = $extLink.LinkedArtifactUri;
                            <span style="color: #0000ff">if</span> ([System.Text.RegularExpressions.Regex]::Match($artifact, <span style="color: #006080">'.*(/Changeset/).*'</span>).Success) {
                                $changesetId = $artifact.Substring($artifact.LastIndexOf(<span style="color: #006080">'/'</span>) + 1);
                                $separator = <span style="color: #006080">&quot;&quot;</span>
                                <span style="color: #0000ff">if</span> ($changesets.Length <span style="color: #cc6633">-gt</span> 0) {
                                    $separator = <span style="color: #006080">&quot;,&quot;</span>
                                }
                                $changesets = [string]::Concat($changesets, $separator, $changesetId)
                            }
                        }
                    }
                }
            }
        }
&#160;
        $fileItems = @();
&#160;
        $changesets.Split(<span style="color: #006080">','</span>) | <span style="color: #0000ff">foreach</span> {
            $vcs.GetChangeset($_).Changes | <span style="color: #0000ff">foreach</span> {
                <span style="color: #0000ff">if</span> ($fileItems -notcontains $_.Item.ServerItem) {
                    $fileItems += $_.Item.ServerItem;
                }
            }
        }
    
        $fileItems | Sort-Object;
    }
}
catch [System.Exception] {
    <span style="color: #006080">'Error: Unable to fetch changeset information!'</span>
    Write-Host $_.Exception.Message -ForegroundColor Red
}

The one drawback of the script is that it only works with 2-level, parent-child relationships. So, Bug-to-Task or PBI-to-Task. If you’re accustom to nesting these deeper then you will need to adapt the script accordingly.

We use the script internally to isolate changes when we want to either do a code review (a lot easier now with Visual Studio 2012) or when we want to isolate the files to package to send to a customer. Enjoy!