Click here to Skip to main content
Click here to Skip to main content
Go to top

SSBrowser: A Sample Visual SourceSafe Automation

, 3 Jun 2004
Rate this:
Please Sign up or sign in to vote.
A sample VC++ program for performing Visual SourceSafe operations in your application.

Sample Image - SSBrowser.jpg

Introduction

A few years ago, I had a project to automate our team's build process which involves several SourceSafe operations. I searched in vain for good sample programs but none were comprehensive enough; and most guides and samples that I could find were in Visual Basic. This sample project is therefore some kind of repository of VC++ codes of how to interface with SourceSafe operations, namely:

  • Connecting to SourceSafe database
  • Operations such as CheckIn, CheckOut, Undo Checkout, Label, Add projects, files etc.
  • Traversing the SourceSafe repository and representing it in a tree control
  • Getting a file history list and retrieving the version from it

I have recently seen a lot more samples and articles about SourceSafe here in CodeProject, but I'll share this article anyway. Who knows, it may help others as well.

Caveat

The sample program provided is ONLY a demonstration of what you can do with SourceSafe automation. Error checking was grossly omitted for brevity. The author is not responsible for any loss of data resulting from running the program or using the codes in the sample program.

Points of Interest

As a sample application article, I believe the source code will speak for itself. I will however just highlight several aspects of SourceSafe automation in Visual C++.

Interfacing with Visual SourceSafe

There are two ways to interface with VSS.

1. Type Library - Import via SSAPI.DLL

This is done by generating and using ssapi.tlh and ssapi.tli. It has the advantage of having the smart pointers already declared and wrapper methods for error handling (throws COM error HRESULT, is non S_OK). However, this method is less portable because it needs the path to the SSAPI.dll. For example: this could be in your stdafx.h.

///////////////////////////////////////////////////////////
// Please edit according to your system's VSS installation
//
#import "C:\Program Files\DevStudio\Vss\win32\ssapi.dll" no_namespace
///////////////////////////////////////////////////////////

Creating a SS database connection is as follows:

// Visual SourceSafe Database
IVSSDatabasePtr mp_vssDatabase;

