tech·nic·al·ly agile

Retrieving an identity from Team Foundation Server using only the display name

Learn how to retrieve identities from Team Foundation Server using display names. Simplify your TFS management with practical coding insights and tools.

Published on
8 minute read
Image
https://nkdagility.com/resources/oz3cfvmwtph

This is a lot harder than it sounds. At first you think there will be a built in option with the Read Identities  method on the IGroupSecurityService  Interface, but you would be wrong!

When capturing an event from Team Foundation Server you have access to a lot of information about the change, including the Display Name of the fields for Assigned To and Changed By.

But what if you allow Work Items to be assigned to groups! First, lets achieve that. Create a group called “Program Management” on a project and add it into the “Contributors” list. We have a group for each of the advocacy groups in the CMMI process.

Edit your “Task” work item definition (you can use the power tools process editor or just edit the XML) and alter the Assigned To field to be the following:

1   1: <FIELD reportable="dimension" type="String" name="Assigned To" rename="System.AssignedTo">
1   2:   <ALLOWEXISTINGVALUE />
1   3:   <ALLOWEDVALUES>
1   4:     <LISTITEM value="[project]Contributors" />
1   5:   </ALLOWEDVALUES>
1   6: </FIELD>

Once you have updated your project you should be able to see all of the users as well as this new group displayed. If you were to assign a task to this group, how would you email everyone in that group so that they know they have been assigned something at all?

Well this needs a wee tweak of the TFS Event Handler    to handle this, I will be releasing the full in place code with the TFS Event Handler v1.3 drop, but you can download my little test app I used to get it all working.

[ Download Project  ]

You can enter a display name of either a user or a group. And here is how it is done:

There is a little but of Active Directory lookup using a little method called GetUsername

1   1: '' <summary>
1   2: '' Retrieves a user's email address from Active Directory based on their display name
1   3: '' </summary>
1   4: Public Shared Function GetUsername(ByVal userDisplayName As String) As String
1   5:     Dim ds As DirectoryServices.DirectorySearcher = New DirectoryServices.DirectorySearcher()
1   6:     ds.PropertiesToLoad.Add("sAMAccountName")
1   7:     ds.Filter = String.Format("(&(displayName={0})(objectCategory=person)((objectClass=user)))", userDisplayName)
1   8: 
1   9:     Dim results As DirectoryServices.SearchResultCollection = ds.FindAll()
1  10:     If results.Count = 0 Then
1  11:         Return String.Empty
1  12:     End If
1  13:     Dim values As DirectoryServices.ResultPropertyValueCollection = results(0).Properties("sAMAccountName")
1  14:     If values.Count = 0 Then
1  15:         Return String.Empty
1  16:     End If
1  17:     Return values(0).ToString()
1  18: End Function

This retrieves the users sAMAccountName (or username) from Active Directory. Easy enough, and I already had it kicking about…

But in order to retrieve an identity that you are not sure is a group or a user, you will need to try to get the Group Identity first. This is because it is faster to wade through a maximum of 20 groups than potentially hundreds of users mentioned on the MSDN Forum  answer below.

