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.