mp_vssDatabase.CreateInstance(__uuidof(VSSDatabase);
mp_vssDatabase->Open(psz_VSSini, psz_User, psz_Password);

Note the differences when using the ssauto.h.

2. Raw COM interface - Microsoft provided ssauto.h

The file is available from the MSDN site. The header contains only the raw COM interface class and defined constants. If you want to use smart pointers, you'll have to define them yourself. The attached sample project does exactly that. I defined them in the stdafx.h as follows.

// Smart pointer typedef declarations
//
#include <span class="code-string">"ssauto.h"
</span>

extern "C" const GUID __declspec(selectany) LIBID_SourceSafeTypeLib =
    {0x783cd4e0,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSItem =
    {0x783cd4e1,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSVersions =
    {0x783cd4e7,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSVersion =
    {0x783cd4e8,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSItems =
    {0x783cd4e5,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSCheckouts =
    {0x8903a770,0xf55f,0x11cf,{0x92,0x27,0x00,0xaa,0x00,0xa1,0xeb,0x95}};
extern "C" const GUID __declspec(selectany) IID_IVSSCheckout =
    {0x783cd4e6,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSDatabase =
    {0x783cd4e2,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) CLSID_VSSItem =
    {0x783cd4e3,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) CLSID_VSSVersion =
    {0x783cd4ec,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) CLSID_VSSDatabase =
    {0x783cd4e4,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSEvents =
    {0x783cd4e9,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSS =
    {0x783cd4eb,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};
extern "C" const GUID __declspec(selectany) IID_IVSSEventHandler =
    {0x783cd4ea,0x9d54,0x11cf,{0xb8,0xee,0x00,0x60,0x8c,0xc9,0xa7,0x1f}};


_COM_SMARTPTR_TYPEDEF(IVSSItem, IID_IVSSItem);
_COM_SMARTPTR_TYPEDEF(IVSSVersions, IID_IVSSVersions);
_COM_SMARTPTR_TYPEDEF(IVSSVersion, IID_IVSSVersion);
_COM_SMARTPTR_TYPEDEF(IVSSItems, IID_IVSSItems);
_COM_SMARTPTR_TYPEDEF(IVSSCheckouts, IID_IVSSCheckouts);
_COM_SMARTPTR_TYPEDEF(IVSSCheckout, IID_IVSSCheckout);
_COM_SMARTPTR_TYPEDEF(IVSSDatabase, IID_IVSSDatabase);
_COM_SMARTPTR_TYPEDEF(IVSSEvents,IID_IVSSEvents);
_COM_SMARTPTR_TYPEDEF(IVSS, IID_IVSS);
_COM_SMARTPTR_TYPEDEF(IVSSEventHandler, IID_IVSSEventHandler);

Creating a SS database connection is as follows:

// Visual SourceSafe Database
IVSSDatabasePtr mp_vssDatabase;

CLSIDFromProgID(L"SourceSafe", &clsid ));
CoGetClassObject( clsid, CLSCTX_ALL, NULL, IID_IClassFactory, (void**)&pClf );
pClf->CreateInstance( NULL, IID_IVSSDatabase, (void **) &mp_vssDatabase );

mp_vssDatabase->Open((CComBSTR)psz_VSSini, 
  (CComBSTR)psz_User, (CComBSTR)psz_Password);

Note the differences when using the ssapi.dll.

Traversing the Visual SourceSafe Database

The sample has a tree control (CSSTreeCtrl) which provides a good example of navigating the database. The tree loads up fast because, when the tree is initialized, it only populates the nodes which are directly connected to the path when traversing from root to the default project (taken from VSSDatabase::get_CurrentProject method). Other nodes are opened when the user actually opens them up by clicking on the tree control graphics.

The h_InsertPath method below shows how to recursively load the partial structure into the tree control. The deepestNodeFound item can be used later to select/expand the tree.

/*****************************************************************
h_InsertPath - Insert a tree structure that represents
               the specified project path.

PARAMETERS:
    IVSSItemPtr vss_item - the sourcesafe item where to start traversing 
    HTREEITEM h_parent - the tree node where to insert 
                         the new tree structure (NULL for tree root)
    CString & rstr_traverseToPath - the path to the project to traverse
    HTREEITEM & rh_deepestNodeFound - (output) the deepest 
                                      node found after traversing the path

RETURNS:
    HTREEITEM - the tree item attached into the specified parent
******************************************************************/
HTREEITEM CSSTreeCtrl::h_InsertPath(IVSSItemPtr vss_item, HTREEITEM h_parent, 
    CString & rstr_traverseToPath, HTREEITEM & rh_deepestNodeFound)
{
    HTREEITEM h_item = NULL;
    try
    {
        CComBSTR str_name;
        v_TestHr(vss_item->get_Name(&str_name));
        CString str_Filename = static_cast<LPCTSTR>(str_name);
        if (h_parent == NULL)
        {
            str_Filename = gsz_root;
        }

        //Add the item into the tree node
        TV_INSERTSTRUCT tvis;
        ZeroMemory(&tvis, sizeof(TV_INSERTSTRUCT));
        tvis.hParent = h_parent;
        tvis.hInsertAfter = TVI_LAST;
        tvis.item.mask = TVIF_CHILDREN | TVIF_IMAGE | 
               TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_PARAM;

        tvis.item.lParam = NULL;
        tvis.item.pszText = str_Filename.GetBuffer(str_Filename.GetLength());

        IVSSItemsPtr vss_items;
        int i_type;
        long l_count, l_checkOut;
        vss_item->get_Type(&i_type);
        vss_item->get_IsCheckedOut(&l_checkOut);
        if (i_type == VSSITEM_FILE)
        {
            // Set image according to check out state
            if (l_checkOut == VSSFILE_NOTCHECKEDOUT)
            {
                tvis.item.iImage = ITEM_FILE_NORMAL_IMAGE;
                tvis.item.iSelectedImage = ITEM_FILE_NORMAL_IMAGE;
            }
            else
            {
                tvis.item.iImage = ITEM_FILE_CHECKOUT_IMAGE;
                tvis.item.iSelectedImage = ITEM_FILE_CHECKOUT_IMAGE;
            }
        }
        else if (i_type == VSSITEM_PROJECT)
        {
            vss_item->get_Items(_variant_t(false), &vss_items);
            tvis.item.iImage = ITEM_PROJECT_NORMAL_IMAGE;
            tvis.item.iSelectedImage = ITEM_PROJECT_SELECTED_IMAGE;
            vss_items->get_Count(&l_count);
            // Add (+) sign to indicate the node has child
            tvis.item.cChildren = (l_count > 0) ? 1:0;
        }
        h_item = InsertItem(&tvis);

        // If this node is within the path, consider it as the deepest node
        CString str_currentPath = str_ItemToPath(h_item);
        ATLTRACE(_T("%s\n"),str_currentPath);
        if (rstr_traverseToPath == str_currentPath)
        {
            rh_deepestNodeFound = h_item;
        }

        // If this node is not within the path, we're done
        if (h_parent && rstr_traverseToPath.Find(str_currentPath) != 0)
        {
            return h_item;
        }
        // Otherwise, recursively populate the node path
        if (i_type == VSSITEM_PROJECT)
        {
            for (long i = 0; i < l_count; i++)
            {
                IVSSItemPtr vss_ChildItem;
                vss_items->get_Item(_variant_t(i+1L), &vss_ChildItem);
                h_InsertPath(vss_ChildItem, h_item, 
                       rstr_traverseToPath, rh_deepestNodeFound);
            }
        }
    }
    catch (_com_error &e)
    {
        ATLTRACE(_T("COM error: %s\n"), e.ErrorMessage());
    }

    return h_item;
}

For the rest of the code, please refer to the CSSTreeCtrl source code in the sample project.

Retrieving a SourceSafe Item History

The history of an item is accessible via IVSSVersions which provides an interface to IEnum from which we could iterate each history item.

void v_DumpItemHistory(IVSSItemPtr vss_item)
{
    int i_type = 0;
    IVSSVersionsPtr versions;

    vss_item->get_Type(&i_type);
    // Get the IVSSVersions object of the item
    if (vss_item->get_Versions((i_type == VSSITEM_PROJECT)? 
             VSSFLAG_HISTIGNOREFILES:0, &versions) == S_OK)
    {
        CComPtr<IENUMVARIANT> p_versionsEnum;
        CComPtr<IUNKNOWN> spunk;

        versions->_NewEnum(&spunk);
        spunk->QueryInterface(IID_IEnumVARIANT, (LPVOID*)&p_versionsEnum);

        ULONG l_fetched;
        long l_value;
        CString str_display;
        CComBSTR bstr_text;
        DATE date;

        // We use this object to point to each version
        _variant_t object;

        // Now iterate through all the versions
        while (p_versionsEnum->Next(1, &object, &l_fetched) == S_OK)
        {
            try
            {
                // Treat the object as IVSSVersion
                IVSSVersionPtr p_version(object);

                // ...where we can get the version number
                p_version->get_VersionNumber(&l_value);
                // the user who did the changes
                p_version->get_Username(&bstr_text);
                ATLTRACE(_T("Version %d by %s"), l_value, bstr_text); 
                // the date it was checked in
                p_version->get_Date(&date);
                // the action done
                p_version->get_Action(&bstr_text);
                ATLTRACE(_T(" On %s %s\n"), 
                             str_FormatDateTime(date), bstr_text);
            }
            catch (_com_error & e)
            {
                ATLTRACE(_T("%s\n"), e.ErrorMessage());
            }
        }
    }
}

A more comprehensive example is in the sample project's CMainDialog::OnLoadHistory().

Downloading an Old Version of an Item

You can retrieve an old version as long as you have the version number. The old version of an item is also represented as a VSSItem object where you can call Get() method to download into the working directory. Note that you cannot checkout an old version.

IVSSItemPtr vss_oldItem;
if (vss_item->get_Version(_variant_t(l_oldVersionNumber), 
                                         &vss_oldItem) == S_OK)
{
    // Download to the working directory
    CComBSTR bstr_localSpec;
    vss_item->get_LocalSpec(&bstr_localSpec);
    vss_oldItem->Get(&bstr_localSpec, VSSFLAG_REPREPLACE);
}

The Sample Application

I highlight the sample application itself because it is also a good example of:

  • WTL Application
  • Subclassing tree control in WTL
  • Message reflection
  • Resizing dialog

Other Resources

Other CodeProject articles about SourceSafe.

Here are links to references I used during my "struggle":

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

Share

About the Author

Ferdie
Software Developer (Senior)
United States United States
Roaming halfway around the globe programming in C++, MFC, COM/ATL, WTL, C#, .NET, OLEDB, ADO, ADO/X.
 
Living under the pleasant weather of Irvine, California, Ferdie is a Computer Engineering graduate of Mapua Institute of Technology (MIT Smile | :) ) in Philippines. Developed GIS applications in Japan for 5 years. Now a member of a team developing Windows GUI and real time software for semi-robotic equipments.

Comments and Discussions

 
GeneralThx. PinmemberMuaddubby7-Sep-08 9:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 4 Jun 2004
Article Copyright 2004 by Ferdie
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid