15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Как получить список пользователей с сервера


Автор: Derek Lakin

Описание

Пример показывает, как пользоваться функцией NetQueryDisplayInformation, для получения специфической информации о пользователях, а так же представлен класс, который позволяет получить более общий доступ к данному сервису.

Детальное описание функции

Прототип функции выглядит так:

NET_API_STATUS NetQueryDisplayInformation(
  LPCWSTR ServerName,
  DWORD Level,
  DWORD Index,
  DWORD EntriesRequested,
  DWORD PreferredMaximumLength,
  LPDWORD ReturnedEntryCount,
  PVOID *SortedBuffer);

Возвращаемый тип NET_API_STATUS определён как DWORD. Если функция завершается успешно, то возвращаемое значение будет ERROR_SUCCESS (соответствует Win32 коду 0). Так же возможны следующие значения ошибки:

Значение Описание
ERROR_ACCESS_DENIED Пользователь не имеет доступа к запрашиваемой информации.
ERROR_INVALID_LEVEL Параметр Level имеет неправильное значение.
ERROR_MORE_DATA Данная ошибка сигнализирует о том, что на сервере доступно больше ячеек информации. Это говорит о том, что параметр SortedBuffer содержит не последнюю доступную ячейку информации. Для получения дополнительных данных, необходимо вызвать NetQueryDisplayInformation заново с параметром Index , содержащем значение, возвращённое в next_index члене последней ячейки в SortedBuffer.

Так же возможно возникновение ошибки со значением 1722, которая соответствует RPC_S_SERVER_UNAVAILABLE. Это говорит о том, что сервер в данный момент не доступен. Конечто не обязательно, но желательно включать в код программы проверку на возникновение данной ошибки.

Первый параметр ServerName - это строка Unicode wide-character, которая нам нужна, чтобы использовать MultiByteToWideChar для преобразования стандартных строк в необходимый формат. Он может быть NULL. При этом функция вернёт информацию о локальном компьютере. Если же не NULL, то строка должна начинаться с \\.

Особый интерес представляет параметр Level. Этот параметр указывает уровень получаемой информации, и может иметь одно из следующих значений.

Значение Описание
1 Получить информацию о пользователях. Параметр SortedBuffer указывает на массив структуры NET_DISPLAY_USER.
2 Получить информацию о компьютере. Параметр SortedBuffer указывает на массив структуры NET_DISPLAY_MACHINE.
3 Получить информацию о группе. Параметр SortedBuffer указывает на массив структуры NET_DISPLAY_GROUP.

Так как нас интересует информация о пользователях, то устанавливаем данный параметр в 1.

Теперь давайте посмотрим, как выглядит структура NET_DISPLAY_USER:

typedef struct _NET_DISPLAY_USER {
  LPWSTR   usri1_name;
  LPWSTR   usri1_comment;
  DWORD    usri1_flags;
  LPWSTR   usri1_full_name;
  DWORD    usri1_user_id;
  DWORD    usri1_next_index;
} NET_DISPLAY_USER, *PNET_DISPLAY_USER;

Не будем вдаваться в подробности описания каждого поля данной структуры. Так как нас интересует только список пользователей, то обратим внимание на два поля: usri1_name, в котором хранится логин пользователя и usri1_full_name, в котором содержится полное имя пользователя. Обе записи имеют формат Unicode wide-character строк.

 

Использование функции

Итак, при использовании данного сервиса рассмотрим три основных его составляющих:

  1. Установка параметров, для передачи в функцию.

  2. Вызов функции и получение результатов.

  3. Обработка возможных ошибок.

Параметры устанавливаются следующим образом:

// Во-первых, нам необходимо наши строки в Unicode wide-character формат
CString szServer = "\\\\MYSERVER";    // Сервер для запроса
LPWSTR pWideServer;
int nBytesSource = strlen(pString) * 2;
// Количество WChars необходимых для сохранения полученных данных
int nWCharNeeded = MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED,
                                     pString, nBytesSource, NULL, 0);

// Распределяем необходимое пространство памяти плюс 2 байта для '\0'
pWideServer = (LPWSTR)GlobalAlloc (GPTR, (nWCharNeeded + 1) * 2);

// Делаем преобразование
nWCharNeeded = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pString,
    nBytesSource,(LPWSTR)pWideServer, nWCharNeeded);

if (0L == nWCharNeeded) {
    pWideServer = NULL;
}
else {
    *(LPWSTR)(pWideServer + nWCharNeeded) = L'\0';
}
nIndex = 0;        // Индекс внутри списка пользователей
DWORD dwCount;        // Возвращённое количество ячеек
void* pBuffer;        // Буфер для хранения результатов
NET_DISPLAY_USER* ndu;    // Информация о пользователе
DWORD dwResult,  i;    // Код завершения и индекс

Далее, для получения результатов используем следующий код:

do {
    dwResult = NetQueryDisplayInformation ((LPCWSTR)pWideServer,
                              1, nIndex, 10, 24, &dwCount, &pBuffer);
    if ((dwResult == ERROR_SUCCESS) || (dwResult == ERROR_MORE_DATA)) { 
        for (i = 0, ndu = (NET_DISPLAY_USER*)pBuffer; i < dwCount; ++i, ++ndu) {
            CString szName, szFullName, szComment;
            szName.Format("%S", ndu->usri1_name);
            szFullName.Format("%S", ndu->usri1_full_name);
            szComment.Format ("%S", ndu->usri1_comment);
            TRACE ("Name:\t\t" + szName + "\n"); 
            TRACE ("Full Name:\t" + szFullName + "\n"); 
            TRACE ("Comment:\t" + szComment + "\n"); 
            TRACE ("--------------------------------\n");
            if (dwCount >  0){
                nIndex = ((NET_DISPLAY_USER *)pBuffer)[dwCount - 1].usri1_next_index;
            }
        }
    }
} while (dwResult == ERROR_MORE_DATA);

