Geek Noise
Rants, rambles, news and notes by Peter Provost
10

Fun with PowerShell and TFS Work Items

Friday, 10 April 2009 06:53 by Peter Provost

It all started with me wanting a better burndown chart for my team. That was relatively simple to solve as we’ve got some pretty good ones floating around inside Microsoft. (Apologies in advance but I can’t share my spreadsheet with you at this time.)

Then came the fun… I wanted to be able to pull any given day’s “work done” and “work remaining” data. But for my own reasons, I don’t want this spreadsheet bound to the OLAP cube, I want to be able to mess with it.

Being the PowerShell junkie that I am I was pretty sure I could do this. I found James Manning’s killer “get-tfs.ps1” script which meant I didn’t have to work out that bit myself.

Using it is simple:

   1: $tfs = get-tfs http://mytfs:8080
   2: $wi = $tfs.wit.GetWorkItem(12345)

As you can see, I get the TFS client object via James’s script, and then I pull a WorkItem from it. From there I can ask the $wi object for all the bits of information in the work item. Piping it to get-member showed me the following methods and properties:

   1:  
   2:    TypeName: Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem
   3:  
   4: Name                   MemberType            Definition                                                                
   5: ----                   ----------            ----------                                                                
   6: FieldChanged           Event                 Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemFieldChangeEve...
   7: Copy                   Method                Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem Copy(Microsof...
   8: Equals                 Method                bool Equals(System.Object obj)                                            
   9: GetHashCode            Method                int GetHashCode()                                                         
  10: GetNextState           Method                string GetNextState(string action)                                        
  11: GetType                Method                type GetType()                                                            
  12: IsValid                Method                bool IsValid()                                                            
  13: MergeToLatest          Method                System.Void MergeToLatest()                                               
  14: Open                   Method                System.Void Open()                                                        
  15: PartialOpen            Method                System.Void PartialOpen()                                                 
  16: Reset                  Method                System.Void Reset()                                                       
  17: Save                   Method                System.Void Save()                                                        
  18: SyncToLatest           Method                System.Void SyncToLatest()                                                
  19: ToString               Method                string ToString()                                                         
  20: Validate               Method                System.Collections.ArrayList Validate()                                   
  21: Item                   ParameterizedProperty System.Object Item(string name) {get;set;}, System.Object Item(Microsof...
  22: AreaId                 Property              System.Int32 AreaId {get;set;}                                            
  23: AreaPath               Property              System.String AreaPath {get;set;}                                         
  24: AttachedFileCount      Property              System.Int32 AttachedFileCount {get;}                                     
  25: Attachments            Property              Microsoft.TeamFoundation.WorkItemTracking.Client.AttachmentCollection A...
  26: ChangedBy              Property              System.String ChangedBy {get;}                                            
  27: ChangedDate            Property              System.DateTime ChangedDate {get;}                                        
  28: CreatedBy              Property              System.String CreatedBy {get;}                                            
  29: CreatedDate            Property              System.DateTime CreatedDate {get;}                                        
  30: Description            Property              System.String Description {get;set;}                                      
  31: DisplayForm            Property              System.String DisplayForm {get;}                                          
  32: ExternalLinkCount      Property              System.Int32 ExternalLinkCount {get;}                                     
  33: Fields                 Property              Microsoft.TeamFoundation.WorkItemTracking.Client.FieldCollection Fields...
  34: History                Property              System.String History {get;set;}                                          
  35: HyperLinkCount         Property              System.Int32 HyperLinkCount {get;}                                        
  36: Id                     Property              System.Int32 Id {get;}                                                    
  37: IsDirty                Property              System.Boolean IsDirty {get;}                                             
  38: IsNew                  Property              System.Boolean IsNew {get;}                                               
  39: IsOpen                 Property              System.Boolean IsOpen {get;}                                              
  40: IsPartialOpen          Property              System.Boolean IsPartialOpen {get;}                                       
  41: IsReadOnly             Property              System.Boolean IsReadOnly {get;}                                          
  42: IsReadOnlyOpen         Property              System.Boolean IsReadOnlyOpen {get;}                                      
  43: IterationId            Property              System.Int32 IterationId {get;set;}                                       
  44: IterationPath          Property              System.String IterationPath {get;set;}                                    
  45: Links                  Property              Microsoft.TeamFoundation.WorkItemTracking.Client.LinkCollection Links {...
  46: NodeName               Property              System.String NodeName {get;}                                             
  47: Project                Property              Microsoft.TeamFoundation.WorkItemTracking.Client.Project Project {get;}   
  48: Reason                 Property              System.String Reason {get;set;}                                           
  49: RelatedLinkCount       Property              System.Int32 RelatedLinkCount {get;}                                      
  50: Rev                    Property              System.Int32 Rev {get;}                                                   
  51: RevisedDate            Property              System.DateTime RevisedDate {get;}                                        
  52: Revision               Property              System.Int32 Revision {get;}                                              
  53: Revisions              Property              Microsoft.TeamFoundation.WorkItemTracking.Client.RevisionCollection Rev...
  54: State                  Property              System.String State {get;set;}                                            
  55: Store                  Property              Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore Store {g...
  56: TemporaryId            Property              System.Int32 TemporaryId {get;}                                           
  57: Title                  Property              System.String Title {get;set;}                                            
  58: Type                   Property              Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType Type {get;} 
  59: Uri                    Property              System.Uri Uri {get;}                                                     
  60: WorkItemLinkHistory    Property              Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemLinkCollection...
  61: WorkItemLinks          Property              Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemLinkCollection...
  62: GetCompletedWorkByDate ScriptMethod          System.Object GetCompletedWorkByDate();                                   
  63: GetRemainingWorkByDate ScriptMethod          System.Object GetRemainingWorkByDate();                                   

Lots of great stuff there, but what I really wanted was the work done on any given day and not the current work remaining. Hmmm…

After digging around in the object model (with PowerShell of course), I found the answer. The Revisions collection contains the full history of the Work Item. If I enumerated that, looking at the dates, I should be able to get what I want.

A little while later I had it all worked out, setup as a type extension in PowerShell so it automatically works with every WorkItem type I get. Type extensions are added via a special XML file (more info). Here’s what I added to mine:

   1: <Type>
   2:     <Name>Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem</Name>
   3:     <Members>
   4:         <ScriptMethod>
   5:             <Name>GetCompletedWorkByDate</Name>
   6:             <Script>
   1:  
   2:                 $date = [datetime] $args[0]
   3:                 $total = 0
   4:                 $this.Revisions | % {
   5:                     if ($_.Fields['Changed Date'].Value.Date -eq $date) {
   6:                         $work = $_.Fields["Completed Work"]
   7:                         if ($work -ne $null) { $total += ([int] $work.Value) - ([int] $work.OriginalValue) }
   8:                     }
   9:                 }
  10:                 return $total
  11:             
</Script>
   7:         </ScriptMethod>
   8:         <ScriptMethod>
   9:             <Name>GetRemainingWorkByDate</Name>
  10:             <Script>
   1:  
   2:                 $date = [datetime] $args[0]
   3:                 $total = 0
   4:                 $this.Revisions | % {
   5:                     if ($_.Fields['Changed Date'].Value.Date -le $date) {
   6:                         $work = $_.Fields["Remaining Work"]
   7:                         if ($work -ne $null) {
   8:                             $total = ([int] $work.Value)
   9:                         }
  10:                     }
  11:                 }
  12:                 return $total
  13:             
</Script>
  11:         </ScriptMethod>
  12:     </Members>
  13: </Type>

What this does is add two new methods to the .NET type Microsoft.TeamFoundation.WorkItemTracking.Client.Workitem. The first method takes a date and returns the sum of the “Completed Work” field for all revisions that occurred on that date. The second method enumerates the same Revisions collection, but this time it process all revisions up through the given date looking for the last one that says anything about the “Remaining Work” field.

Next step… create a script to wrap all this up into an easy to use command I call get-burndowndata.ps1:

   1: param( 
   2:         $deliverable = $(throw "Must provide deliverable ID"),
   3:         $date = [datetime]::Now.Date 
   4: )
   5:  
   6: $tfs = get-tfs http://mytfs:8080
   7: $deliverable = $tfs.WIT.GetWorkItem($deliverable) 
   8:  
   9: $result = @()
  10:  
  11: $deliverable.WorkItemLinks | ? { $_.LinkType.Name -eq "Child" } | % {
  12:     $feature = $tfs.WIT.GetWorkItem($_.TargetId)
  13:  
  14:     $feature.WorkItemLinks | ? { $_.LinkType.Name -eq "Child" } | % {
  15:         $task = $tfs.WIT.GetWorkItem($_.TargetId)
  16:         $completed = $task.GetCompletedWorkByDate($date)
  17:         $remaining = $task.GetRemainingWorkByDate($date)
  18:  
  19:         $obj = "" | select-object ID, Title, Completed, Remaining 
  20:         $obj.ID = $task.ID
  21:         $obj.Title = $task.Title
  22:         $obj.Completed = $completed
  23:         $obj.Remaining = $remaining
  24:  
  25:         $result += $obj
  26:     }
  27: }
  28:  
  29: $result

Now, getting any given day’s burndown information is as simple as calling that script and opening the result in Excel:

   1: get-burndowndata.ps1 12345 "4/1/2009" | export-csv data_20090401.csv
   2: invoke-item data_20090401.csv

Works like a charm!

Disclaimer: The get-burndowndata.ps1 script makes lots of assumptions about your TFS Work Item struction and relationships . It probably won’t work unmodified for anyone outside of DevDiv.

Currently rated 1.0 by 1 people

  • Currently 1/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
07

Software Development Teams and Sports

Thursday, 7 August 2008 07:52 by Peter Provost

283716_1341 For a long time I’ve made the analogy that the best software development teams are like basketball or hockey teams, and lately I’ve been thinking more about how you can recognize what kind of a team you have by continuing this analogy and looking for similarities and differences.

In sports there are a few different kinds of teams. There are teams like gymnastics or bowling where the final result of a match is a result of the combined individual efforts of the team members, but the efforts of the team members aren’t combinatorial. In other words, on an Olympic gymnastics team, the team medal is awarded to the team who’s combined individual scores are the highest. On a bowling team, each player’s score is added together and the team with the highest total wins.

Another kind of sports team is the kind you see in American Pro Football. Here we have one team that is subdivided into two or three parts. The offense is a sub-team that works together to score points. The defense is another sub-team that works together to prevent the opposition from scoring points. There are other sub-teams for special activities like punt returns, field goals, etc.

Basketball, hockey, soccer and rugby are examples of yet another kind of team. On these teams the whole team is working together for the entire game to accomplish their task. There may be specialists on the team like goalies, defenders and wings who have focus areas, but if the time came for an offensive player to block a goal or even a goalie to shoot and score, they would do it.

The best functioning agile teams that I’ve worked with have been like that last kind of team. We have specialists who are good at certain things, but when the time comes to do some work, anyone and everyone does what needs to be done. We can talk all day about elimination of roles on agile teams, but we know from experience that the best testers are people with a natural bent for it. There are also people who are instinctive toolsmiths and will take small taxes that affect the team and mitigate them by adding script or a tool that benefits the whole team. But when it is time to signup for a task, or pair with someone, or fix the build server, or write a new test harness, everyone on the team is prepared and empowered to do that work.

I have also seen agile teams that are more like American Football teams. The most prominent indicator of this is a separation of developers (offense) from testers (defense). This can work, and a large number of teams work this way, but as with football, sometimes the defense finds itself with the ball and if they may not really know what to do with it. Or even worse, they may not feel empowered to do something with it.

You will also find that there are development teams that are more like gymnastic teams. Indicators of this are things like “class ownership” or "module ownership”. When you find architects who must design all the details before a developer can “code it up” you are likely seeing a gymnastics team. Almost any time you find architectural, design or process siloing, you are probably seeing one of these teams. As with gymnastics, these teams can be successful, but it creates a different culture than the one I’m a fan of.

One of the key differences between these types of teams is the attitude about work and dependencies between people. On a hockey or basketball team, the team must work together to win. You can put an amazing group of individuals together, but if they don’t play well together, they won’t win. Done well, the work is combinatorial, where the effort of one person improves the people around them and thereby improves the team. If someone falls down on the front line in a hockey game, you will be unsurprised when the defenseman continues forward and attacks the goal.

Each subteam on a football team behaves this way, but they can’t cross the boundaries very well. The people on the field are interdependent, but they are still siloed into certain responsibilities and expectations. Some might say that this is a result of the way the game is played, with each side taking turns, but in some college and most high-school football teams, the players play “both ways”, meaning that the same people play on both offense and defense. On those teams, when the ball is intercepted, you are much more likely to see the person with the ball being someone who knows how to run and/or throw.

On teams like gymnastics teams, though, you see something different. The person doing floor exercise goes and puts up a score, then the person doing parallel bars goes next, etc. One person can’t really help the next person other than by making a strong enough score to boost the overall team.

The next time you’re thinking about your team, about roles, about siloing and ownership, take a look around and think about what kind of team it is. Think about what kind of team you want. And think about what the forces are that are driving your team to be one way or another. You might be surprised what kind of team you’re on.

(Photo by jimmybop)

Currently rated 3.0 by 2 people

  • Currently 3/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
10

Scrum of Scrums Haiku

Thursday, 10 July 2008 06:05 by Peter Provost

Agility (Binshou) Kanji My Scrum of Scrums has
Become something other than
What I want it to be

It should be simply
A coming together of
Technical people

Instead it becomes
Like a management review
I lieu of sharing

What did you do since?
What will you do until we?
Is something blocking you?

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
08

Taking on dependencies from non-agile teams

Tuesday, 8 July 2008 03:58 by Peter Provost

After a spirited discussion this week on one of our internal agile aliases, Eric Gunnerson wrote a post on his blog that wraps up his thinking (and mine) about how to deal with taking dependencies on a non-agile team when your team is running in an agile manner:

Eric Gunnerson's C# Compendium : Taking on dependencies

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
29

Tracking Actual Work Done vs Estimated Work Remaining

Thursday, 29 May 2008 02:51 by Peter Provost

I see this get discussed all the time on some of the agile aliases I'm on.

I care how much time is left, not how much time you spent on it.

I know people like to talk about “using the data to make our estimating better” but there are flaws in that argument that just can’t be ignored:

  1. Nobody ever does it. In fact, I don’t even know of a process to achieve this. Hollering at people who over/under estimate is not an improvement process.
  2. It assumes you can make developer estimates better. More experienced developers estimate better, that I’ll take as a given, but can you accelerate this with novice/junior developers or testers? I don’t think so.
  3. Software is NOT like mechanical engineering. It is a craft. Every activity you do is very likely the first time you’ve done it exactly that way. So our inability to accurately and precisely estimate shouldn’t be all that surprising.

At least that’s how I see it. :)

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5