VSS tool – mass unchecking
От: filkov СССР  
Дата: 12.04.10 18:00
Оценка: 18 (1)

Недавно на Code Project была опубликована моя статья VSS tool – mass unchecking.

Надеясь, что описанное в статье решение может оказаться интересным для участников RSDN, помещаю русский перевод.


Введение

На сайте Code Project можно найти немало статей о пользовательских утилитах для работы с MS Visual SourceSafe (VSS).
Я тоже написал для себя утилиту для администрирования VSS. Ничего выдающегося в ней нет, она просто «заточена» под мои нужды.
Одна из возможностей, которую я не нашёл в других опубликованных утилитах, это возможность проделать "mass-uncheck". Типичный сценарий для такой функции – программист покидает компанию а администратор VSS обнаруживает, что множество файлов во многих проектах остались «checked out» бывшим сотрудником.
У Microsoft Visual SourceSafe Explorer есть такая возможность, она даже может быть применена для любого выбранного (под)дерева проектов. Однако SourceSafe Explorer задаёт слишком много вопросов по каждой детали каждого «checked out» файла. И что более всего раздражает – для каждого файла, у которого имеет место множественный «check out», VSS Explorer вываливает диалог со списком check-out’ов и требует, чтобы пользователь выбрал из списка нужный check-out.
Для описанного случая, когда сотрудника в компании уже нет, а отмену его check-out’ов делать надо, хотелось бы иметь возможность указать на (под)дерево проектов и отменить все check-out’ы указанного пользователя (ex-User) без лишних вопросов.

Проблема

VSS не публикует в COM никаких методов выполнить отмену check-out для другого пользователя. Можно отменить только свои check-out’ы. (MS VSS Explorer знает, как это сделать, да только вот не говорит как.)
Давайте заглянем в библиотеку типов VSS.

// typelib filename: ssapi.dll

[
  uuid(783CD4E0-9D54-11CF-B8EE-00608CC9A71F),
  version(5.2),
  helpstring("Microsoft SourceSafe 8.0 Type Library")
]
library SourceSafeTypeLib
...

interface IVSSItemOld : IDispatch {
...
[id(0x0000000d)]
HRESULT UndoCheckout(
                [in, optional, defaultvalue("")] BSTR Local, 
                [in, optional, defaultvalue(0)] long iFlags);

Вот и всё, чем мы располагаем..
Можно бы войти в VSS под именем бывшего сотрудника. Но для этого надо знать его пароль. Само собой разумеется, VSS не публикует в COM никаких методов для извлечения пароля пользователя.

Решение

Библиотека типов VSS показывает, что у интерфейса IVSSUser есть свойство только-для-записи:

interface IVSSUser : IDispatch {
...
[id(0x00000003), propput]
HRESULT Password([in] BSTR rhs);
...

Соответственно можно воспользоваться таким обходным манёвром:

  • (Войдя как Admin) Переустановить пароль пользователя ex-User; установить пароль “exuser”, например.
  • Войти под именем ex-User.
  • Отменить check-out’ы ex-User’а всех выбранных файлов.
  • Опять войти как Admin.

    Вот этим манёвром я и воспользовался, чтобы воплотить в моей утилите возможность mass-uncheck как для узла дерева проектов, так и для списка файлов.

    Вот так выглядит отмена для списка файлов:



    А так выглядит отмена для узла проектов:



    Как оно работает

    На сайте Code Project можно найти много интересных статей по VSS API.Например:

  • VssReporter 2.1 — A Visual SourceSafe reporting tool for build administrators by .dan.g.
  • SSBrowser: A Sample Visual SourceSafe Automation by Ferdie

    Я не хочу забивать сайт повторением известного кода. Поэтому выложу только то, что относится к "undo checkout".
    В классе главного диалога я написал для этого четыре метода. Кроме того там есть ещё пара вспомогательных методов для записи в лог и для извлечения элемента из коллекции, но они весьма просты.

  • OnUnCheck()

    Когда пользователь выбирает в контекстном меню Uncheck, либо из списка файлов, либо из дерева проектов, вызывается метод OnUnCheck().
    OnUnCheck() определяет, какой пользователь был выбран из списка и определяет, откуда был вызов, из tree-view проектов, или из list-view файлов.

    Потом он делает следующее:

    o "Взламывает" пользователя – входит в VSS под именем выбранного пользователя;
    o Отменяет check-out’ы выбранных файлов из списка или в дереве проектов;
    o Снова входит в VSS как Admin.