Первый раз функция NQDI делает запрос для индекса 0. Далее индекс увеличивается до тех пор, пока не будет получено очередных данных о пользователе, либо не возникнет ошибка. Индекс следующего пользователя содержится в поле usri1_next_index структуры NET_DISPLAY_USER.

      
switch (dwResult) { 
    case ERROR_ACCESS_DENIED: 
        TRACE ("%s(%i): The user does not have access
                 to the requested information.\n", __FILE__, __LINE__); 
        break; 
    case ERROR_INVALID_LEVEL:
        TRACE ("%s(%i): The Level parameter specifies
                             an invalid value.\n", __FILE__, __LINE__); 
        break;
    case ERROR_MORE_DATA: 
        TRACE ("%s(%i): More entries are available.\n", __FILE__, __LINE__); 
        break;
    case ERROR_SUCCESS: 
        //    ничиго не делаем 
        break; 
    default: {
        // Другие ошибки, возможно RPC related 
        LPVOID lpMsgBuf; // буфер для сообщения
        ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            0,
            dwResult, 
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Язык по умолчанию
            (LPTSTR)&lpMsgBuf, 0, NULL ); 
        TRACE ("%s(%i): %s\n", __FILE__, __LINE__, lpMsgBuf); 
        // Освобождаем буфер, который был выделен при помощи LocalAlloc() 
        ::LocalFree (lpMsgBuf);
        }
        break;
}
GlobalFree (pWideServer);

 

 

Класс для использования данного сервиса

Как и все хорошие программисты, я не смог не создать для этой функции своего класса! Он имеет три основных статических функции и один дополнительный класс CNetInfo:

static DWORD GetUserInfo (USER_LIST* pUsers, LPCSTR pServer = NULL);
static DWORD GetMachineInfo (MACHINE_LIST* pMachines, LPCSTR pServer = NULL);
static DWORD GetGroupInfo (GROUP_LIST* pGroups, LPCSTR pServer = NULL); 
static CString FormatMessage (DWORD dwError);

Первые три функции вызывают всё тот же NQDI. Последняя используется для для приёма сообщений об ошибках и возвращает строку, содержащую описание произошедшей ошибки.

Типы USER_LIST, MACHINE_LIST и GROUP_LIST определены как:

#define USER_LIST    CList<NET_DISPLAY_USER, NET_DISPLAY_USER&>
#define MACHINE_LIST    CList<NET_DISPLAY_MACHINE, NET_DISPLAY_MACHINE&>
#define GROUP_LIST    CList<NET_DISPLAY_GROUP, NET_DISPLAY_GROUP>

Так же определены уровни получаемой информации:

#define LEVEL_USER    1
#define LEVEL_MACHINE    2
#define LEVEL_GROUP    3

Для использования данного класса, нам необходимо создать список необходимых типов, передать указатель на него и имя сервера в требуемую функцию (определённую параметром level), а затем, в зависимости от результата, либо обработать полученные результаты, либо обработать сообщение об ошибке. Класс определён в именном пространстве ssl_net, так что необходимо добавить строчку using namespace ssl_net;, либо вызывать функцию из класса, как это показано ниже:

USER_LIST pUsers = new USER_LIST;
CString szServer = "\\\\MYSERVER"
DWORD dwResult = ssl_net::CNetInfo::GetUserInfo (pUsers, szServer);
if (ERROR_SUCCESS == dwResult) {
    // Обрабатываем результаты
    POSITION pos = pUsers->GetHeadPosition ();
    while (NULL != pos) {
        NET_DISPLAY_USER ndu = pUsers->GetNext (pos); 
        CString szName, szComment, szFlags, szFullName, szUserID; 
        szName.Format ("%S", ndu.usri1_name);
        szComment.Format ("%S", ndu.usri1_comment); 
        szFlags.Format ("%d", ndu.usri1_flags); 
        szFullName.Format ("%S",n du.usri1_full_name); 
        szUserID.Format ("%d", ndu.usri1_user_id);
        TRACE ("%S\n%S\n%d\n%S\n%d\n", ndu.usri1_name,
            ndu.usri1_comment, ndu.usri1_flags,
            ndu.usri1_full_name, ndu.usri1_user_id);
    }
}
else {
    // Обрабатываем ошибки
    CString szErrMsg = ssl_net::CNetInfo::FormatMessage (dwResult);
    AfxMessageBox (szErrMsg);
}
delete pUsers;

Теперь для того, чтобы это всё заработало, достаточно включить в проект NetInfo.h и прилинковать NetApi32.lib.

Приведённый пример был написан на Visual C++ 6 SP4, но думаю, что код должен работать с различными модификациями VC6 или сервиспаков. Тестирование проводилось только на Windows NT 4 SP6a. В документации сказано, что функция NQDI поддерживается в Windows NT 3.1 или поздних версиях (включая Windows 2000), но не поддерживается в Windows 95 или 98.