1   1: Try
1   2:            '----------------------------------------
1   3:            Dim svr As New TeamFoundationServer(Me.uxTeamServer.Text)
1   4:            Dim GroupSecurityService As IGroupSecurityService = CType(svr.GetService(GetType(IGroupSecurityService)), IGroupSecurityService)
1   5:            '----------------------------------------
1   6:            Dim CommonStructureService As ICommonStructureService = CType(svr.GetService(GetType(ICommonStructureService)), ICommonStructureService)
1   7:            '----------------------------------------
1   8:            ' Return App Group if you can
1   9:            Dim pi As ProjectInfo = m_CommonStructureService.GetProjectFromName(Me.uxProject.Text)
1  10:            Dim appGroup As Identity = (From i In m_GroupSecurityService.ListApplicationGroups(pi.Uri) Where i.DisplayName = Me.uxDisplayName.Text).SingleOrDefault
1  11:            If Not appGroup Is Nothing Then
1  12:                appGroup = m_GroupSecurityService.ReadIdentity(SearchFactor.Sid, appGroup.Sid, QueryMembership.Expanded)
1  13:                WriteToLog(String.Format("Recieved identity for {0}", Me.uxDisplayName.Text))
1  14:                WriteIdentity(appGroup)
1  15:                Exit Try
1  16:            End If
1  17:            ' Not app group. Then return user is you can
1  18:            Dim username As String = GetUsername(Me.uxDisplayName.Text)
1  19:            Dim usrIdent As Identity = m_GroupSecurityService.ReadIdentity(SearchFactor.AccountName, username, QueryMembership.Expanded)
1  20:            If Not usrIdent Is Nothing Then
1  21:                WriteToLog(String.Format("Recieved identity for {0}", username))
1  22:                WriteIdentity(usrIdent)
1  23:                Exit Try
1  24:            End If
1  25:            '----------------------------------------
1  26:            WriteToLog(String.Format("identity for {0} not found", Me.uxDisplayName.Text))
1  27: 
1  28:            '----------------------------------------
1  29:        Catch ex As Exception
1  30:            Me.uxResults.Items.Add(ex.ToString)
1  31:        End Try

There is a lot going on here, but the first thing you need to do is retrieve the TFS objects that we will need to work with which include a TeamFoundationServer  instance as well as an IGroupSecurityService  and ICommonStructureService  . You could use the WorkItemStore  instead of the ICommonStructureService  , but the WorkItemStore  has a heavy performance hit for retrieving an instance.

1   1: Dim svr As New TeamFoundationServer(Me.uxTeamServer.Text)
1   2: Dim GroupSecurityService As IGroupSecurityService = CType(svr.GetService(GetType(IGroupSecurityService)), IGroupSecurityService)
1   3: Dim CommonStructureService As ICommonStructureService = CType(svr.GetService(GetType(ICommonStructureService)), ICommonStructureService)

Next we need to try and retrieve the Identity of the group, if it is one.  The ICommonStructureService  has a method for listing all of the Groups available within a project, but for that you need the project name which in the demo is just entered.

1   1: ' Return App Group if you can
1   2: Dim pi As ProjectInfo = m_CommonStructureService.GetProjectFromName(Me.uxProject.Text)
1   3: Dim appGroup As Identity = (From i In m_GroupSecurityService.ListApplicationGroups(pi.Uri) Where i.DisplayName = Me.uxDisplayName.Text).SingleOrDefault
1   4: If Not appGroup Is Nothing Then
1   5:   appGroup = m_GroupSecurityService.ReadIdentity(SearchFactor.Sid, appGroup.Sid, QueryMembership.Expanded)
1   6:   WriteToLog(String.Format("Recieved identity for {0}", Me.uxDisplayName.Text))
1   7:   WriteIdentity(appGroup)
1   8:   Exit Try
1   9: End If

