Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Visual Source Safe 6.0 Recursive Rollback

0.00/5 (No votes)
3 Oct 2005 2  
Covering the basics of VSS automation using the SourceSafe API (SourceSafeTypeLib) and the SS.exe command line utility. The sample shows rolling back an entire project hierarchy.

Contents

Introduction

Background

What is this article about?

Very simply, this article covers the basics of how to roll back an entire project hierarchy to a particular label. This is accomplished by using a combination of the SourceSafe API (SourceSafeTypeLib) and the SS.exe command line utility.

Why did I write this article?

I have been programming for a short 4 years. Over the last 1/2 year, I have "discovered" CodeProject and the wonderful resources this site has to offer. This is my first CodeProject article, written with the intention of:

  1. learning to write articles on CP;
  2. passing some information back to this community that has given me help;
  3. offer some insight on a task in Visual Source Safe that doesn't seem to have a lot of help around it.

I was trying to upgrade my version of Microsoft Pattern and Practices team's Enterprise Library code. I had the January release and had made modifications to that; I then wanted to merge in the June release, keeping my changes. Of course, I did it wrong the first time, and wanted to roll the entire project back to a label (which I had thankfully thought to put on before I tried to merge).

I researched on Google and found that what I wanted to do could not be done, at least not in the way I expected to do it. You can use the command line tool (described later in this article) to roll back all files in a project; however, you could not recurse a project tree. And if any of you have seen Enterprise Library, there are about 4-6 levels in the hierarchy. There are about 38 projects or so. Yikes!

So, as any developer would, I decided to come up with my own solution, and pass that on to you. Here are a couple of ways you may find this article useful.

Why is this useful to you?

Recursive rollbacks are not supported by the VSS Explorer

In the Visual Source Safe Explorer, you cannot roll back a project or a project tree (i.e. recurse). You are forced to roll back file-by-file, which gets tedious even in small projects. My sample code can be used as a lightweight tool that will help you to automatically recurse through a project tree and roll back all the files.

It is important to note that behind-the-scenes we are still rolling back file-by-file; however, we are automating this by looking at the project tree in VSS, so you do not have to roll back each project or file yourself.

Automation tutorial

This article also serves as a very basic VSS automation and SS.exe command line tutorial. There are actually quite a few things you can do with VSS automation (that are not covered in this article) such as:

  • Automated builds
  • Automated labels
  • Advanced reporting
  • Automated change logs or email notification

See the References section at the bottom of this article for some handy articles that discuss the many uses of SourceSafe automation.

My personal disclaimer

I am trying to pass on what I have learned, but I have only been programming for a short time. It only stands to reason that there are bigger and better coding practices out there that I do not follow, or technology that I am not aware of.

I encourage you to use the comments section at the bottom of the article to ask questions, and to let me know about ways in which I can improve this article.

Disclaimer for this program - Caution!!

This tool will roll back versions of your SourceSafe files to a certain label or version number. To my knowledge, there is no way to recover these files once you have rolled back. I strongly recommend that you back up your SourceSafe database before using this or any tool that interacts with SS. The author will take no responsibility for lost or destroyed files, or any loss of business resulting from the loss of said files. By downloading the sample project, you agree to abide by these terms.

Please look at this code first; it would be better if you first try this on a test SourceSafe database. I do not provide the executable as a download, as a small precaution that will encourage you to open the project (I hope).

Prerequisites

This article assumes you have Visual SourceSafe 6.0 and Visual Studio .NET 2003. (I have not tried this with VS 2002). I am running this code on a Windows XP Professional machine with service pack 1, and have not tested with other platforms. I would appreciate any feedback from anyone using VS 2002 or running the code on other platforms.

The two files we will be working with are SS.exe and SSAPI.dll. If you have installed SourceSafe using the default settings, they should both be located in this directory:

C:\Program Files\Microsoft Visual Studio\VSS\win32

The sample code

Below is an explanation of the business problem, some approaches I took to solve it, and some explanation of the actual code. To see how to use this program from a UI perspective, see the section "Using the Sample Program".

The problem

