Buck Hodges

Visual Studio ALM (VSALM, formerly VSTS) - Team Foundation Service/Server (TFS) - MSDN

October, 2005

Posts
  • Buck Hodges

    Getting email when someone overrides a policy

    • 0 Comments

    James Manning today pointed out a post from Marcel de Vries showing how to register for email when someone checks in after overriding a policy failure.  While any policy failres and override comment are included in the standard check-in email, this allows you to get an email specifically when someone overrides a policy failure.  It's only a few lines of code, and it uses the built-in event system support for sending emails.  He even provides a link to a zip file with the solution.

    How to receive email on a Team Foundation check in policy violation

    [Update] He mentions in the post that the delivery of the events is guaranteed: "the implementation also has a guaranteed delivery system using SQL server."  Though the events do get queued for delivery in a table in the SQL DB, there's no guarantee that an event gets recorded, because the recoding of the event is not transactional with respect to the work that generated the event.  It's pretty robust, but it's not guaranteed.

  • Buck Hodges

    Beta 3 refresh released

    • 2 Comments

    Beta 3 of TFS has now been released and should be available on MSDN today (or very soon :-).  The differences between beta 3 and beta 3 refresh are small for most things.  Jeff Beehler wrote about some of the beta 3 refresh differences two weeks ago.  The most important reason for the beta 3 refresh is that it is built against and uses all of the final release versions of Visual Studio 2005, .NET 2.0, and SQL Server 2005.  Beyond that, many of the fixes in it were to enhance the setup experience and to address a few localization issues.  So, if you are wondering whether some particular bug is fixed, the answer is most likely to be no, with the exception of some issues that Jeff mentioned.

    If you have beta 3 installed, you'll want to read the following guide to succesfully upgrade without losing your existing data.

    Migrating to Visual Studio 2005 Team Foundation Server Beta 3 Refresh

  • Buck Hodges

    Changing the encoding of a pending add works for RTM

    • 1 Comments
    I had mentioned in my September 10th post on file type detection that you couldn't change the encoding on a pending add, without undoing the add and re-adding it.  While it won't work in beta 3 or beta 3 refresh, you'll be able to change the encoding on a pending add in the next public release.  That means you can use the edit command with /type option from the command line or the properties dialog in the GUI to tweak the encoding before checking it in.
  • Buck Hodges

    Displaying the sizes and dates of files in the server

    • 4 Comments

    Have you wished that the output of the dir command from tf.exe would show you the dates and sizes of the files like cmd.exe's dir command?  Even though tf.exe won't do that in version 1, the version control API provides the call necessary.  This little app is similar to my last post on displaying labels on a file.  You can find a simple example of creating a workspace, pending changes, and so forth in my September API example.

    The GetItems() method is the key part of the app.  Here's the declaration on the VersionControlServer class.

    public ItemSet GetItems(String path, RecursionType recursion)

    For this overload of the GetItems() method, there's no version parameter, so it defaults to get the Item objects for the latest version in the repository.  There is another overload that takes the version, and you could pass "new WorkspaceVersionSpec(wsInfo.Name, wsInfo.OwnerName)" as the version to get the Item objects corresponding to the versions in the workspace.

    The first argument is the path on which we want data.  That can be either a local path or a server path.

    The recursion parameter specifies how deep we want to go.  If the path is a directory, specifying RecursionType.OneLevel retrieves only the items directly contained in that directory, just like specifying the wildcard "*" would.  If the path is a file, there is no difference between one level of recursion and none because a file can't have children.  The default for this app is onle level of recursion, which is the same as what both cmd.exe's dir command and tf.exe's dir command use.

    If the user specifies /r, the app passes RecursionType.Full.  For a directory, that will return all of its descendants.  If the path doesn't match a directory, the server will recursively find all items under path that match.  The path it uses is the directory portion of the path, and the pattern is the file name portion of the path.  So, if the user specifies "c:\project\readme.txt /r" the server will return all files called readme.txt underneath c:\project, recursively.

    In the app, we only want the items for display, which is the Items property on the ItemSet object returned by GetItems().  The other two properties on ItemSet, QueryPath and Pattern, indicate how the server used the specified path to do the matching.  In our previous example of "c:\project\readme.txt /r" the result would be QueryPath set to the server path corresponding to c:\project and Pattern set to readme.txt.

    Here's an example of running the app.

    D:\ws1>D:\code\projects\DirSize\DirSize\bin\Debug\DirSize.exe . /r
    10/25/2005 11:40:25 AM    <DIR>             $/testproj
    10/25/2005 03:59:50 PM    <DIR>             $/testproj/A
    10/25/2005 03:59:50 PM    <DIR>             $/testproj/B
    10/26/2005 11:04:34 PM                 7996 $/testproj/out.txt
    10/25/2005 04:55:50 PM                    6 $/testproj/A/a.txt

    2 files, 3 folders, 8002 bytes

    To build it, you can create a Windows console app in Visual Studio, drop this code into it, and add the following references to the VS project.

    Microsoft.TeamFoundation.Client
    Microsoft.TeamFoundation.Common
    Microsoft.TeamFoundation.VersionControl.Client
    Microsoft.TeamFoundation.VersionControl.Common

    using System;
    using System.Globalization;
    using System.IO;
    using Microsoft.TeamFoundation;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.VersionControl.Client;
    using Microsoft.TeamFoundation.VersionControl.Common;
    
    namespace DirSize
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Check and get the arguments.
                String path;
                RecursionType recursion;
                VersionControlServer sourceControl;
                GetPathAndRecursion(args, out path, out recursion, out sourceControl);
    
                Item[] items = null;
                try
                {
                    // Get the latest version of the information for the items.
                    ItemSet itemSet = sourceControl.GetItems(path, recursion);
                    items = itemSet.Items;
                }
                catch (TeamFoundationServerException e)
                {
                    // We couldn't contact the server, the item wasn't found,
                    // or there was some other problem reported by the server,
                    // so we stop here.
                    Console.Error.WriteLine(e.Message);
                    Environment.Exit(1);
                }
    
                if (items.Length == 0)
                {
                    Console.WriteLine("There are no items for " + path);
                }
                else
                {
                    Array.Sort(items, Item.Comparer);
    
                    long totalBytes = 0;
                    int numFiles = 0, numFolders = 0;
                    String format = "{0,-25} {1,-8} {2, 8} {3}";
                    foreach (Item item in items)
                    {
                        if (item.ItemType == ItemType.File)
                        {
    // In addition to the information printed, the Item object has
    // the changeset version and MD5 hash of the file content as
    // properties on item
    . numFiles++; totalBytes += item.ContentLength; Console.WriteLine(format, item.CheckinDate.ToLocalTime().ToString("G"), String.Empty, item.ContentLength, item.ServerItem); } else { numFolders++; Console.WriteLine(format, item.CheckinDate.ToLocalTime().ToString("G"), "<DIR>", String.Empty, item.ServerItem); } } Console.WriteLine(); Console.WriteLine("{0} files, {1} folders, {2} bytes", numFiles, numFolders, totalBytes); } } private static void GetPathAndRecursion(String[] args, out String path, out RecursionType recursion, out VersionControlServer sourceControl) { if (args.Length > 2 || args.Length == 1 && args[0] == "/?") { Console.WriteLine("Usage: dirsize"); Console.WriteLine(" dirsize [path] [/r]"); Console.WriteLine(); Console.WriteLine("With no arguments, shows the size information for the current directory."); Console.WriteLine("If a path is specified, it shows the size information for that path."); Console.WriteLine("If /r is specified, all of the children of the path will be included."); Console.WriteLine(); Console.WriteLine("Examples: dirsize $/secret"); Environment.Exit(1); } // Figure out the server based on either the argument or the // current directory. WorkspaceInfo wsInfo = null; if (args.Length < 1 || args.Length == 1 && args[0] == "/r") { path = Environment.CurrentDirectory; } else { path = args[0]; try { if (!VersionControlPath.IsServerItem(path)) { wsInfo = Workstation.Current.GetLocalWorkspaceInfo(path); } } catch (Exception e) { // The user provided a bad path argument. Console.Error.WriteLine(e.Message); Environment.Exit(1); } } if (wsInfo == null) { wsInfo = Workstation.Current.GetLocalWorkspaceInfo(Environment.CurrentDirectory); } // Stop if we couldn't figure out the server. if (wsInfo == null) { Console.Error.WriteLine("Unable to determine the server."); Environment.Exit(1); } TeamFoundationServer tfs = TeamFoundationServerFactory.GetServer(wsInfo.ServerName); // RTM: wsInfo.ServerUri.AbsoluteUri); sourceControl = (VersionControlServer)tfs.GetService(typeof(VersionControlServer)); // Pick up the recursion, if supplied. By default, // we want item and its immediate children, if it is a folder. // If the item is a file, then only the file will be returned. recursion = RecursionType.OneLevel; if (args.Length == 1 && args[0] == "/r" || args.Length == 2 && args[1] == "/r") { recursion = RecursionType.Full; } } } }

    [Update 11/4/2005] I added some explanation of the version used with the GetItems() call and the HashValue property of the Item class.
  • Buck Hodges

    Displaying the labels on a file, including label comments

    • 14 Comments

    Unfortunately, there's not a fast, efficient way to see the list of labels in the system with the full comment without also seeing a list of all of the files included in a label.  You also can't efficiently answer the question, "What labels involve foo.cs?"  While this won't be changed for v1, you can certainly do it using code.  I mentioned on the TFS forum that I'd try to put together a piece of code to do this.  The result is the code below.

    The code is really simple to do this, but I ended up adding more to it than I originally intended.  All that's really necessary here is to call QueryLabels() to get the information we need.

    Let's look at the QueryLabels() call in a little detail, since it is the heart of the app.  Here is the method declaration from the VersionControlServer class.

    public VersionControlLabel[] QueryLabels(String labelName,
                                             
    String labelScope,
                                             String owner, 
                                             bool includeItems,
                                             String filterItem,
                                             VersionSpec versionFilterItem)

    By convention, methods that begin with "Query" in the source control API allow you to pass null to mean "give me everything."  In the code below, I don't want to filter by labelName or owner, so I set those to null to include everything.

    If the user specified a server path for the scope (scope is always a server path and not a local path), we'll use it, and otherwise we'll use the root ($/).  The scope of a label is, effectively, the part of the tree where it has ownership of that label name.  In other words by specifying the label scope, $/A and $/B can have separate labels named Foo, but no new label Foo can be used under $/A or $/B.  For this program, setting the scope simply narrows the part of the tree it will include in the output.  For example, running this with a scope of $/A would show only one label called Foo, but running it with $/ as the scope (or omitting the scope) would result in two Foo labels being printed.

    D:\ws1>tree
    Folder PATH listing for volume Dev
    Volume serial number is 0006EE50 185C:793F
    D:.
    ├───A
    └───B

    D:\ws1>tf label Foo@$/testproj/A A
    Created label
    Foo@$/testproj/A

    D:\ws1>tf label Foo@$/testproj/B B
    Created label
    Foo@$/testproj/B

    D:\ws1>d:\LabelHistory\LabelHistory\bin\Debug\LabelHistory.exe
    Foo (10/25/2005 4:00 PM)
    Foo (10/25/2005 4:00 PM)

    The most important parameter here is actually includeItems.  By setting this parameter to false, we'll get the label metadata without getting the list of files and folders that are in the label.  This saves both a ton of bandwidth as well as load on the server for any query involving real-world labels that include many thousands of files.

    The remaining parameters are filterItem and versionFilterItem.  The filterItem parameter allows you to specify a server or local path whereby the query results will only include labels involving that file or folder.  It allows you to answer the question, "What labels have been applied to file foo.cs?"  The versionFilterItem is used to specify what version of the item had the specified path.  It's an unfortunate complexity that's due to the fact that we support rename (e.g., A was called Z at changeset 12, F at changeset 45, and A at changeset 100 and beyond).  Before your eyes glaze over (they haven't already, right?), I just set that parameter to latest.

    Here's an example of using the program with the tree mentioned earlier.  I modified the Foo label on A to have a comment, so it has a later modification time.

    D:\ws1>tf label Foo@$/testproj/A /comment:"This is the first label I created."
    Updated label Foo@$/testproj/A

    D:\ws1>d:\LabelHistory\LabelHistory\bin\Debug\LabelHistory.exe
    Foo (10/25/2005 4:05 PM)
       Comment: This is the first label I created.
    Foo (10/25/2005 4:00 PM)

    Then I added a file under A, called a.txt, and modified the label to include it.  Running the app on A\a.txt, we see that it is only involved in one of the two labels in the system.

    D:\ws1>tf label Foo@$/testproj/A A\a.txt
    Updated label
    Foo@$/testproj/A

    D:\ws1>d:\LabelHistory\LabelHistory\bin\Debug\LabelHistory.exe A\a.txt
    Foo (10/25/2005 4:56 PM)
       Comment: This is the first label I created.

    To build it, you can create a Windows console app in Visual Studio, drop this code into it, and add the following references to the VS project.

    Microsoft.TeamFoundation.Client
    Microsoft.TeamFoundation.Common
    Microsoft.TeamFoundation.VersionControl.Client
    Microsoft.TeamFoundation.VersionControl.Common

    using System;
    using System.IO;
    using Microsoft.TeamFoundation;
    using Microsoft.TeamFoundation.Client;
    using Microsoft.TeamFoundation.VersionControl.Client;
    using Microsoft.TeamFoundation.VersionControl.Common;
    
    namespace LabelHistory
    {
        class Program
        {
            static void Main(string[] args)
            {
                // Check and get the arguments.
                String path, scope;
                VersionControlServer sourceControl;
                GetPathAndScope(args, out path, out scope, out sourceControl);
    
                // Retrieve and print the label history for the file.
                VersionControlLabel[] labels = null;
                try
                {
                    // The first three arguments here are null because we do not
                    // want to filter by label name, scope, or owner.
                    // Since we don't need the server to send back the items in
                    // the label, we get much better performance by ommitting
                    // those through setting the fourth parameter to false.
                    labels = sourceControl.QueryLabels(null, scope, null, false, 
                                                       path, VersionSpec.Latest);
                }
                catch (TeamFoundationServerException e)
                {
                    // We couldn't contact the server, the item wasn't found,
                    // or there was some other problem reported by the server,
                    // so we stop here.
                    Console.Error.WriteLine(e.Message);
                    Environment.Exit(1);
                }
    
                if (labels.Length == 0)
                {
                    Console.WriteLine("There are no labels for " + path);
                }
                else
                {
                    foreach (VersionControlLabel label in labels)
                    {
                        // Display the label's name and when it was last modified.
                        Console.WriteLine("{0} ({1})", label.Name,
                                          label.LastModifiedDate.ToString("g"));
    
                        // For labels that actually have comments, display it.
                        if (label.Comment.Length > 0)
                        {
                            Console.WriteLine("   Comment: " + label.Comment);
                        }
                    }
                }
            }
    
            private static void GetPathAndScope(String[] args,
                                                out String path, out String scope,
                                                out VersionControlServer sourceControl)
            {
                // This little app takes either no args or a file path and optionally a scope.
                if (args.Length > 2 || 
                    args.Length == 1 && args[0] == "/?")
                {
                    Console.WriteLine("Usage: labelhist");
                    Console.WriteLine("       labelhist path [label scope]");
                    Console.WriteLine();
                    Console.WriteLine("With no arguments, all label names and comments are displayed.");
                    Console.WriteLine("If a path is specified, only the labels containing that path");
                    Console.WriteLine("are displayed.");
                    Console.WriteLine("If a scope is supplied, only labels at or below that scope will");
                    Console.WriteLine("will be displayed.");
                    Console.WriteLine();
                    Console.WriteLine("Examples: labelhist c:\\projects\\secret\\notes.txt");
                    Console.WriteLine("          labelhist $/secret/notes.txt");
                    Console.WriteLine("          labelhist c:\\projects\\secret\\notes.txt $/secret");
                    Environment.Exit(1);
                }
    
                // Figure out the server based on either the argument or the
                // current directory.
                WorkspaceInfo wsInfo = null;
                if (args.Length < 1)
                {
                    path = null;
                }
                else
                {
                    path = args[0];
                    try
                    {
                        if (!VersionControlPath.IsServerItem(path))
                        {
                            wsInfo = Workstation.Current.GetLocalWorkspaceInfo(path);
                        }
                    }
                    catch (Exception e)
                    {
                        // The user provided a bad path argument.
                        Console.Error.WriteLine(e.Message);
                        Environment.Exit(1);
                    }
                }
    
                if (wsInfo == null)
                {
                    wsInfo = Workstation.Current.GetLocalWorkspaceInfo(Environment.CurrentDirectory);
                }
    
                // Stop if we couldn't figure out the server.
                if (wsInfo == null)
                {
                    Console.Error.WriteLine("Unable to determine the server.");
                    Environment.Exit(1);
                }
    
                TeamFoundationServer tfs =
                    TeamFoundationServerFactory.GetServer(wsInfo.ServerName);
                                                          // RTM: wsInfo.ServerUri.AbsoluteUri);
                sourceControl = (VersionControlServer)tfs.GetService(typeof(VersionControlServer));
    
                // Pick up the label scope, if supplied.
                scope = VersionControlPath.RootFolder;
                if (args.Length == 2)
                {
                    // The scope must be a server path, so we convert it here if
                    // the user specified a local path.
                    if (!VersionControlPath.IsServerItem(args[1]))
                    {
                        Workspace workspace = wsInfo.GetWorkspace(tfs);
                        scope = workspace.GetServerItemForLocalItem(args[1]);
                    }
                    else
                    {
                        scope = args[1];
                    }
                }
            }
        }
    }

    [Update 10/26] I added Microsoft.TeamFoundation.Common to the list of assemblies to reference.

    [Update 7/12/06]  Jeff Atwood posted a VS solution containing this code and a binary.  You can find it at the end of http://blogs.vertigosoftware.com/teamsystem/archive/2006/07/07/Listing_all_Labels_attached_to_a_file_or_folder.aspx.

  • Buck Hodges

    Web Load Testing webcast Tuesday (Oct. 25) at 1:00 pm PST

    • 0 Comments

    If you want to learn more about web load testing in VSTS, you'll want to check out Ed Glas' webcast on Tuesday, Oct. 25.  Brian Harry has been using VSTS load testing developed by Ed's group to answer the question, "How many users will your Team Foundation Server support?"  Find out how you can do the same for your own web site or web service.

    MSDN Webcast: Load and Web Testing with Microsoft Visual Studio 2005 Team System (Level 200)    

    Start Time:   Tuesday, October 25, 2005 1:00 PM (GMT-08:00) Pacific Time (US & Canada) 
    End Time:
       Tuesday, October 25, 2005 2:00 PM (GMT-08:00) Pacific Time (US & Canada) 
     
    Event Description 
     Products: Visual Studio.

     Recommended Audience: Developer.

     Language: English-American
     
     Description:   By using Microsoft Visual Studio 2005 Team System as a platform, you can better manage the software development life cycle. You have the flexibility to customize and extend this platform to meet organizational needs. In this webcast, gain a general understanding of the Web and load testing features in Visual Studio 2005.

    Presenter: Ed Glas, Group Manager, Microsoft Corporation

  • Buck Hodges

    Team Foundation Beta 3 Virtual PC is headed your way

    • 7 Comments
    The beta 2 Virtual PC image was hugely popular.  The new Team Foundation beta 3 VPC image is now making its way up to MSDN.  So, later today or Monday you'll be able to download and run a single-server and client beta 3.  Just make sure you have plenty of RAM since you'll be running everything on one machine (say 2GB+).
  • Buck Hodges

    Beta 3 known bug when comparing large files: "Invalid access code (bad parameter)."

    • 0 Comments

    In the forum, Carl Daniel reported getting the message "Invalid access code (bad parameter)" when comparing two different versions of a file in Team Foundation Source Control Beta 3.

    Unfortunately this is a bug that was discovered and fixed after beta 3 was released.  The problem is that large files (on the order of 500K) will crash the diffmerge.exe tool.  I'm not certain as to the exact size that begins to trigger the problem.

    If you hit this bug, you'll need to work around it by using another diff tool until a newer public release fixes the problem (the upcoming beta 3 refresh will still have this bug).  Of course, you could also reduce the size of the file, but that's not often an option.

  • Buck Hodges

    September dogfood statistics

    • 0 Comments

    You can find the latest dogfood statistics on Brian's blog.

    [Update 10/12]  John Lawrence also has the statistics as usual, including Excel charts.  I saw Brian's, and I didn't think to check there.

  • Buck Hodges

    SQL error in get with beta 3

    • 0 Comments

    On the forum, a couple of users have run into this issue with get, so I thought I would mention it here.

    There's a known issue in beta 3 where mapping a server path to a local path that exceeds the NTFS limit results in a SQL error message.  The RTM code will give you a nice message to that effect rather than the SQL error message.

    A database error occurred (SQL error 8152) ---> String or binary data would be truncated.

    MyServer.TFSVersionControl..prc_Get: Database Update Failure - Error 8152 executing EXECUTESQL statement for #versionedItems

    The statement has been terminated.

    If you run into this problem, you can fix it by editing your workspace mappings to change the local path to be shorter.

    For example, if you have "$/project/some_path" mapped to "c:\documents and settings\user\visual studio projects\some_path" then change the local path to something shorter, such as "c:\projects\some_path" and then run get again.  To modify your mappings in VS, use File -> Source Control -> Workspaces, select your workspace, and click Edit.

  • Buck Hodges

    Ed Hintz has started blogging

    • 0 Comments
    Ed Hintz has started blogging with a post that describes some of the differences between working with SourceSafe and working with TFS.  Ed's the dev lead for the source control client team and is the person to whom I report.  So, check it out and be sure to let him know about the things you do and don't like in the TFS source control integration for VS 2005.  Your feedback will not likely change v1 at this point, but if you want to influence the next release, now is the time to speak up.
  • Buck Hodges

    TFS version control command line: tf.exe

    • 0 Comments

    For folks who like command lines or those who've found the typical command line experience a little spartan, you should try tf.exe.

    One interesting feature of our command line is that it uses dialogs for a number of commands when you are running it interactively (i.e., you don't specify /noprompt or /i).  For instance, you get the same check-in dialog from the checkin command in tf.exe as you get from VS.

    The following commands bring up the same dialogs you'd find in VS.

    • changeset
    • checkin
    • difference runs external diff viewer
    • get will take you to the conflict dialog as necessary
    • history
    • resolve
    • shelve
    • unshelve
    • view runs the associated editor
    • workspace

    That makes working with the command line a lot more convenient than you might otherwise think.  You can find the command line docs on MSDN at http://msdn2.microsoft.com/en-us/library/cc31bk2e(en-us,vs.80).aspx.

  • Buck Hodges

    Brian Harry posts about the changes between beta 2 and beta 3

    • 0 Comments

    A while back I wrote a little about what's changed since beta 2 and the release of beta 3.

    Brian Harry, the person who heads Team Foundation (he is Product Unit Manager), has just written the post about what changed.  In What's new with TFS Beta 3?, he covers the whole product, as you might expect.  You'll want to read it.

    One of the things he mentions is something new, tfpt.exe.  That is a power toy that provides some very useful features that didn't make it into the regular product.  We've been using it internally now for a little while.  I'll post more about it when it is available in the next couple of weeks.

    • merging changes in a shelveset with changes in you workspace
    • rolling back a changeset
    • checking out all writable files in your workspace that aren't checked out
    • a dialog focused on code reviews
    • undoing unchanged files
    • getting just the files in a particular changeset
Page 1 of 1 (13 items)