ACL and Group Membership change logging in TFS – what are your options?

As you may know, current versions of TFS do not offer any native method to log security changes (this story is not changing much in TFS 2010, either).  So what are your options for tracking TFS security changes now? Well, I’d like to present a few ideas based on some discussions I’ve seen internally this week…

  • Manage your TFS security in Active Directory

    I have not researched it but I am fairly certain there have to be at least two AD monitoring tools out there that allow admins to track changes therein. We have long recommended that TFS group membership be managed through active directory groups, so once those groups are created and added to TFS, you maintain membership through Active Directory Users & Computer, or some other tool – not in TFS itself.

  • Roll Your Own

    TFS is open and very extensible. It’s also basically a bunch of web services. Now that my be a huge oversimplification (as I’m sure anyone who’s ever supported it to any degree will attest), but the fact remains – the client interface to TFS and the intercommunication of TFS to itself is all handled via web services. As such, you are – for the most part, and given the proper rights – allowed to call these same web services for your own purposes. To that end may I present the IdentityChangedEvent, AclChangedEvent and DataChangedEvent TFS events. The first two were introduced in TFS 2005 and later deprecated by the third (DataChangedEvent) with the release of 2008. I won’t go into depth on their usage here, suffice it to say that these events should provide enough information to those wanting to monitor TFS ACL and group membership changes through their own home-grown apps. Brian Randell's MSDN magazine article on the TFS Eventing Service is a good reference to get you started here. Here is another by Mariano Szklanny. This one has some really cool code showing how to send emails based on certain WIT events, and a particularly useful tip on translating an AD user name into their email address!

Try as I might I was unable to find an example on the Interwebs of subscribing to/utilizing information from the DataChangedEvent. So, using the aforementioned samples in bullet two as a base, I created this simple one below. Basically it’s a web service which subscribes to DataChangedEvent on a TF server. It receives the XML from the event and if the  DataType == IDENTITY, passes the SeqId-1 to the GetChangedIdentities method of  GroupSecurityService2.asmx on the same server. It then writes what it gets back to a custom even log. In my testing it is triggered very well by adding a Windows user to a TFS group (for example). Once created and up on a web server - be it on the TFS AT itself or another IIS server - you will have to subscribe your web service to DataChangedEvent on your TF server. This is the command I used (one line), where…

  • My CMD prompt was open to C:\Program Files\Microsoft Visual Studio 2008 Team Foundation Server\TF Setup on the TF server
  • My TF server was named trevorh-wstfs08
  • My custom listener web service was named ProcessDataChangedEvent.asmx, and was running on the TFS AT… just on another port (51235)

bissubscribe /eventType DataChangedEvent /address http://trevorh-wstfs08:51235/ProcessDataChangedEvent.asmx /deliveryType Soap /server https://TREVORH-WSTFS08:8081

This is not meant to be production ready by any means, of course. My intent here was to publish an example of how one might capture some of this identity change info from DataChangedEvent. Try it out and have a look in your Event Viewer on the machine hosting the web service. You may be surprised at the info you can get out of GetChangedIdentities  as you manipulate TFS permissions. The code is fairly well commented, but if you have any questions please post a comment of your own and I will try to help you out (no slagging my coding skills, please <g>).

Hope this helps!
- Trevor

Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
Imports System.IO
Imports System.Security.Cryptography.X509Certificates
Imports System.Net
Imports System.Net.Security
<System.Web.Services.WebService(Namespace:="TrevTools")> _
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<ToolboxItem(False)> _
Public Class ProcessDataChangedEvent
    Inherits System.Web.Services.WebService
    <SoapDocumentMethod( _
  "" & _
  "/06/Services/Notification/03/Notify", _
  RequestNamespace:="" & _
  "/TeamFoundation/2005/06/Services/Notification/03")> _
  <WebMethod()> _
    Public Sub Notify(ByVal eventXml As String, _
                 ByVal tfsIdentityXml As String)
        ' Creates event log for this app. Only need to be run one per machine.
        ' SetupEventSource()
        ' Necessary in order to deal with self-served certificate on my TF server using HTTPS
        ServicePointManager.ServerCertificateValidationCallback = _
            New RemoteCertificateValidationCallback(AddressOf CertificateValidationCallBack)
        Const logSource As String = "TFS_DataChangedEvent_Listener"
        Const logApp As String = "ProcessDataChangedEvent"
        Dim el As New EventLog(logApp)
        el.Source = logSource
            If eventXml Is Nothing OrElse eventXml = String.Empty Then Return
            Dim x As XElement = XElement.Load(New StringReader(eventXml))
            Dim DataType As String = x.<DataType>.Value
            If DataType Is Nothing OrElse DataType = String.Empty Then
                el.WriteEntry("DataType was empty")
            End If
            Dim SeqId As String = x.<SeqId>.Value
            If SeqId Is Nothing OrElse SeqId = String.Empty Then
                el.WriteEntry("SeqId was empty")
            End If
            el.WriteEntry("DataType " & DataType & ", SeqId " & _
                        SeqId & " received. Next event log contains data...")
            ' In my testing the SeqId returned is the next number that will be used (MaxSequence), 
            ' not the one that we're interested in, so subtract 1 from it before calling GetChangedIdentities()
            ' from /Services/v2.0/GroupSecurityService2.asmx
            SeqId = CType((CType(SeqId, Integer) - 1), String)
            If DataType = "IDENTITY" Then
                ' "MyTFS" is a Web Reference to https://trevorh-wstfs08:8081/Services/v2.0/GroupSecurityService2.asmx
                Dim GCI As New MyTFS.GroupSecurityService2
                GCI.PreAuthenticate = True
                GCI.Credentials = System.Net.CredentialCache.DefaultCredentials
                Dim ChangeInfo As String = GCI.GetChangedIdentities(SeqId)
                If ChangeInfo Is Nothing OrElse ChangeInfo = String.Empty Then
                End If
            End If
        Catch ex As Exception
            el.WriteEntry(ex.Message, EventLogEntryType.Error)
        End Try
    End Sub
    ' Creates an event log. Make sure you have enough permission to do this.
    Private Sub SetupEventSource()
        Const logSource As String = "TFS_DataChangedEvent_Listener"
        Const logApp As String = "ProcessDataChangedEvent"
        If Not EventLog.SourceExists(logSource) Then
            EventLog.CreateEventSource(logSource, logApp)
        End If
    End Sub
    ' Necessary in order to deal with self-served certificate on my TF server using HTTPS
    Function CertificateValidationCallBack( _
        ByVal sender As Object, _
        ByVal certificate As X509Certificate, _
        ByVal chain As X509Chain, _
        ByVal sslPolicyErrors As SslPolicyErrors _
            ) As Boolean
        Return True
    End Function
End Class

Comments (2)

  1. SS says:

    Hi –

    It says "Exception Message: Event type DataChangedEvent does not exist. (type SoapException)". Please help.

    Here is the command which i used

    bissubscribe /eventType DataChangedEvent /address http://xxxx:8080/TestService/DataChangedEventListner.asmx /deliveryType Soap /server http://localhost:8080

  2. SS says:

    Sounds like this won't work with TFS 2010… now in 2010 GroupSecurityService2.asmx calls have to be associated with a team project collection….

Skip to main content