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

VSS: protocol handler for Visual SourceSafe

0.00/5 (No votes)
27 Jun 2001 1  
This article describes how to hook up a protocol, in the example "vss:", to a custom handler to open a document from a Visual SourceSafe repository using automation.

Sample Image - vssprotocolhandler.gif

Introduction

Imagine that you could open a document in a Visual SourceSafe database with one mouse click.

No more opening the SourceSafe client, browsing to the correct project tree and choosing 'view latest version' from the document's context menu. In the screenshot above, you see three links to project documentation in a Word document; the links have the new VSS: protocol prefix. Clicking a VSS: link will fire up the handler presented below and open the designated document using shell association (.doc -> Word). In other words: "VSS://$/Projects/VSS ProtHandler/TD-VSSProtocolHandler.doc" will start Visual SourceSafe, find the TD-VSSProtocolHandler.doc in project "$/Projects/VSS ProtHandler" and attempt to open it using "shellexec". This is all done using Automation, you won't see the SourceSafe client at all!

You are of course familiar with the use of HTTP:, FTP: and other protocols. The VSS: protocol can be used in almost the same manner in a (intranet) webpage, on the Windows Run box, in a Word document or in a shortcut. All protocol requests are handled by URL.dll (a DLL installed by Internet Explorer). URL.dll will attempt to find a registration for the protocol and call the registered handler. For HTTP: it is most likely Internet Explorer, for VSS: it is VSSProtocolHandler.exe.

Please note that the user must have the SourceSafe client installed (to use its automation objects) and his/her machine must be able to reach the VSS database.

This article presents two principles:

  • Registering & hooking up the "VSS:" protocol to the handler VSSProtocolHandler.exe,
  • Handling the protocol request: letting the handler open the indicated document on the protocol line from the Visual SourceSafe database, using automation.

Registering & hooking the "VSS:" protocol

Actually, the "VSS:" protocol is similar to the "MAILTO:" protocol. Researching this protocol is how I found out how to hook the protocol! A few lines in the registry and the protocol is registered.

[HKEY_CLASSES_ROOT]
  [vss]
    (Default) = "URL:VSS Protocol"
    URL Protocol = ""
    [DefaultIcon]
      (Default) = "VSSProtocolHandler.exe"
    [shell]
      [open]
        [command]
          (Default) = "c:\whatever\VSSProtocolHandler.exe "%1""

The secret is the string entry URL Protocol with an empty string. Now URL.dll recognizes "VSS" as a protocol.

Handling the protocol request

When the hook is registered, URL.dll will call VSSProtocolHandler.exe whenever it encounters "VSS:xxxx". The argument must be parsed by the handler.

VSS://$/Projects/VSS ProtHandler/TD-VSSProtocolHandler.doc

will eventually yield the next command line:

c:\whatever\VSSProtocolHandler.exe 
    "VSS://$/Projects/VSS ProtHandler/TD-VSSProtocolHandler.doc"

Note that the protocol prefix is still present in the argument. The handler needs to parse it and check for the "VSS:" or "VSS://" prefix. The remaining part of the argument is used to look up the document in the SourceSafe database.

The source archive contains a full Visual C++ 6 project for the handler. It's plain Win32 & Automation (no MFC). Note that the project settings automatically register the "VSS:" protocol once the project has compiled successfully, just like a COM component project does.

The code was developed on Windows 2000 and only tested on Windows 2000.

The first part is parsing the argument:

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