As I mentioned in the introduction above, I was trying to upgrade my version of Microsoft Pattern and Practices team's Enterprise Library code. I had downloaded the January 2005 release, added the project to SourceSafe, and then made some minor modifications.

In June, they announced a new release which fixed some bugs and which also had some support for Visual Studio 2005. I wanted to merge this new release into my old changed release, and frankly I didn't really know a "good" way to do it. So, taking a precaution, I put a label on my updated January 2005 version that was in SourceSafe.

Good thing I did.

Don't ask me what I did wrong (how could someone mess up copying and pasting files?) but I had the distinct impression that I had messed up my projects. No problem, I thought: I had just put a label on the old, good, version; I'll just completely roll back to that label, and start over. That should be quick. [Insert evil chuckle here]

I went in to SourceSafe Explorer, and immediately realized I could not roll back a whole project to a label or version. I could only roll back file-by-file. So I went looking on Google and finally came to an article which mentioned the following:

While the rollback command does not have a recursive option, you can at least use it to specify all files in a given project. Therefore, you'd have to walk through each project in your database yourself. Here's an example:

ss rollback $/proj1/* -vlbeforechange -i-y

I found out that this person was referring to a command line tool, SS.exe, which I already had in the SourceSafe directory. The problem was - well take a look at this project hierarchy:

Each one of those folders you see has several levels such as the expanded Caching/Configuration project shown. If I wanted to roll back the entire project hierarchy using this methodology, I would have to run it for each folder.

Theories and solution

Theory #1: Use SS.exe command line util

Simply put, theory # 1 was to use the command line tool to rollback * files within each project, like was mentioned in the post above:

[Editor comment: Line breaks used to avoid scrolling.]

ss rollback $/MILB/EnterpriseLibrary/Caching/* 
                                 -vlMyLabel -i-y
ss rollback $/MILB/EnterpriseLibrary/Caching/Configuration/* 
                                             -vlMyLabel -i-y
ss rollback $/MILB/EnterpriseLibrary/Caching/Configuration/Design/* 
                                                    -vlMyLabel -i-y

REM ** where "MILB" actually reads 
   "Microsoft Enterprise Library Application Blocks 1.0"

I figured I could just use the System.IO.DirectoryInfo object to get a starting directory on my file system, and then use directoryInfoObject.GetDirectories() in a recursive manner to get all of the sub-folders. The problem with this is that I was relying on the Working Directory to contain everything that was in the SourceSafe tree. In other words, I would be using my file system directory structure to get the hierarchy, and not the actual SourceSafe project structure.

I then started to research if there was a way to get the hierarchy right out of SourceSafe using the command line utility. As I was searching, CodeProject's Marc Clifton turned me on to an article about the SourceSafe API.

Theory #2: Use SSAPI.dll

I figured, now, that I'd just use the SourceSafe API and trash the command line utility. I could get all of the projects and files using the API. All I had to do was recurse through all of the files, and roll back each file individually, right?

I started happily coding down this path, and when I got all the way through setting up my recursive function, checking for errors, setting up an acceptable UI, and setting up confirmations for the user, I then went to code the next line to do the actual rollback: I was thinking it would be something like vssItemObject.Rollback("LabelName");. The VssItemObject has the following methods:

  • Add
  • Branch
  • Checkin
  • Checkout
  • Destroy
  • Get
  • Label
  • Move
  • Share
  • UndoCheckout

But - you guessed it - no Rollback method.

Theory #3 / Solution: Use both SSAPI.dll and SS.exe

After pounding my head on the desk for a few minutes, I decided to take a bit of both worlds: I could use the API to get the projects and files, and I could use the command line utility to actually perform the rollback. In the next section, I will very briefly discuss some of the code I ended up using in my "final" (beta?) version that I've included here.

Explaining the code

We could have a whole discussion about my commenting style, but without diving into that, here are some short explanations of the key classes and methods.

Enums and helper classes

public enum RollbackConfirmOptions
{
    ConfirmOnce,
    ConfirmAllFiles,
    DoNotConfirm
}

This enum gives the user the option to have a confirmation dialog just once (Default), a dialog for each and every rollback, or (not recommended) none at all.

public enum RollbackToType
{
    RollbackToLabel,
    RollbackToVersion
}

Perhaps better described as something like RollbackToType, this enum gives the user the option to roll back to a Label, or to roll back to a version number.

public class VSSOptions
{
    protected string selectedProject;
    public string SelectedProject
    {
        get { return selectedProject; }
        set { selectedProject = value; }
    }

    protected bool recurse;
    public bool Recurse
    {
        get { return recurse; }
        set { recurse = value; }
    }

    protected RollbackToType rollbackType;
    public RollbackToType RollbackType
    {
        get { return rollbackType; }
        set { rollbackType = value; }
    }

    protected RollbackConfirmOptions confirmOption;
    public RollbackConfirmOptions ConfirmOption
    {
        get { return confirmOption; }
        set { confirmOption = value; }
    }

    protected string rollbackCriteria;
    public string RollbackCriteria
    {
        get { return rollbackCriteria; }
        set { rollbackCriteria = value; }
    }

    protected bool getLatest;
    public bool GetLatest
    {
        get { return getLatest; }
        set { getLatest = value; }
    }

    public VSSOptions()
    {
    }
}

This class holds the options set when rolling back the files.

  • SelectedProject - The VSS project or file to roll back.
  • Recurse - true if you want to recurse every file in the selected project's hierarchy.
  • RollbackType - Roll back to a label or to a version number.
  • ConfirmOption - Confirm rollback once only, every time, or not at all.
  • RollbackCriteria - The actual label or version number to roll back to. If you wish to roll back to label 'My_Label', enter My_Label without quotes. If you wish to roll back to each file's version 4, enter 4.
  • GetLatest - true (default) if you want to replace your working folder's copy of the file after you roll the file back.

We could have used a struct here, but that would force you to set each and every property when instantiating a new VSSOptions object, which looks a bit messy in code. Code maintainability won out over the miniscule (if any?) performance increase. We've already lost tons of performance on building the project TreeView, which you'll see later in this section.

On another note, I originally had all of the properties in this class listed as public variables. I was advised not to do that, and to always use public properties, for the following reasons:

  • Public properties are a part of what is required to support data binding.
  • Extensibility
  • Supports declarative instantiation
  • Promotes encapsulation
  • Avoids entanglement and dependencies

I don't pretend to understand all of this, but I had never heard good arguments before against using public variables in place of "simple" getters and setters.

OpenSourceSafeDatabase method

This method is responsible for picking a srcsafe.ini file (which represents a SourceSafe database), gathering login credentials, opening the actual database, and populating the TreeView you see on the right-hand side of the UI.

private void OpenSourceSafeDatabase()
{
    ///////////////////////////

    // First get the file name

    SetOpenFileDirectory();
    if (openFile.ShowDialog() == DialogResult.Cancel || 
                          !File.Exists(openFile.FileName))
    {
        // Could check here to see 

        // if it's a srcsafe.ini file

        // rtbMessages.Text = "Please select a 

        //               VSS database file (srcsafe.ini).";

        return;
    }

The SetOpenFileDirectory() method is a helper method which searches a registry key to see if you have opened up a SourceSafe database before. If you have, the openFile component's InitialDirectory property is set to that path. If you have not, we try to default to C:\Program Files\Microsoft Visual Studio\VSS. Finally, if all else fails, default to the Program Files directory.

The Open File dialog is then opened, and if the user cancels or enters a blank file name, we simply stop and don't do anything.

    //////////////////////

    // Next, Get the login information for the database

    frmLogon loginForm = new frmLogon();
    if (loginForm.ShowDialog(this) != DialogResult.Cancel)
    {

frmLogon is a very simple form which collects the credentials for the SourceSafe database. It exposes two public properties which retrieve what the user types in.

////////////////////////

// Finally, create the database object and log in

Cursor = Cursors.WaitCursor;
try 
{
    vssDB = new VSSDatabaseClass();
    vssDB.Open(openFile.FileName, 
          loginForm.UserName, loginForm.Password);

    // Update display

    sbPnlDatabase.Text = "Logged In";
    sbPnlMessages.Text = 
      "Current Project: '" + vssDB.CurrentProject + "'";
    rtbMessages.AppendText("Successfully opened " + 
              "Database '" + openFile.FileName + "'\n");

    // Set the Registry Key for the database location

    Microsoft.Win32.RegistryKey key =
        Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
                                      REGISTRY_KEY, true);
    if (key == null)
    {
      key = 
        Microsoft.Win32.Registry.CurrentUser.CreateSubKey(
                                              REGISTRY_KEY);
    }

    key.SetValue("LastDB", openFile.FileName);

    // Determine whether to load the TreeView

    if (Convert.ToBoolean(key.GetValue("LoadTreeview", true)) == 
                                                             true)
    {
        BuildTreeView();
    }
    else 
    {
        pnlTreeview.Visible = false;
    }

    pnlRecurse.Visible = true;
}

The VSSDatabaseClass is the key class here from the SourceSafe API that allows you to work with the database. We provide the credentials and open the database, and then we store the database you just opened so that we can try to open the same file next time.

BuildTreeView() populates the TreeView control on the right of the UI with the hierarchy of the database you just opened. We will discuss that function shortly. When the TreeView is properly populated, we show the panel that allows the user to actually run the rollback.

        catch(System.Runtime.InteropServices.COMException comex)
        {
            // error -2147166526 = User Name Not Found

            // error -2147352566 = Bad Password


            pnlRecurse.Visible = false;

            if (comex.ErrorCode == -2147166526 || 
                       comex.ErrorCode == -2147352566)
            {
                rtbMessages.Text = 
                   "User Name or Password is incorrect.";
            }
            else
            {
                rtbMessages.Text = 
                  "Unknown VSS COM Exception:\n\n" +  
                                        comex.ToString();
            }
        }
        catch(Exception ex)
        {
            pnlRecurse.Visible = false;

            rtbMessages.Text = 
                 "System Exception:\n\n" + ex.ToString();

            sbPnlDatabase.Text = "Not Logged In";
            sbPnlMessages.Text = "No Project Loaded";
        }
        finally
        {
            Cursor = Cursors.Default;
        }
    }
}

Finally, we catch exceptions. We trap specifically for two particular exceptions, "User Name Not Found" and "Bad Password", because that is a bit of a bad practice - we do not want to give the user that information. We can also trap here for any other error thrown specifically by the SourceSafe API, and then we trap for any other exception resulting from our code.

In any exception case, we don't show the main panel, and we update the status bar showing that we're not logged in.

BuildTreeView method

The intention of the BuildTreeView method is to populate the TreeView control, on the right hand side of the UI, with the hierarchy of the database just opened. We have to show folders (projects) first and then files, so I have chosen to build a data table with all of the information necessary for building and sorting the tree.

We use the vssDB.get_VSSItem method to retrieve the Root item of the database, and then the vssProj.get_Items method to retrieve all files and projects of the Root. Using the BuildNode helper method, all levels of projects are traversed recursively and added to the data table, which has been passed in By Ref. Finally, we sort the data table, cycle through the sorted rows, and add nodes to the TreeView accordingly.

The problem I've run in to is that this method seems to take far longer than expected with a database of any significant size. I have tried a methodology where I did not return a data table and instead returned nested TreeNodes directly; however, taking this approach, I'd have to cycle through the VSSItem twice, once looking for projects, and a second time looking for files. It seems that the vssProj.get_Items method itself is very slow.

Because this hierarchy takes so long to build, I have given the user the option to "turn off" the TreeView feature. See the Using the sample program section below.

RecursiveRollback and RollbackNode methods

When the user clicks the cmdRollback button, we first set up our VSSOptions object with their choices, such as label name or version number, and whether to get the latest version after rollback. After the options are set, the RecursiveRollback method is called, which does the actual work, and returns a string which can be displayed in the message box. This string will hold error messages, for instance in a case where a file could not be rolled back.

The first step, after confirming the rollback to the user, is to get the project that is in the TextBox from the user having typed it in, or from the user having selected the project from the TreeView.

    // Get the project as listed in TextBox

    VSSItem vssProj = 
       vssDB.get_VSSItem(options.SelectedProject, false);

We set up a global Boolean variable that will catch if the user wants to cancel the rollback, and we call the helper method RollbackNode which recursively performs the rollback.

private string RollbackNode(VSSItem item, VSSOptions options)
{
    if (stopRollback)
    {
        return "";
    }

    StringBuilder sb = new StringBuilder();
    
    switch(item.Type)
    {

The method checks to see if rollback is cancelled, and then sets up a StringBuilder variable to hold the current iteration's error messages. The type of the VSSItem is checked, to see if we are working with a project or a file.

        case 0:        
            // This is a project. If no recurse, stop here

            if (!options.Recurse)
            {
                return "";
            }

            // Get all children and recurse

            IVSSItems childItems = item.get_Items(false);
            if (childItems != null && childItems.Count > 0)
            {
                foreach(VSSItem childItem in childItems)
                {
                    sb.Append(RollbackNode(childItem, options));
                }
            }
            break;

If the item type is 0, then the object we are on is a folder/project. We take no action on the project itself other than to recurse through all of its children. If the user has specified that they do not want to recurse, we simply return an empty string. (Not recursing pretty much defeats the purpose of the project, but the option is there anyway.)

        case 1:        
            // This is a file. Confirm (if required) 

            // and roll back file

            DialogResult result = DialogResult.Yes;

            string fullPath = item.Spec;
            string ver = options.RollbackCriteria;
            bool isLabel = options.RollbackType == 
                           RollbackToType.RollbackToLabel;

            if (options.ConfirmOption == 
                   RollbackConfirmOptions.ConfirmAllFiles)
            {
                // Construct a confirmation question.

                string msg = String.Format(
                    "Confirm rollback of '{0}' to {1} '{2}'?\n",
                    fullPath, isLabel ? "label" : "version", ver);
                msg += "\n'Yes' will roll back the file, " + 
                                     "and rollback will continue";
                msg += "\n'No' will not roll back the file, " + 
                                     "and rollback will continue";
                msg += "\n'Cancel' stops the rollback";
                result = MessageBox.Show(msg, "Confirm File", 
                                  MessageBoxButtons.YesNoCancel,
                                  MessageBoxIcon.Question);
            }

            if (result == DialogResult.Cancel)
            {
                stopRollback = true;
            }
            else if (result == DialogResult.Yes)
            {

If the user has specified that they want confirmation on each and every file, we construct a confirmation dialog for the current file. The user can choose to roll the file back or not to roll the file back, or to cancel completely out of the rollback operation. It is important to note that any rollbacks already performed will remain in effect.

[Editor comment: Line breaks used to avoid scrolling.]

                if (options.RollbackType == 
                               RollbackToType.RollbackToLabel)
                {
                    ver = "L" + ver;
                }    

                System.Diagnostics.ProcessStartInfo pInfo = 
                    new System.Diagnostics.ProcessStartInfo();

                // TODO: Make dynamic, search for this 

                // or ask for this setting, etc.

                pInfo.FileName = "C:\\Program Files\\
                         Microsoft Visual Studio\\VSS\\win32\\SS.exe";
                pInfo.UseShellExecute = false;
                pInfo.RedirectStandardError = true;
                pInfo.CreateNoWindow = true;

                // If there are any spaces we have to 

                // surround version switch with quotes

                // Also, add -G- switch to NOT 

                // get the latest version

                if (ver.IndexOf(" ") > -1)
                {
                    pInfo.Arguments = String.Format(
                      "rollback \"{0}\" \"-V{1}\"{2} -I-Y\n", 
                      fullPath, ver, options.GetLatest ? "" : " -G-");
                }
                else
                {
                    pInfo.Arguments = String.Format(
                        "rollback \"{0}\" -V{1}{2} -I-Y\n", fullPath, 
                        ver, options.GetLatest ? "" : " -G-");
                }

                System.Diagnostics.Process proc = 
                      System.Diagnostics.Process.Start(pInfo);

                string err = proc.StandardError.ReadToEnd();
                if (err != "" && !err.StartsWith(
                    "Rollback cannot be undone; some versions" + 
                    "will be lost irretrievably!"))
                {
                    sb.Append(err + "\n");
                }
            }
            break;
    }

    return sb.ToString();
}

A command line string is constructed to run the SS.exe program, giving it the proper settings to roll back the file with the user's options. If the rollback type is "Label", a "V" and an "L" must be prepended to the argument whereas if it is a version number, just the "V" gets prepended. Also, spaces in the label name must be accounted for.

For example, to roll back MyFile.cs to the version labeled "Release31" and get the latest version of the file to its working directory, the command line would look like this:

rollback "MyFile.cs" -VLRelease31 -I-Y

To roll back MyFile.cs to the version number 5 and not get the latest version of the file to its working directory, the command line would look like this:

rollback "MyFile.cs" -V5 -G- -I-Y

Finally, to roll back MyFile.cs to the version labeled "Release Version 2.5" and get the latest version of the file to its working directory, the command line would look like this:

rollback "MyFile.cs" "-VLRelease Version 2.5" -I-Y

You will notice that when a label has spaces, we put quotes around the entire argument including the switch indicator (-).

Lastly in the RollbackNode method, we capture (and ignore) extra warnings thrown by the process, and return any other error string returned from the Process object.

Using the sample program

The UI is pretty self-explanatory, but here are some tips:

To perform a rollback on a SourceSafe database

  1. Open an existing SourceSafe database by clicking on the Open icon or clicking File -> Open SS Database. Navigate to the srcsafe.ini file and click Open.
  2. Enter your user name and password, and click OK.
  3. Click on the project in the TreeView on the right-hand side, or type in the project path directly into the text box.
  4. Set your options:
    • To recurse a project tree (default), check the checkbox marked Recurse Tree.
    • Type in the label name or version number in the Roll Back To text box.
    • To roll back to a labeled version (default), click the radio button marked Label. To roll back to a version number, click the radio button marked Version Number.
    • To confirm the rollback operation once in the beginning of the rollback, select Confirm Once in the Confirmation Mode dropdown. To confirm every file before rolling back, select Confirm Every File. To forgo confirmation (silent rollback) [not recommended], select Do Not Confirm.
    • To get the latest version of a file into the working directory of the project after rollback (default), check the checkbox marked Get Latest Version.
  5. Press the Roll Back button when you are ready to roll back your files.
  6. Results of the rollback appear in the text box at the bottom of the screen.

To turn off the TreeView (for large databases)

  • Select Tools -> Options on the file menu. Check or uncheck the option Build Treeview after loading Database.
  • The TreeView will not appear the next time a database is opened if the option is unchecked.
  • To manually build and show the TreeView when the option is unchecked, select View -> Rebuild Treeview from the file menu.
  • Your settings are saved - next time the application is run, the Build Treeview after loading Database option will remain how you had left it when you last exited the application.

Conclusion

In summary, we end up with a rather slow and clunky program, but a program that can recursively roll back a project hierarchy using the SourceSafe project structure directly.

While I wouldn't recommend using this for simple rollbacks, we also see by looking at the API and command line utilities that there are other possibilities for exploring advanced capabilities when the SourceSafe Explorer does not meet our needs. Automated change log creation, based on labels or comments, for example, might be a good application of the API. You could create automated bug-fix logs, you could link SourceSafe checkin versions to bug tickets; the possibilities are certainly there.

I'd be interested in finding out if the next version of SourceSafe and/or Team System makes source control any easier. Do these systems address some of the problems we've covered here? Do they provide any advanced features or functionality that we don't see today in SourceSafe 6.0?

Known issues

The following is a list of issues or problems that exist in this tool:

  • It is very slow to traverse through the hierarchy and build a TreeView with larger SourceSafe databases.
  • If labels exist for dates later than the label you are rolling back to, those later labels do not get removed. For example if we had a history (with the oldest being listed first) such as this:
    Version 1
    Label "A"
    Version 2
    Version 3
    Label "B"
    Version 4
    Label "C"

    If we were to roll back to Label "B", the version history in SourceSafe now looks like this (but your file versions would only go up to 3):

    Version 1
    Label "A"
    Version 2
    Version 3
    Label "B"
    Label "C"

I would appreciate any suggestions you may have on tackling these issues.

References and links

Revision history

  • 9/26/2005 - First draft published to review site (not CP).
  • 9/29/2005 - Second draft published to review site (not CP).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here