What this does is use the project name entered (in the event it is under the element ProtfolioProject (yea, I don’t know why it is called that either) to search a list of Group’s within your project for the one of interest.

Then, as it does not by default load the “Members” and “MemberOf” arrays you need to call ReadIdentity with the Expand option is you want to list the Members, and I do.

Retrieving an identity from Team Foundation Server using only the display name

If this does not return an identity, then we need to look at the display name being a user account.

1   1: ' Not app group. Then return user is you can
1   2: Dim username As String = GetUsername(Me.uxDisplayName.Text)
1   3: Dim usrIdent As Identity = m_GroupSecurityService.ReadIdentity(SearchFactor.AccountName, username, QueryMembership.Expanded)
1   4: If Not usrIdent Is Nothing Then
1   5:   WriteToLog(String.Format("Recieved identity for {0}", username))
1   6:   WriteIdentity(usrIdent)
1   7:   Exit Try
1   8: End If

Actually quite easy, but it could be easier.

Example WorkItemChangedEvent:

1   1: <?xml version="1.0" encoding="utf-8"?>
1   2: <WorkItemChangedEvent xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
1   3:   <PortfolioProject>TFS Sticky Buddy</PortfolioProject>
1   4:   <ProjectNodeId>614c944e-7799-46a2-a519-30e68eea040b</ProjectNodeId>
1   5:   <AreaPath>TFS Sticky Buddy</AreaPath>
1   6:   <Title>TFS Sticky Buddy Work Item Changed: Requirement 1267 - Visual Enhancement</Title>
1   7:   <WorkItemTitle>Mobility Continental Exceptions Report</WorkItemTitle>
1   8:   <Subscriber>HINSHDOMsvc_tfsservices</Subscriber>
1   9:   <ChangerSid>S-1-5-21-1390067357-651377827-682003330-21716</ChangerSid>
1  10:   <DisplayUrl>http://tfs01.hinshelwood.com:8080/workitemtracking/workitem.aspx?artifactmoniker=1267</DisplayUrl>
1  11:   <TimeZone>GMT Standard Time</TimeZone>
1  12:   <TimeZoneOffset>00:00:00</TimeZoneOffset>
1  13:   <ChangeType>Change</ChangeType>
1  14:   <CoreFields>
1  15:     <IntegerFields>
1  16:       <Field>
1  17:         <Name>ID</Name>
1  18:         <ReferenceName>System.Id</ReferenceName>
1  19:         <OldValue>1267</OldValue>
1  20:         <NewValue>1267</NewValue>
1  21:       </Field>
1  22:       <Field>
1  23:         <Name>Rev</Name>
1  24:         <ReferenceName>System.Rev</ReferenceName>
1  25:         <OldValue>4</OldValue>
1  26:         <NewValue>5</NewValue>
1  27:       </Field>
1  28:       <Field>
1  29:         <Name>AreaID</Name>
1  30:         <ReferenceName>System.AreaId</ReferenceName>
1  31:         <OldValue>551</OldValue>
1  32:         <NewValue>551</NewValue>
1  33:       </Field>
1  34:     </IntegerFields>
1  35:     <StringFields>
1  36:       <Field>
1  37:         <Name>Work Item Type</Name>
1  38:         <ReferenceName>System.WorkItemType</ReferenceName>
1  39:         <OldValue>Requirement</OldValue>
1  40:         <NewValue>Requirement</NewValue>
1  41:       </Field>
1  42:       <Field>
1  43:         <Name>Title</Name>
1  44:         <ReferenceName>System.Title</ReferenceName>
1  45:         <OldValue>Visual Enhancement</OldValue>
1  46:         <NewValue>Visual Enhancement</NewValue>
1  47:       </Field>
1  48:       <Field>
1  49:         <Name>Area Path</Name>
1  50:         <ReferenceName>System.AreaPath</ReferenceName>
1  51:         <OldValue>TFS Sticky Buddy</OldValue>
1  52:         <NewValue>TFS Sticky Buddy</NewValue>
1  53:       </Field>
1  54:       <Field>
1  55:         <Name>State</Name>
1  56:         <ReferenceName>System.State</ReferenceName>
1  57:         <OldValue>Active</OldValue>
1  58:         <NewValue>Active</NewValue>
1  59:       </Field>
1  60:       <Field>
1  61:         <Name>Reason</Name>
1  62:         <ReferenceName>System.Reason</ReferenceName>
1  63:         <OldValue>Accepted</OldValue>
1  64:         <NewValue>Accepted</NewValue>
1  65:       </Field>
1  66:       <Field>
1  67:         <Name>Assigned To</Name>
1  68:         <ReferenceName>System.AssignedTo</ReferenceName>
1  69:         <OldValue>Mike Hunt</OldValue>
1  70:         <NewValue>Mike Hunt</NewValue>
1  71:       </Field>
1  72:       <Field>
1  73:         <Name>Changed By</Name>
1  74:         <ReferenceName>System.ChangedBy</ReferenceName>
1  75:         <OldValue>Martin Hinshelwood</OldValue>
1  76:         <NewValue>Mike Hunt</NewValue>
1  77:       </Field>
1  78:       <Field>
1  79:         <Name>Created By</Name>
1  80:         <ReferenceName>System.CreatedBy</ReferenceName>
1  81:         <OldValue>Mike Hunt</OldValue>
1  82:         <NewValue>Mike Hunt</NewValue>
1  83:       </Field>
1  84:       <Field>
1  85:         <Name>Changed Date</Name>
1  86:         <ReferenceName>System.ChangedDate</ReferenceName>
1  87:         <OldValue>01/12/2008 16:05:21</OldValue>
1  88:         <NewValue>02/12/2008 12:17:08</NewValue>
1  89:       </Field>
1  90:       <Field>
1  91:         <Name>Created Date</Name>
1  92:         <ReferenceName>System.CreatedDate</ReferenceName>
1  93:         <OldValue>01/12/2008 13:51:22</OldValue>
1  94:         <NewValue>01/12/2008 13:51:22</NewValue>
1  95:       </Field>
1  96:       <Field>
1  97:         <Name>Authorized As</Name>
1  98:         <ReferenceName>System.AuthorizedAs</ReferenceName>
1  99:         <OldValue>Martin Hinshelwood</OldValue>
1 100:         <NewValue>Mike Hunt</NewValue>
1 101:       </Field>
1 102:       <Field>
1 103:         <Name>Iteration Path</Name>
1 104:         <ReferenceName>System.IterationPath</ReferenceName>
1 105:         <OldValue>TFS Sticky BuddyR4</OldValue>
1 106:         <NewValue>TFS Sticky BuddyR4</NewValue>
1 107:       </Field>
1 108:     </StringFields>
1 109:   </CoreFields>
1 110:   <ChangedFields>
1 111:     <IntegerFields />
1 112:     <StringFields>
1 113:       <Field>
1 114:         <Name>Changed By</Name>
1 115:         <ReferenceName>System.ChangedBy</ReferenceName>
1 116:         <OldValue>Martin Hinshelwood</OldValue>
1 117:         <NewValue>Mike Hunt</NewValue>
1 118:       </Field>
1 119:     </StringFields>
1 120:   </ChangedFields>
1 121: </WorkItemChangedEvent>

And what did I have to use to figure this out?

As you can see there was a lot of research, which does not include all the stuff I already know from doing the TFS Sticky Buddy  and the TFS Event Handler  .

I think that this was an unnecessary complexity and there should be an additional option for the Search Factor  enumeration should be added to make this easier.

Technorati Tags: ALM    CodeProject    TFS 

Software Development Windows
Comments

Related blog posts

No related videos found.

Connect with Martin Hinshelwood

If you've made it this far, it's worth connecting with our principal consultant and coach, Martin Hinshelwood, for a 30-minute 'ask me anything' call.

Our Happy Clients​

We partner with businesses across diverse industries, including finance, insurance, healthcare, pharmaceuticals, technology, engineering, transportation, hospitality, entertainment, legal, government, and military sectors.​

Alignment Healthcare Logo
Hubtel Ghana Logo
Schlumberger Logo
YearUp.org Logo
Boxit Document Solutions Logo
Epic Games Logo
Philips Logo
Deliotte Logo
Microsoft Logo
Kongsberg Maritime Logo
Freadom Logo
New Signature Logo
Genus Breeding Ltd Logo
Milliman Logo
Slaughter and May Logo
DFDS Logo
Xceptor - Process and Data Automation Logo
Bistech Logo
Washington Department of Transport Logo
Royal Air Force Logo
Nottingham County Council Logo
Department of Work and Pensions (UK) Logo
Ghana Police Service Logo
Washington Department of Enterprise Services Logo
Qualco Logo
Genus Breeding Ltd Logo
Cognizant Microsoft Business Group (MBG) Logo
Microsoft Logo
Trayport Logo
Sage Logo