// WinMain

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{

    char szArgument[1024] = {0};
    LPSTR pszVSSFile = NULL;

    // Use a copy of the argument

    StrNCpy(szArgument, lpCmdLine, 1023);

    // Remove the quote from the string...

    PathUnquoteSpaces(szArgument);

    char *pszTemp = szArgument;
    while (*pszTemp)
    {
        if (*pszTemp == '\\')
            *pszTemp = '/';
        pszTemp++;
    }

    if (strlen(szArgument) == 0)
    {
        ReportError(pszErrorNoArgumentSpecified);
        exit(1);
    }
    // Check if the vss protocol string is present

    else if (_strnicmp(szArgument, "vss://$/", 8) != 0)
    {
        // Check if the slashslash is missing... that's ok too

        if (_strnicmp(szArgument, "vss:$/", 6) != 0)
        {
            // Check for "/register".

            if (_strnicmp(szArgument, "/register", 9) == 0)
            {
                RegisterVSSProtocol(_strnicmp(szArgument, 
                    "/registerq", 10) == 0? true: false);
                exit(0);
            }
            else if (_strnicmp(szArgument, "/?", 2) == 0)
            {
                ShowHelp();
                exit(0);
            }
            else
            {
                ReportError(pszErrorProtocolInvalidArgument, 
                                        szArgument, pszAbout);
                exit(1);
            }
        }
        else
            pszVSSFile = szArgument + 4;    // Point past the protocol

    }
    else
        pszVSSFile = szArgument + 6;        // Point past the protocol



    if (strlen(pszVSSFile) == 0)
    {
        MessageBox(NULL, pszErrorNoVSSFileSpecified, 
                               pszAppName, MB_ICONERROR);
        return 1;
    }

Next is firing up COM, instantiating a Visual SourceSafe Object instance, finding the current SourceSafe database in the registry and calling ViewVSSFile() to get the intended file:

    // Init COM Libraries

    HRESULT hr;
    if (FAILED(hr=CoInitialize(NULL)))
    {
        ReportError(pszErrorFailedToInitCOM, hr);
        exit(1);
    }

    CLSID clsid;

    // Create SourceSafe Automation object.

    if(SUCCEEDED(hr=CLSIDFromProgID(L"SourceSafe", &clsid)))
    {
        IVSSDatabase *pVSSDBObject = NULL;
        if (SUCCEEDED(hr=CoCreateInstance(clsid, NULL, 
          CLSCTX_ALL, IID_IVSSDatabase, (void**)&pVSSDBObject)))
        {
            long    lErr = 0;
            HKEY    hKey;
            if ((lErr = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
                       _T("SOFTWARE\\Microsoft\\SourceSafe"),
                       0,
                       KEY_READ,
                       &hKey)) == ERROR_SUCCESS)
            {
                DWORD   dwType = REG_SZ;
                char    szVSSDatabasePath[512] = {0};
                DWORD   dwSize = 511;

                if ((lErr = RegQueryValueEx(hKey,
                            _T("Current Database"),
                            NULL,
                            &dwType,
                            (LPBYTE)szVSSDatabasePath,
                            &dwSize)) == ERROR_SUCCESS && dwType == REG_SZ)
                {

                    if (!ViewVSSFile(pVSSDBObject, 
                        szVSSDatabasePath, pszVSSFile))
                    {
                        // Opening the file from the

                        // current VSS database FAILED.

                        // Now try the other known databases.

                        // Exercise for the reader...



                    }
                }
                else
                    ReportError(pszErrorFindingVSSCurrentDatabase);

                RegCloseKey(hKey);
            }
            else
                ReportError(pszErrorOpeningVSSRegKey, lErr);

            pVSSDBObject->Release();
        }
        else
            ReportError(pszErrorCreatingVSSAutomationObject, hr);
    }
    else
        ReportError(pszErrorFindingVSSAutomationObject, hr);

    CoUninitialize();

ViewVSSFile() will open the designated database (srcsafe.ini), look up the designated item (file), test it if it is a file, which is not marked 'Deleted', and performs a 'Get' command on it, resulting in a file in the Temporary directory. This file is executed using ShellExecuteW (UNICODE version because the arguments (widestrings) are UNICODE and I was lazy at the time).

bool ViewVSSFile(IVSSDatabase *pVSSDBObject, 
       char *pszVSSDatabasePath, char *pszVSSFile)
{
  bool    bViewSucceeded = false;
  HRESULT hr = S_OK;
  // Note: pszDatabasePath is a BUFFER with sufficient space...


  PathRemoveBackslash(pszVSSDatabasePath);
  strcat(pszVSSDatabasePath, "\\srcsafe.ini");
  CComBSTR bsVSSIniFile(pszVSSDatabasePath);

  // Open the sourcesafe database under the current

  // logged on username and (cached) password

  // by entering empty strings.

  if(SUCCEEDED(hr=pVSSDBObject->Open(bsVSSIniFile, L"", L"")))
  {
    IVSSItem *pIVSSItem = NULL;
    // bsVSSFile("$/Courses/Cursus evaluatie.doc");

    CComBSTR bsVSSFile(pszVSSFile);

    //MessageBox(NULL, "DATABASE IS OPEN!", 

                           pszAppName, MB_ICONINFORMATION);
    if(SUCCEEDED(hr=pVSSDBObject->get_VSSItem(bsVSSFile, 
                                                0, &pIVSSItem)))
    {
      int nItemType = -1;
      // Test if the VSS item is a file and not a project.

      if (SUCCEEDED(hr = pIVSSItem->get_Type(&nItemType)))
      {
        if (nItemType == VSSITEM_FILE)
        {
          VARIANT_BOOL vbDeleted;
          if (SUCCEEDED(hr = 
            pIVSSItem->get_Deleted(&vbDeleted)))
          {
            if (vbDeleted == VARIANT_FALSE)
            {
              // Get the filename from

              // the specified SourceSafe filepath.

              char *pszTargetFileName= 
                  StrRChrA(pszVSSFile, NULL, '/');
              pszTargetFileName++;

              char szTempFile[_MAX_PATH] = {0};

              if (GetTempPath(_MAX_PATH, szTempFile) > 0)
              {
                StrCat(szTempFile, pszTargetFileName);

                CComBSTR bsLocal(szTempFile);

                if (SUCCEEDED(hr=pIVSSItem->Get(&bsLocal, 
                                         VSSFLAG_REPREPLACE))) 
                // VSSFLAG_USERROYES | VSSFLAG_REPREPLACE)))

                {
                  // bsLocal now contains a path

                  // to a file in the temp folder,

                  // which is the extracted file.

                  int hInst = (int)ShellExecuteW(NULL, L"open", 
                    (BSTR)bsLocal, NULL, NULL, SW_SHOWMAXIMIZED);
                  if (hInst <= 32)
                    ReportError(pszErrorOpeningTempDocument, 
                               hInst, szTempFile, pszVSSFile);
                  else
                    bViewSucceeded = true;
                }
                else
                  ReportError(pszErrorGettingVSSDocument, hr, 
                    pszVSSFile, pszVSSDatabasePath, szTempFile);
              }
              else
                ReportError(pszErrorCantDetermineTempDir);
            }
            else
              ReportError(pszErrorVSSFileMarkedDeleted, pszVSSFile);
          }
          else
            ReportError(pszErrorRetrievingVSSFileDeletedState, hr);
        }
        else
          ReportError(pszErrorVSSItemIsNotAFile, pszVSSFile);
      }
      else
        ReportError(pszErrorRetrievingVSSFileType, hr);

      pIVSSItem->Release();
    }
    else
      ReportError(pszErrorFindingVSSDocument, 
              hr, pszVSSFile, pszVSSDatabasePath);
  }
  else
    ReportError(pszErrorOpeningVSSDatabase, hr, pszVSSDatabasePath);

  return bViewSucceeded;
}

