![]() |
General Programming »
Programming Tips »
General
Intermediate
VSS: protocol handler for Visual SourceSafeBy Victor VogelpoelThis 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. |
VC6Win2KIE6.0, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||

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:
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.
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.
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.
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:
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.
| You must Sign In to use this message board. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+PgUp/PgDown to switch pages.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 27 Jun 2001 Editor: Smitha Vijayan |
Copyright 2001 by Victor Vogelpoel Everything else Copyright © CodeProject, 1999-2010 Web20 | Advertise on the Code Project |