  • HackUser()
    // Hack the user: change the user's password and log in as the user
    bool CMainDlg::HackUser(CString sUser, CString pw);

    ex-user ID и новый пароль передаются в HackUser() как параметры.

    HackUser() делает следующее:

    o Находит объект ex-user по его string ID: IVSSUserPtr pUser;
    o Меняет пароль: HRESULT hr = pUser->put_Password(bsPW);
    o И открывает базу данных VSS под именем ex-user: hr = m_pVssDB->raw_Open(bsDB, bsUser, bsPW).

  • UndoCheckouts()
    int CMainDlg::UndoCheckouts(CString strVssPath, LPCTSTR theUser);
    int CMainDlg::UndoCheckouts(IVSSItemPtr vssi, CString strVssPath, LPCTSTR theUser);

    UndoCheckouts() un-checks файлы в list-view или в выбранном узле проектов в tree-view
    Из строкового параметра пути к элементу в VSS UndoCheckouts() получает IVSSItemPtr:
    _bstr_t bsVssPath = strVssPath;
    IVSSItemPtr vssi = m_treeVss.m_pVssDB->GetVSSItem(bsVssPath, 0);
    
    return UndoCheckouts(vssi, strVssPath, theUser);

    Если IVSSItemPtr vssi указывает на проект:
    if (vssi->GetType() == VSSITEM_PROJECT)

    то функция перебирает в цикле все под-элементы проекта, и делает рекурсию:
    UndoCheckouts(vssi2, strVssPath, theUser);

    Когда наконец UndoCheckouts() "достигает дна" – найден элемент типа VSSITEM_FILE, то вызывается метод:
    UndoCheckout(vssi, strVssPath, theUser)

    Который и выполняет un-checking элемента.

  • UndoCheckout()
    // undo checkout for the single file.
    bool CMainDlg::UndoCheckout(IVSSItemPtr vssi, LPCTSTR strVssPath, LPCTSTR theUser);

    Файловый элемент IVSSItemPtr vssi, переданный как параметр, может иметь много звеньев (links) – элемент может быть общим (shared) для нескольких проектов в VSS.
    UndoCheckout() перебирает в цикле все звенья элемента, кроме удалённых:
    IVSSItemPtr link;
    IVSSItemsPtr links = vssi->Links;
    int nLinks = vssi->Links->Count;    
    ATLTRACE(_T("Item %s has %d links\n"), (LPCTSTR)vssi->Spec, nLinks);
    for(int i = 0; i < nLinks; i++)
    {
        IVSSItemPtr link = GetOneOfItems(links, i);
        ...
    }

    Затем для каждого звена метод извлекает коллекцию checkouts – одно звено может быть check-out несколько раз (разными пользователями):
    IVSSCheckoutsPtr lnkCheckouts = link->Checkouts;
    long nCheckouts = lnkCheckouts->Count;
    IVSSCheckoutPtr lnkCheckout;
    for(long idx = 0; idx < nCheckouts; idx++) {...}

    И наконец UndoCheckout() находит звено check-out пользователем ex-user и пытается выполнить un-check с параметрами по умолчанию:
    try
    {
          _bstr_t bsDefault;
          bsDefault = "";
          long nDefault = VSSFLAG_DELNO|VSSFLAG_KEEPYES;
          hr = link->raw_UndoCheckout(bsDefault, nDefault);
    }
    catch (_com_error & e) {...}


    Код
    ////////////////////////////////////////////
    // Headers:
    //
    #import "C:\Program Files\Microsoft Visual SourceSafe\SSAPI.DLL" no_namespace
    
    // UndoCheckout menu-item selected
    LRESULT CMainDlg::OnUnCheck(WORD /*wNotifyCode*/, WORD wID, HWND hwnd, BOOL& bHandled);
    
    // Hack the user: change the user's password and log in as the user
    bool CMainDlg::HackUser(CString sUser, CString pw);
    
    // undo checkout recursively for the project
    // verify strVssPath is part of the item->Spec!
    // (there might be multiple links on the item)
    int CMainDlg::UndoCheckouts(IVSSItemPtr vssi, CString strVssPath, LPCTSTR theUser);
    
