EternalWindows
LSA / ログオンセッション

プロセスがLogonUserなどでユーザーをログオンさせた場合、 システム内部で主に2つの作業が行われます。 1つは、ログオンしたユーザーを表すトークンを作成してそれを呼び出し側に返すことです。 これは、LogonUserからの引数からも想像できます。 そしてもう1つは、そのユーザーに関連するログオンセッションを作成することです。 ログオンセッションは、ユーザーがログオンしてからログオフするまでの間に存在します。 話を分かりやすくするため、次に例を示します。

#include <windows.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR  szUserName[] = TEXT("Guest");
	TCHAR  szPassword[] = TEXT("");
	HANDLE hToken;
	
	if (!LogonUser(szUserName, NULL, szPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)) {
		MessageBox(NULL, TEXT("ログオンに失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	MessageBox(NULL, TEXT("ログオンしました。"), TEXT("OK"), MB_OK);

	CloseHandle(hToken);

	MessageBox(NULL, TEXT("ログオフしました。"), TEXT("OK"), MB_OK);

	return 0;
}

この例では、LogonUserでユーザーをログオンし、その後にログオンした旨をMessageBoxで表示しています。 この時点で、ユーザーはログオンに成功しているため、 トークンとログオンセッションが内部的に作成されていることになります。 その後、CloseHandleを呼び出し、ログオフした旨を表示していますが、 これはCloseHandleの呼び出しでユーザーがログオフしたと考えてよいのでしょうか。 結論から言うと、その通りと言えます。 トークンはカーネルオブジェクトであり、内部で参照カウントを持っています。 この値は初期値が1であり、0になった場合はオブジェクトが破棄されることになります。 CloseHandleは内部でオブジェクトの参照カウントを1つ下げますから、 そのトークンが外部プロセスによって参照されていない限り、 トークンは破棄されることになります。 トークンが破棄されるということは、ログオンしたユーザーを表す情報がなくなることを意味しますから、 ユーザーはログオフしたことになり、ログオンセッションも削除されることになります。

ログオンしたユーザーのセキュリティ的な情報を取得するには、トークンのハンドルをGetTokenInformationに指定することになります。 一方、ユーザーがどのようにログオンしたのかを取得したい場合は、 ログオンセッションに関連するデータを取得することになります。 これには、LsaGetLogonSessionDataを呼び出します。

NTSTATUS NTAPI LsaGetLogonSessionData(
  PLUID LogonId,
  PSECURITY_LOGON_SESSION_DATA *ppLogonSessionData
);

LogonIdは、ログオンセッションを識別するLUIDを指定します。 ppLogonSessionDataは、ログオンセッションのデータを受け取る変数のアドレスを指定します。 SECURITY_LOGON_SESSION_DATA構造体は、次のように定義されています。

typedef struct _SECURITY_LOGON_SESSION_DATA {
  ULONG              Size;
  LUID               LogonId;
  LSA_UNICODE_STRING UserName;
  LSA_UNICODE_STRING LogonDomain;
  LSA_UNICODE_STRING AuthenticationPackage;
  ULONG              LogonType;
  ULONG              Session;
  PSID               Sid
  LARGE_INTEGER      LogonTime;
  LSA_UNICODE_STRING LogonServer;
  LSA_UNICODE_STRING DnsDomainName;
  LSA_UNICODE_STRING Upn;
#if (_WIN32_WINNT >= 0x0600)
  ULONG UserFlags;
  LSA_LAST_INTER_LOGON_INFO LastLogonInfo;
  LSA_UNICODE_STRING LogonScript;
  LSA_UNICODE_STRING ProfilePath;
  LSA_UNICODE_STRING HomeDirectory;
  LSA_UNICODE_STRING HomeDirectoryDrive;
  LARGE_INTEGER LogoffTime;
  LARGE_INTEGER KickOffTime;
  LARGE_INTEGER PasswordLastSet;
  LARGE_INTEGER PasswordCanChange;
  LARGE_INTEGER PasswordMustChange;
#endif
} SECURITY_LOGON_SESSION_DATA,  *PSECURITY_LOGON_SESSION_DATA;

Sizeは、この構造体のサイズが格納されます。 LogonIdは、ログオンセッションのLUIDが格納されます。 UserNameは、ログオンセッションを所有するユーザーの名前が格納されます。 LogonDomainは、ログオンセッションの所有者が認証されたドメイン名が格納されます。 AuthenticationPackageは、ログオンセッションの所有者の認証に使用された認証パッケージの名前が格納されます。 認証パッケージとは、認証を行うDLLのことです。 LogonTypeは、ログオンセッションの所有者がどのようにログオンしたかを示すログオンタイプが格納されます。 Sessionは、ターミナルサービスのセッションIDが格納されます。 コンソールへログオンしたユーザーは、1が格納されます。 Sidは、ログオンセッションの所有者のSIDが格納されます。 LogonTimeは、ログオンした時間が格納されます。 LogonServerは、ログオンしたサーバーの名前が格納されます。 DnsDomainNameは、DNS形式のドメイン名が格納されます。 Upnは、ログオンセッションを所有するユーザーのUPN形式の名前が格納されます。 これ以降のメンバは、Windows Vistaから使用可能となります。

SECURITY_LOGON_SESSION_DATA構造体のLogonTypeには、 SECURITY_LOGON_TYPEとして定義されている定数が格納されます。

typedef enum _SECURITY_LOGON_TYPE {
  UndefinedLogonType = 0,
  Interactive = 2,
  Network,
  Batch,
  Service,
  Proxy,
  Unlock,
  NetworkCleartext,
  NewCredentials,
#if (_WIN32_WINNT >= 0x0501)
  RemoteInteractive,
  CachedInteractive,
#endif
#if (_WIN32_WINNT >= 0x0502)
  CachedRemoteInteractive,
  CachedUnlock
#endif

これらの意味は、定数の名前が示す通りとなっています。 たとえば、Interactiveの場合はシステムに対話ログオンしたことを意味し、 Networkの場合はネットワークからログオンしたことを意味します。

LsaGetLogonSessionDataを呼び出すためには、ログオンセッションを識別するLUIDが必要になりますが、 これはGetTokenInformationで取得することができます。 次に、例を示します。

#include <windows.h>
#include <ntsecapi.h>

#pragma comment (lib, "secur32.lib")

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	DWORD                        dwLength;
	HANDLE                       hToken;
	PTOKEN_STATISTICS            pTokenStatistics;
	NTSTATUS                     ns;
	PSECURITY_LOGON_SESSION_DATA pLogonSessionData;
	
	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
		MessageBox(NULL, TEXT("トークンのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	GetTokenInformation(hToken, TokenStatistics, NULL, 0, &dwLength);
	pTokenStatistics = (PTOKEN_STATISTICS)LocalAlloc(LPTR, dwLength);
	GetTokenInformation(hToken, TokenStatistics, pTokenStatistics, dwLength, &dwLength);

	ns = LsaGetLogonSessionData(&pTokenStatistics->AuthenticationId, &pLogonSessionData);
	if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("ログオンセッションのデータの取得に失敗しました。"), NULL, MB_ICONWARNING);
		LocalFree(pTokenStatistics);
		CloseHandle(hToken);
		return FALSE;
	}
	
	MessageBox(NULL, TEXT("ログオンセッションのデータを取得しました。"), TEXT("OK"), MB_OK);

	LsaFreeReturnBuffer(pLogonSessionData);
	LocalFree(pTokenStatistics);
	CloseHandle(hToken);

	return 0;
}

GetTokenInformationにTokenStatisticsを指定した場合、 TOKEN_STATISTICS構造体を取得することができます。 この構造体のAuthenticationIdはログオンセッションを識別するLUIDになっているため、 これをLsaGetLogonSessionDataの第1引数に指定することができます。

ログオンセッションのデータを取得するために上記のような手順が必要となるのであれば、 最初からデータをトークンに含ませる設計になっていればよいようにも思えます。 そもそも、ログオンセッションは、どのような目的で存在しているのでしょうか。 まず1つ考えられるのは、統計的な情報を管理するという点です。 SECURITY_LOGON_SESSION_DATA構造体のメンバを見ると、 誰がいつどのようにログオンしたかといった情報で構成されており、 これはアプリケーション開発者でない方にも理解しやすい情報です。 そして、もう1つ考えられるのは、こうした統計的な情報を取得するために トークンのハンドルが必要になることを避けるという点です。 トークンのハンドルを取得すれば、特権リストなどセキュリティ的に重要なデータも取得できますし、 ハンドルに割り当てられたアクセス権によっては、データの変更が可能になる事も考えられます。 こうしたことから、統計的な情報をログオンセッションとして分離しているのではないかと思われます。

ログオンセッションは、ログオンしたユーザーの統計的な情報を含んでいますから、 現在のユーザーだけではなく、他のユーザーの情報も取得できたら便利であるといえます。 LsaEnumerateLogonSessionsを呼び出せば、存在する全てのログオンセッションのLUIDを取得できるため、 これを基にLsaGetLogonSessionDataを呼び出す方法が可能になります。

NTSTATUS NTAPI LsaEnumerateLogonSessions(
  PULONG LogonSessionCount,
  PLUID *LogonSessionList
);

LogonSessionCountは、ログオンセッションの数を受け取る変数のアドレスを指定します。 LogonSessionListは、各ログオンセッションのLUIDを格納した配列を受け取る変数のアドレスを指定します。 次に示すLUIDは、システムによって確保されています。

LUID
SYSTEM_LUID 999
ANONYMOUS_LOGON_LUID 998
LOCALSERVICE_LUID 997
NETWORKSERVICE_LUID 996
IUSER_LUID 995

LsaEnumerateLogonSessionsで列挙されるログオンセッションには、 ANONYMOUS LOGONという名前を持ったものがありますが、 このセッションのLUIDはANONYMOUS_LOGON_LUIDではないことに注意してください。 ANONYMOUS_LOGON_LUIDは、ImpersonateAnonymousTokenで割り当てられるトークンに関連するLUIDです。

LsaGetLogonSessionDataやLsaEnumerateLogonSessionsで取得したデータは、LsaFreeReturnBufferで開放することになります。

NTSTATUS LsaFreeReturnBuffer(
  PVOID Buffer
);

Bufferは、開放したいデータを指定します。

今回のプログラムは、LsaEnumerateLogonSessionsでログオンセッションのLUIDを取得し、 各ログオンセッションに関連付けられたユーザー名を左のリストボックスに列挙します。 そして、そのユーザー名を選択した場合は、 関連するログオンセッションのデータを右のリストボックスに表示します。 LsaGetLogonSessionDataやLsaEnumerateLogonSessionsの呼び出しには、 secur32.libへのリンクが必要になります。

#include <windows.h>
#include <ntsecapi.h>
#include <sddl.h>

#pragma comment (lib, "secur32.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND  hwndListBoxLeft = NULL;
	static HWND  hwndListBoxRight = NULL;
	static PLUID pLogonSessionList = NULL;

	switch (uMsg) {
	
	case WM_CREATE: {
		ULONG                        i;
		ULONG                        uLogonSessionCount;
		WCHAR                        szBuf[256];
		NTSTATUS                     ns;
		PSECURITY_LOGON_SESSION_DATA pLogonSessionData;

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		ns = LsaEnumerateLogonSessions(&uLogonSessionCount, &pLogonSessionList);
		if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS && pLogonSessionList != NULL)
			return -1;

		for (i = 0; i < uLogonSessionCount; i++) {
			ns = LsaGetLogonSessionData(&pLogonSessionList[i], &pLogonSessionData);
			if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS)
				continue;
			
			lstrcpyn(szBuf, pLogonSessionData->UserName.Buffer, (pLogonSessionData->UserName.Length / sizeof(WCHAR)) + 1);
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)szBuf);
			
			LsaFreeReturnBuffer(pLogonSessionData);
		}

		return 0;
	}
	
	case WM_COMMAND: {
		DWORD                        dwIndex;
		WCHAR                        szBuf[256];
		WCHAR                        szFormatBuf[1024];
		WCHAR                        szLogonType[256];
		FILETIME                     fileTime;
		SYSTEMTIME                   systemTime;
		LPWSTR                       lpszSid;
		PSECURITY_LOGON_SESSION_DATA pLogonSessionData;
		
		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;
		
		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);
		
		dwIndex = (DWORD)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);
		LsaGetLogonSessionData(&pLogonSessionList[dwIndex], &pLogonSessionData);

		wsprintf(szBuf, L"LUID : %d", pLogonSessionData->LogonId.LowPart);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		lstrcpyn(szBuf, pLogonSessionData->Upn.Buffer, (pLogonSessionData->Upn.Length / sizeof(WCHAR)) + 1);
		
		lstrcpyn(szBuf, pLogonSessionData->LogonDomain.Buffer, (pLogonSessionData->LogonDomain.Length / sizeof(WCHAR)) + 1);
		wsprintf(szFormatBuf, L"ドメイン : %s", szBuf);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szFormatBuf);

		lstrcpyn(szBuf, pLogonSessionData->LogonServer.Buffer, (pLogonSessionData->LogonServer.Length / sizeof(WCHAR)) + 1);
		wsprintf(szFormatBuf, L"認証されたサーバー : %s", szBuf);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szFormatBuf);

		lstrcpyn(szBuf, pLogonSessionData->AuthenticationPackage.Buffer, (pLogonSessionData->AuthenticationPackage.Length / sizeof(WCHAR)) + 1);
		wsprintf(szFormatBuf, L"認証パッケージ : %s", szBuf);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szFormatBuf);
		
		switch (pLogonSessionData->LogonType) {
		case UndefinedLogonType:
			lstrcpy(szLogonType, L"UndefinedLogonType");
			break;
		case Interactive:
			lstrcpy(szLogonType, L"Interactive");
			break;
		case Network:
			lstrcpy(szLogonType, L"Network");
			break;
		case Service:
			lstrcpy(szLogonType, L"Service");
			break;
		default:
			lstrcpy(szLogonType, L"");
			break;
		}
		wsprintf(szBuf, L"ログオンタイプ : %s %d", szLogonType, pLogonSessionData->LogonType);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		if (ConvertSidToStringSid(pLogonSessionData->Sid, &lpszSid)) {
			wsprintf(szBuf, L"SID : %s", lpszSid);
			SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
			LocalFree(lpszSid);
		}
		
		FileTimeToLocalFileTime((LPFILETIME)&pLogonSessionData->LogonTime, &fileTime);
		FileTimeToSystemTime(&fileTime, &systemTime);
		wsprintf(szBuf, L"ログオン時間 %02d : %02d", systemTime.wHour, systemTime.wSecond);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		LsaFreeReturnBuffer(pLogonSessionData);

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 3, 0, LOWORD(lParam) - (LOWORD(lParam) / 3), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		LsaFreeReturnBuffer(pLogonSessionList);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_CREATEでは、LsaEnumerateLogonSessionsを呼び出して、 ログオンセッションの数と識別子のリストを取得しています。 そして、この数だけLsaGetLogonSessionDataを呼び出し、 UserNameメンバの文字列を左のリストボックスに選択しています。 UACなどで制限されたユーザーはサービスなどのデータを取得できないため、 LsaGetLogonSessionDataの戻り値は確認するようにします。 左のリストボックスが選択されるとWM_COMMANDが送られ、 選択されたユーザーが所有するログオンセッションの各データが、 右のリストボックスに追加されます。 SIDの表示に関しては文字列に変換するために、ConvertSidToStringSidを呼び出しています。 sddl.hをインクルードしているのは、この関数のためです。 ログオン時間に関しては、FileTimeToLocalFileTimeとFileTimeToSystemTimeを呼び出すことでシステム時刻を取得しています。

