How do I get filenames of checked out files from PendChangesNotification?
-
Monday, February 28, 2011 6:10 PM
How do I get information about the pending changes when a user checks out a file from TFS 2010? I need to know what files the user is trying to checkout.
I'm writing a TFS server-side subscriber plugin that subscribes to PendChangesNotification.
I've implemented my plugin following the information in Martin Hinshelwood's blog post Team Foundation Server 2010 Event Handling with Subscribers.
I was hoping that PendingChanges property of the PendChangesNotification class would lead me to the information about which file was being checked out, but in my tests PendingChanges.Length is always zero.
The documentation online for the Microsoft.TeamFoundation.VersionControl.Server.PendChangesNotification class does not provide any information about the class members other than their name and type.Interestingly if I subscribe to UndoPendingChangesNotification the ItemsToUndo property contains a list file paths within version control (for example, "$/TestProj/TestApp1/Source/Class1.cs") when the NotificationType is DecisionPoint, but not when the NotificationType is Notification. It seems strange to me that I can get the file path information for UndoPendingChangesNotification when I can't get it for PendChangesNotification.
Any help with this is greatly appreciated.
Bryan Knox
All Replies
-
Wednesday, March 02, 2011 3:06 AMModerator
Hello Bryan,
Thanks for your post and sorry for the late reply.
It is a shame that I am not familiar with PendChangesNotification class. However, one method I can think out to get the filename of these checked out files is that you can use the tf.exe status command line.
You can have your command line to be similar to:
tf.exe status /collection:TeamProjectCollectionURL /user:* /recursive > c:\checkedout.txt
“c:\checkedout.txt” is the path to save the output.
You can refer to this article for further information about the Status Command.
http://msdn.microsoft.com/en-us/library/9s5ae285.aspx
In addition, you can also take a look at this blog:
I hope it can help you with your question.
Thanks,
Vicky Song [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.

-
Wednesday, March 02, 2011 5:52 PM
Hi Vicky,
Thanks for the tips. Unfortunately my plug-in needs to run on the TFS server where I told installing VS and other client tools is not recommended.
I guess I could figure out how to get the information I need directly from the SQL database when my plugin receives a PendChangesNotification, but I'd really rather use the APIs and not break encapsulation.
Is there anywhere else I can go to learn more about using PendChangesNotification and other classes in the
Microsoft.TeamFoundation.VersionControl.Server namespace?
Bryan Knox -
Friday, March 04, 2011 9:13 AMModerator
Hello Bryan,
You can refer to these articles for further information about PendChangesNotification class:
PendChangesNotification Methods
PendChangesNotification Properties
And more classes information in the namespace Microsoft.TeamFoundation.VersionControl.Server:
http://msdn.microsoft.com/en-us/library/microsoft.teamfoundation.versioncontrol.server.aspx
I hope it can help you with your issue.
Thanks,
Vicky Song [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.

-
Saturday, March 05, 2011 4:05 AM
Hi Vicky,
I had arleady seen those articles, they only contain member name and type information and no other documentation. No help.
Thanks.
Bryan Knox -
Wednesday, March 09, 2011 3:27 AMModerator
Hello Bryan,
I am sorry the links above do not help you a lot. I have escalated your issue to my mentor, and he may help you write the sample code. Once he finished it, I will share it with you.
Thanks,
Vicky Song [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.

-
Tuesday, October 04, 2011 9:42 AM
I experience the very same issue :(
I want to validate the creation of a Branch during a Notification's DecisionPoint, and before the check-in of that new branch. The only event I can subscribe, to be notified of this creation before the check-in, is therefore PendChangesNotification. But it does not contain any useful information :(
(I get a PendChangesNotification only when the creation convert the source folder into a branch. If it's already a branch, I get directly a check-in notification).
Help would really be welcome on this topic!
Notice I did try to fetch all the pendingChanges (using the code here after) but didn't get the newly created Branch in the output reader, meaning that it's not yet (?) a "pendingChange" during the DecisionPoint. However, the name of target branch is already known at least by Visual Studio and should be made available for TFS! So, where is this info ???
var versionControl = requestContext.GetService<TeamFoundationVersionControlService>(); PendChangesNotification arg = notificationEventArgs as PendChangesNotification; if (arg != null) { ItemSpec item = new ItemSpec("$", RecursionType.Full); var itemSpecs = new ItemSpec[] { item }; var output = tempVersionControl.QueryPendingChangesForWorkspace( requestContext, arg.WorkspaceName, arg.UserName, itemSpecs, true, 9999, null, true);
Did you find anything helpful Bryan ?
V.
- Edited by Valéry Letroye Tuesday, October 04, 2011 1:05 PM
-
Tuesday, October 04, 2011 5:24 PM
Hi Valéry,
I haven't found anything helpful yet. I'll post an update here if I do.
Kind regards.
Bryan Knox -
Tuesday, November 01, 2011 9:22 AM
You might find this interesting for info (in VB)
' First Get the local file path Dim params1 As NameValueCollection = requestContext.Method.Parameters Dim workspacename As String = params1("workspaceName") Dim requesttype As String = params1("changes[0].RequestType") Dim localfilepath As String = params1("changes[0].ItemSpec") ' Debug Output For Each key In params1.AllKeys Dim keyval As String = key Dim val As String = params1(key) Debug.WriteLine("key:" & keyval & " val:" & val) Next ' Next get the Workspace Dim vc As TeamFoundationVersionControlService = requestContext.GetService(Of TeamFoundationVersionControlService)() Dim tmpws As Workspace = vc.QueryWorkspace(requestContext, workspacename, notificationEventArgs.UserName) ' Next get the SourceControl Path Dim serverpath As String = "" For Each wf As WorkingFolder In tmpws.Folders If wf.Type = WorkingFolderType.Map And localfilepath.Contains(wf.LocalItem) Then Dim tmp_str As String = localfilepath.Replace(wf.LocalItem, "") tmp_str = tmp_str.Replace("\", "/") serverpath = wf.ServerItem & tmp_str Exit For End If Next Debug.WriteLine("Serverpath: " & serverpath)
- Marked As Answer by BryanKnox Tuesday, November 01, 2011 5:14 PM
-
Tuesday, November 01, 2011 5:17 PM
Thanks for the code Richard! That gets me what I need.
Bryan Knox -
Wednesday, November 02, 2011 4:18 PM
The below code might be more useful, I've discovered that ItemSpec sometimes also contains the recursion type at the end if you check out a folder instead of a file
also with folders it looks like it's the server path of $/ form instead of the local path
What I'm trying to figure out next, is if it's possible to re-use the requestcontext to do another checkout on the back of the first (I'm trying to write a plugin for Shared Files similar to the old sourcesafe)
Example call
' Get the Version Control Service VCService = RequestContext.GetService(Of TeamFoundationVersionControlService)() ' Get the Workspace Name WorkSpaceName = NotificationEventArgs.WorkspaceName ' Get the Workspace instance RequestWorkspace = VCService.QueryWorkspace(RequestContext, WorkSpaceName, NotificationEventArgs.UserName) ' Get a List of Files / directories ready to be checked out ChangeRequests = VCChangeRequest.NVC_To_ChangeRequestList(RequestContext.Method.Parameters, RequestWorkspace)
VCChangeRequest.vb
Imports Microsoft.TeamFoundation.VersionControl.Server Imports System.Collections.Specialized Namespace Containers ''' <summary> ''' Custom ChangeRequest class, inherits from TFs Server ChangeRequest ''' </summary> Public Class VCChangeRequest Inherits ChangeRequest #Region "Public Properties" ''' <summary> ''' Server Request Parameters that were used to construct this Change Request ''' </summary> Public Property ServerRequestParameters As NameValueCollection ''' <summary> ''' ID of the ChangeRequest within the Server Request Parameters ''' </summary> Public Property ID As Integer ''' <summary> ''' Local File Path to the file ''' </summary> Public ReadOnly Property LocalPath As String Get Return Me.ItemSpec.Item End Get End Property ''' <summary> ''' Server Path to the file (e.g. $/ form) ''' </summary> Public Property ServerPath As String #End Region #Region "Public Constructors" ''' <summary> ''' Default Constructor ''' </summary> Public Sub New() End Sub ''' <summary> ''' Default Constructor ''' </summary> Public Sub New(nvc As NameValueCollection, index As Integer) Me.ServerRequestParameters = nvc Me.ID = index Dim keyheader As String = "changes[" & index & "]" If nvc.AllKeys.Contains(keyheader & ".DeletionId") Then Dim val As String = nvc(keyheader & ".DeletionId") Integer.TryParse(val, Me.DeletionId) End If If nvc.AllKeys.Contains(keyheader & ".Encoding") Then Dim val As String = nvc(keyheader & ".Encoding") Integer.TryParse(val, Me.Encoding) End If If nvc.AllKeys.Contains(keyheader & ".ItemSpec") Then Dim val As String = nvc(keyheader & ".ItemSpec") ' Determine the item name ' If the RecursionType has been specified then remove this from the end of the string Dim itemname As String = val For Each name As String In [Enum].GetNames(GetType(RecursionType)) Dim tmp_str As String = " (" & name & ")" If itemname.EndsWith(tmp_str) Then itemname = itemname.Substring(0, itemname.LastIndexOf(tmp_str)) Next ' Determine the recursion type Dim resurstype As RecursionType = RecursionType.None For Each name As String In [Enum].GetNames(GetType(RecursionType)) Dim tmp_str As String = " (" & name & ")" If val.EndsWith(tmp_str) Then [Enum].TryParse(name, resurstype) Next Me.ItemSpec = New ItemSpec(itemname, resurstype) End If If nvc.AllKeys.Contains(keyheader & ".LockLevel") Then Dim val As String = nvc(keyheader & ".LockLevel") [Enum].TryParse(val, Me.LockLevel) End If If nvc.AllKeys.Contains(keyheader & ".RequestType") Then Dim val As String = nvc(keyheader & ".RequestType") [Enum].TryParse(val, Me.RequestType) End If If nvc.AllKeys.Contains(keyheader & ".TargetItem") Then Dim val As String = nvc(keyheader & ".TargetItem") Me.TargetItem = val End If If nvc.AllKeys.Contains(keyheader & ".TargetItemType") Then Dim val As String = nvc(keyheader & ".TargetItemType") [Enum].TryParse(val, Me.TargetItemType) End If End Sub #End Region #Region "Public Functions" ''' <summary> ''' Calculate the Server Path based on the work space / local path ''' </summary> Public Sub DetermineServerPath(ws As Workspace) If LocalPath.StartsWith("$/") Then ServerPath = LocalPath Exit Sub End If For Each wf As WorkingFolder In ws.Folders If wf.Type = WorkingFolderType.Map And Me.LocalPath.Contains(wf.LocalItem) Then Dim tmp_str As String = Me.LocalPath.Replace(wf.LocalItem, "") tmp_str = tmp_str.Replace("\", "/") ServerPath = wf.ServerItem & tmp_str Exit For End If Next End Sub #End Region #Region "Public Shared Functions" ''' <summary> ''' Convert a NameValueCollection / List of Parameters to a List of VCChangeRequest ''' </summary> ''' <remarks>Typically Requests come in to the server as a NameValueCollection ''' This Function Converts them to a List of ChangeRequests</remarks> Public Shared Function NVC_To_ChangeRequestList(nvc As NameValueCollection) As List(Of ChangeRequest) Return NVC_To_ChangeRequestList(nvc, Nothing) End Function ''' <summary> ''' Convert a NameValueCollection / List of Parameters to a List of VCChangeRequest ''' </summary> ''' <remarks>Typically Requests come in to the server as a NameValueCollection ''' This Function Converts them to a List of ChangeRequests</remarks> Public Shared Function NVC_To_ChangeRequestList(nvc As NameValueCollection, ws As Workspace) As List(Of ChangeRequest) Dim retval As New List(Of ChangeRequest) Dim CRCount As Integer If nvc.AllKeys.Contains("changes") = False Then Return Nothing Dim tmp_count As String = nvc("changes") tmp_count = tmp_count.Replace("Count =", "").Trim(" ") If Integer.TryParse(tmp_count, CRCount) = False Then Return Nothing For i As Integer = 0 To (CRCount - 1) Dim newCR As New VCChangeRequest(nvc, i) If ws IsNot Nothing Then newCR.DetermineServerPath(ws) retval.Add(newCR) Next Return retval End Function #End Region End Class End Namespace
- Marked As Answer by BryanKnox Wednesday, November 02, 2011 4:32 PM
-
Wednesday, November 02, 2011 5:13 PMIt looks like requestContext.Method.Parameters contains maximum 10 changes - there is never "changes[10].ItemSpec". If you do checkout more then 10 files at the same time you will miss some :(.
-
Wednesday, November 02, 2011 5:35 PM
I'll have a look at that next, using VCService.CheckPendingChanges with a new requestcontext might be one way around it
in the mean time, I've discovered that if you want to create a new RequestContext (for example to check out a 2nd file when the 1st file is checked out during the 1st event), you can use
newcontext = RequestContext.ServiceHost.CreateSystemContext()
e.g.
' Create a change request Dim newchange As New ChangeRequest newchange.RequestType = RequestType.Edit newchange.ItemSpec = New ItemSpec("C:\DotNet TFS\TestPlugin1\Plugin1\test2\readme2.txt", RecursionType.None) newchange.DeletionId = 0 newchange.ItemType = ItemType.Any newchange.Encoding = -2 ' Do a CheckOut Dim changearr() As ChangeRequest = New ChangeRequest() {newchange} Dim newcontext As TeamFoundationRequestContext newcontext = RequestContext.ServiceHost.CreateSystemContext() VCService.PendChanges(newcontext, WorkSpaceName, NotificationEventArgs.OwnerName, changearr, 0, 0, Nothing) ' In order for this to show up in Studio, a Refresh of the folder is needed in the Context menu