    // undo checkout for the single file; verify spec.
    bool CMainDlg::UndoCheckout(IVSSItemPtr vssi, LPCTSTR strVssPath, LPCTSTR theUser);
    
    
    ////////////////////////////////////////////
    // Implementation:
    //
    // UndoCheckout for specific user for:
    // all subitems of the item selected on the tree / all items
    // in the report / all selected items in the report
    // returns the number of unchecked items
    LRESULT CMainDlg::OnUnCheck(WORD /*wNotifyCode*/, WORD wID, HWND hwnd, BOOL& bHandled)
    {
         int unCount = 0;
    
         // find out was it called from the tree or from the report
         bool isTree = false;
         if(m_hwndActive == m_wndListView.m_hWnd)
              isTree = false;
         else if(m_hwndActive == m_treeVss.m_hWnd)
              isTree = true;
         else
              return 0;
    
         // Retrieve the user from dialog control
         CString theUser;
         // ...
         // Retrieve selected project
         CString prj = m_treeVss.GetSelectedProject();
         // ...
    
         // if called from list view, check if there are selected files
         // (one file, if just one selected; all files, if none selected)
         if(!isTree)
         {
              int count = m_wndListView.GetItemCount();
             int selCount = m_wndListView.GetSelectedCount();
             // ...
    
             // start unchecking
             m_bReporting = true;
             EnableControls(FALSE);
             CWaitCursor cursor;
    
             // ImpersonateUser(sUser); reset PW; open DB as the user
             if(theUser.Compare(_T("Admin")))
                  bOk = HackUser(theUser, "exuser");
    
              while((idx = m_wndListView.GetNextItem(idx, flag)) > -1)
             {
                  dbg = m_wndListView.GetItemText(idx, 2, buf,
                               sizeof(buf)/sizeof(TCHAR));
                  if(!_tcslen(buf))
                  continue;
    
                  if(UndoCheckout(buf, theUser))
                      unCount++;
             }
    
             // reopen DB as admin
             if(theUser.Compare(_T("Admin")))
                  OpenDatabase(m_sDbPath);
    
             m_bReporting = false;
             EnableControls();
         }
         else
         {
             CString message;
             message.Format(_T("Undo '%s' checkouts recursively in %s?"), theUser, prj);
             if(IDOK == MessageBox(message, _T("Uncheck"), MB_OKCANCEL|MB_ICONQUESTION))
             {
                  // start unchecking
                  m_bReporting = true;
                  EnableControls(FALSE);
                  CWaitCursor cursor;
    
                  // ImpersonateUser(sUser); reset PW; open DB as the user
                  if(theUser.Compare(_T("Admin")))
                      bool bOk = HackUser(theUser, "exuser");
    
                  unCount = UndoCheckouts(prj, theUser);
    
                  // reopen DB as admin
                  if(theUser.Compare(_T("Admin")))
                      OpenDatabase(m_sDbPath);
             }
         }
         m_bReporting = false;
         EnableControls();
    
         // restore selections; refresh controls
         // ...
    
         return unCount;
    }
    
    // Hack the user: change the user's password and log in as the user
    bool CMainDlg::HackUser(CString sUser, CString pw)
    {
         bool bOk = false;
         IVSSUsersPtr pUsers = NULL;
         try
         {
             pUsers = m_treeVss.m_pVssDB->GetUsers();
             CComVariant var(sUser);
             IVSSUserPtr pUser = pUsers->GetItem(var);
             _bstr_t bsPW = pw;
             HRESULT hr = pUser->put_Password(bsPW);
             if(FAILED(hr))
             {
                  ATLTRACE(_T("HackUser::put_Password() error: %X\n"), hr);
                  return false;
             }
    
             _bstr_t bsDB = m_sDbPath;
             _bstr_t bsUser = sUser;
    
             hr = m_treeVss.m_pVssDB->Close();
    
             hr = m_treeVss.m_pVssDB->raw_Open(bsDB, bsUser, bsPW);
             if(FAILED(hr))
             {
                  ATLTRACE(_T("HackUser:: raw_Open() error: %X\n"), hr);
                  return false;
             }
    
             bOk = true;
         }
         catch (_com_error & e)
         {
             ATLTRACE(_T("HackUser() error: %s\n"), e.ErrorMessage());
         }
    
         return bOk;
    }
    
