a·gen·tic a·gil·i·ty

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

Explains how to retrieve a user or group identity from Team Foundation Server using only a display name, including handling Active Directory lookups and group assignments.

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

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
Subscribe

Related Blog

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.​

Genus Breeding Ltd Logo

Genus Breeding Ltd

Cognizant Microsoft Business Group (MBG) Logo

Cognizant Microsoft Business Group (MBG)

NIT A/S

Ericson Logo

Ericson

Sage Logo

Sage

Slicedbread Logo

Slicedbread

Capita Secure Information Solutions Ltd Logo

Capita Secure Information Solutions Ltd

Brandes Investment Partners L.P. Logo

Brandes Investment Partners L.P.

Freadom Logo

Freadom

Philips Logo

Philips

Trayport Logo

Trayport

YearUp.org Logo

YearUp.org

SuperControl Logo

SuperControl

Alignment Healthcare Logo

Alignment Healthcare

Higher Education Statistics Agency Logo

Higher Education Statistics Agency

Emerson Process Management Logo

Emerson Process Management

MacDonald Humfrey (Automation) Ltd. Logo

MacDonald Humfrey (Automation) Ltd.

New Signature Logo

New Signature

Washington Department of Transport Logo

Washington Department of Transport

Ghana Police Service Logo

Ghana Police Service

Washington Department of Enterprise Services Logo

Washington Department of Enterprise Services

Department of Work and Pensions (UK) Logo

Department of Work and Pensions (UK)

Nottingham County Council Logo

Nottingham County Council

New Hampshire Supreme Court Logo

New Hampshire Supreme Court

Qualco Logo

Qualco

Freadom Logo

Freadom

DFDS Logo

DFDS

Workday Logo

Workday

Slaughter and May Logo

Slaughter and May

Xceptor - Process and Data Automation Logo

Xceptor - Process and Data Automation