NetWkstaUserEnumによる列挙

現在ログオンしているユーザーの情報は、NetWkstaUserEnumでも取得することができます。 この関数は、LsaEnumerateLogonSessionsのようにサービスの情報を列挙することができず、 LsaGetLogonSessionDataと比べて取得できるデータ量は少なくなっていますが、 ログオンしているユーザーの名前だけを列挙したいような場合は便利な関数といえます。 次に、例を示します。

#include <windows.h>
#include <lm.h>

#pragma comment (lib, "netapi32.lib")

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND                hwndListBoxLeft = NULL;
	static HWND                hwndListBoxRight = NULL;
	static LPWKSTA_USER_INFO_1 lpWkstaUserInfo = NULL;

	switch (uMsg) {
	
	case WM_CREATE: {
		DWORD i;
		DWORD dwEntryCount;
		DWORD dwTotalEntries;
		DWORD dwResult;

		hwndListBoxLeft = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBoxRight = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_NOTIFY, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		dwResult = NetWkstaUserEnum(NULL, 1, (LPBYTE *)&lpWkstaUserInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL);
		if (dwResult != NERR_Success)
			return -1;

		for (i = 0; i < dwEntryCount; i++)
			SendMessage(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)lpWkstaUserInfo[i].wkui1_username);

		return 0;
	}
	
	case WM_COMMAND: {
		WCHAR szBuf[256];
		DWORD dwIndex;

		if ((HWND)lParam == hwndListBoxRight || HIWORD(wParam) != LBN_SELCHANGE)
			return 0;
		
		SendMessage(hwndListBoxRight, LB_RESETCONTENT, 0, 0);

		dwIndex = (DWORD)SendMessage(hwndListBoxLeft, LB_GETCURSEL, 0, 0);

		wsprintf(szBuf, L"ドメイン : %s", lpWkstaUserInfo[dwIndex].wkui1_logon_domain);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, L"認証されたサーバー : %s", lpWkstaUserInfo[dwIndex].wkui1_logon_server);
		SendMessage(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBoxLeft, 0, 0, LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBoxRight, LOWORD(lParam) / 3, 0, LOWORD(lParam) - (LOWORD(lParam) / 3), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (lpWkstaUserInfo != NULL)
			NetApiBufferFree(lpWkstaUserInfo);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

NetWkstaUserEnumの第1引数は、列挙の対象とするサーバー名ですが、これはNULLで構いません。 第2引数は取得するデータのレベルであり、ここでは1を指定します。 1を指定した場合は、第3引数からWKSTA_USER_INFO_1構造体を受け取ることになります。 第4引数にはログオンしているユーザーの数が返るため、 この数だけループ処理を実行し、 ログオンしているユーザーの名前を左のリストボックスに追加します。 これが選択された場合は、WKSTA_USER_INFO_1構造体の残りのメンバを表示します。



戻る