There is some extra code to handle registration of the protocol and handle error messaging; they're pretty straight forward.

Demo executable

The demo archive contains the (release version) executable of the project. Extract it somewhere and run it through the Windows Run box (Start | Run) using argument "/register", e.g.: "C:\path\VSSProtocolHandler.exe /register". The handler will register the "VSS:" protocol and set itself up to be the handler. You are now ready to use the protocol. You may want to hardcode your SourceSafe username and password in VSS Open command (pVSSDBObject->Open(bsVSSIniFile, L"", L"")) instead of the empty strings. Empty strings make SourceSafe use the current Windows username and cached password. You must also setup an argument line ("VSS://$/path_to_existing_file_in_VSS_database") in the project's Settings "Debug" tab before debugging.

Further

This project was a 'proof-of-concept', and was never intended to be an idiot proof, user ready application. I won't be doing any more development on it, but I still have some ideas that you might find interesting to implement:

  • Better "logging on" code for the SourceSafe database. The current code assumes that you are using your Windows logon name to log on into the SourceSafe database. It also assumes that you have logged on to the database at least once; the logon/password is cached by Windows and is provided automatically in this case.
  • If one uses more than one SourceSafe database, and the document cannot be found in the current SourceSafe database, then the handler could open up the other databases until the file is found (or not). SourceSafe stores all used databases in the registry, so this shouldn't be hard to find.
    • Search for srcsafe.ini in the directory where ssapi.dll is located.
    • Search for srcsafe.ini in each directory of the path to ssapi.dll. In other words, if ssapi.dll is located in C:\Folder1\Folder2\Folder3\SSAPI.DLL, then Folder3, Folder2, Folder1 and C:\ are searched (in that order).
    • Search for srcsafe.ini in the location indicated by the named value "API Current Database" in the registry key HKEY_LOCAL_MACHINE\Software\Microsoft\SourceSafe.
    • Search for srcsafe.ini in the location indicated by the named value "SCCProviderPath" in the registry key HKEY_LOCAL_MACHINE\Software\Microsoft\SourceSafe.

By the way: MS Office applications do not recognize the "VSS:" protocol as a hyperlink and underline it automatically. You will have to use the "Insert Hyperlink" (CTRL+K) command to enter a Visual SourceSafe hyperlink.

An MSDN article describes the "Note:" protocol, which works in the same manner as the "VSS" protocol, but is hooked to notepad.exe. However, this example doesn't work, because notepad.exe receives "Note://file.ext" as argument, but can't handle the prefix "Note://" and fails to open the document.

References

  • Q167134 - HOWTO Open Visual SourceSafe to a Specific Project
  • Q169928 - HOWTO Open a SourceSafe Database with OLE Automation
  • Q175758 - HOWTO Trap Visual SourceSafe OLE Errors
  • Q176350 - HOWTO Open Visual SourceSafe to a Specific Database
  • Q201431 - HOWTO Write Automation for Visual SourceSafe 5_0-6_0
  • Microsoft Visual SourceSafe OLE Automation (Ken Felder, October 1995)
  • ssauterr - SourceSafe Automation errors
  • ssauto - Visual C++ Header File for Visual SourceSafe
  • Visual SourceSafe Frequently Asked Questions
  • vsstree - VSS OLE Sample Code Written in Visual C

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