    // undo checkout recursively for the project
    // parameters are retrieved from the dialog controls:
    // IVSSItemPtr vssi    - VSS item;
    // CString strVssPath  - VSS item path;
    // LPCTSTR theUser     - the user to undo checkouts for;
    int CMainDlg::UndoCheckouts(IVSSItemPtr vssi, CString strVssPath, LPCTSTR theUser)
    {
         int count = 0;
         int  idx = 0;
         _variant_t var;
         IVSSItemPtr   vssi2;
         IVSSItemsPtr  vssis;        // VSS collection
         CWaitCursor cursor;
    
         // if user clicked Cancel button – cancel checkouts
         GetDlgItem(IDCANCEL).EnableWindow();
         if (!Continue(m_bReporting))
         {
             GetDlgItem(IDCANCEL).EnableWindow(FALSE);
             return idx;
         }
    
         if (vssi->GetType() == VSSITEM_PROJECT)
         {
             vssis = vssi->GetItems(FALSE);
             for(idx = 0; idx < vssis->GetCount(); idx++)
             {
                  var = (long)(idx + 1);
                  vssi2 = vssis->GetItem(var);
    
                  UndoCheckouts(vssi2, strVssPath, theUser); 
             }
         }
         else
         {
             // we hit the bottom: it's an item (not the project/folder)
             ATLTRACE(_T("Uncheck item %s, version %d\n"),
                           (LPCTSTR)vssi->Spec, vssi->VersionNumber);
             if(UndoCheckout(vssi, strVssPath, theUser))
                  count++;
         }
    
         return count;
    }
    
    
    // undo checkout for the specified VSS item
    // IVSSItemPtr vssi    - VSS item;
    // CString strVssPath  - VSS item path;
    // LPCTSTR theUser     - the user to undo checkouts for;
    bool CMainDlg::UndoCheckout(IVSSItemPtr vssi, LPCTSTR strVssPath, LPCTSTR theUser)
    {
         bool bOk = false;
         long nCheckouts = 0;
         long nVersion = (long)(-1);
         CString csUser;
         csUser = theUser;
    
         // find in the links the one with the right spec
         // NB: there might be multiple links for the VSS items
         //– the item may be shared between the projects
         // and for each link there maight be several checkouts by different users
         IVSSItemPtr link;
         IVSSItemsPtr links = vssi->Links;
         int nLinks = vssi->Links->Count;  
         ATLTRACE(_T("Item %s has %d links\n"), (LPCTSTR)vssi->Spec, nLinks);
         for(int i = 0; i < nLinks; i++)
         {
             IVSSItemPtr link = GetOneOfItems(links, i);
             // simple helper to get links[i]
             
             _variant_t varDeleted = link->Deleted;
             // skip deleted links
    
             if((BOOL)varDeleted)
                  continue;
    
             CString csLink;
             _bstr_t lnkSpec = link->Spec;
             csLink = (LPCTSTR)lnkSpec;
             long ChkStatus = link->IsCheckedOut;
             switch(ChkStatus)
             {
             case VSSFILE_NOTCHECKEDOUT:
                  ATLTRACE(_T"Link %s is NOT checked out\n"), csLink);
                  break;
             case VSSFILE_CHECKEDOUT:
                  ATLTRACE(_T"Link %s is checked out by OTHER user\n"), csLink);
                  break;
             case VSSFILE_CHECKEDOUT_ME:
                  ATLTRACE(_T"Link %s is checked out by ME\n"), csLink);
                  break;
             }
    
             // verify there is a checkout for the user
             bool bFound = false;
             IVSSCheckoutsPtr lnkCheckouts = link->Checkouts;
             long nCheckouts = lnkCheckouts->Count;
             IVSSCheckoutPtr lnkCheckout;
             for(long idx = 0; idx < nCheckouts; idx++)
             {
                  CComVariant var(idx + 1);
                  lnkCheckout = lnkCheckouts->GetItem(var);
                  _bstr_t user = lnkCheckout->Username;
    
                  // filter by the user (to simplify comparison, make it CString)
                  if(0 == csUser.CompareNoCase((LPCTSTR)user))
                  {
                      bFound = true;      // checkout for the user was found
                      nVersion = lnkCheckout->VersionNumber;
                      CString sLocal;
                      sLocal = (LPCTSTR)lnkCheckout->LocalSpec;
                      ATLTRACE(_T "Found checkout ver: %d to: %s\n"), nVersion, sLocal);
                      break;
                  }
             }
             if(!bFound)
                  return false;
    
             HRESULT hr = S_OK;
             try
             {
                  _bstr_t bsDefault;
                  bsDefault = "";
                  long nDefault = VSSFLAG_DELNO|VSSFLAG_KEEPYES;
    
                  hr = link->raw_UndoCheckout(bsDefault, nDefault);
             }
             catch (_com_error & e)
             {
                  ATLTRACE(_T("%s\n"), e.ErrorMessage());
             }
             if(SUCCEEDED(hr))
             {
                  bOk = true;
                  break;
             }
             else
                  LogCOMError(hr, _T("UndoCheckout()"));  // simple helper function
         }
    
         return bOk;
    }
  • Санкционный Смотритель.
    vss com wtl
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.