EternalWindows
共有管理 / セッションとファイルの列挙

共有フォルダに対して接続を確立した場合は、 その接続を確立したユーザーのセッションも作成されることになります。 セッションを列挙するには、NetSessionEnumを呼び出します。

NET_API_STATUS NetSessionEnum(
  LPWSTR servername,
  LPWSTR UncClientName,
  LPWSTR username,
  DWORD level,
  LPBYTE *bufptr,
  DWORD prefmaxlen,
  LPDWORD entriesread,
  LPDWORD totalentries,
  LPDWORD resume_handle
);

servernameは、この関数を実行するサーバーの名前を指定します。 NULLを指定した場合は、ローカルコンピュータで実行されます。 UncClientNameは、特定のコンピュータのセッションを列挙したい場合に指定します。 NULLを指定した場合は、全てのコンピュータのセッションが列挙されます。 usernameは、特定のユーザーのセッションを列挙したい場合に指定します。 NULLを指定した場合は、全てのユーザーのセッションが列挙されます。 levelは、情報レベルを指定します。 bufptrは、バッファへのアドレスを受け取る変数のアドレスを指定します。 このバッファに、列挙された共有情報が格納されます。 prefmaxlenは、取得したいデータのサイズを指定します。 全てのデータを取得したい場合は、MAX_PREFERRED_LENGTHを指定します。 entriesreadは、列挙されたエントリの数を受け取る変数のアドレスを指定します。 totalentriesは、全てのエントリの数を受け取る変数のアドレスを指定します。 resume_handleは、レジュームハンドルを受け取る変数のアドレスを指定します。 通常は必要がないため、NULLを指定します。

NetSessionEnumの第4引数に1を指定した場合は、 第5引数にSESSION_INFO_1構造体が返ることになります。

typedef struct _SESSION_INFO_1 {
  LPWSTR sesi1_cname;
  LPWSTR sesi1_username;
  DWORD  sesi1_num_opens;
  DWORD  sesi1_time;
  DWORD  sesi1_idle_time;
  DWORD  sesi1_user_flags;
} SESSION_INFO_1, *PSESSION_INFO_1, *LPSESSION_INFO_1;

sesi1_cnameは、セッションを確立したコンピュータの名前が格納されます。 sesi1_usernameは、セッションを確立したユーザーの名前が格納されます。 sesi1_num_opensは、現在オープンしているファイル数が格納されます。 sesi1_timeは、セッションの接続時間が格納されます。 sesi1_idle_timeは、セッションのアイドル時間が格納されます。 sesi1_user_flagsは、セッションの確立方法を表す定数が格納されます。 SESS_GUESTという定数が定義されていますが、このゲストというのが、 Guestカウントによる確立を意味するのか、リモートからの確立を意味するのかはよく分かりません。 なお、セッションには接続した共有名が含まれないことに注意してください。

接続及びセッションを確立したユーザーによってオープンされたファイルは、 NetFileEnumで列挙することができます。

NET_API_STATUS NetFileEnum(
  LMSTR servername,
  LMSTR basepath,
  LMSTR username,
  DWORD level,
  LPBYTE *bufptr,
  DWORD prefmaxlen,
  LPDWORD entriesread,
  LPDWORD totalentries,
  PDWORD_PTR resume_handle
);

servernameは、この関数を実行するサーバーの名前を指定します。 NULLを指定した場合は、ローカルコンピュータで実行されます。 basepathは、特定のパスを含むパスのみを列挙したい場合に指定します。 NULLを指定した場合は、全てのパスが対象になります。 usernameは、特定のユーザーがオープンしているファイルを列挙したい場合に指定します。 NULLを指定した場合は、ユーザーを問わず、オープンされている全てのファイルが列挙されます。 levelは、情報レベルを指定します。 bufptrは、バッファへのアドレスを受け取る変数のアドレスを指定します。 このバッファに、列挙された共有情報が格納されます。 prefmaxlenは、取得したいデータのサイズを指定します。 全てのデータを取得したい場合は、MAX_PREFERRED_LENGTHを指定します。 entriesreadは、列挙されたエントリの数を受け取る変数のアドレスを指定します。 totalentriesは、全てのエントリの数を受け取る変数のアドレスを指定します。 resume_handleは、レジュームハンドルを受け取る変数のアドレスを指定します。 通常は必要がないため、NULLを指定します。

NetFileEnumの第4引数に3を指定した場合は、 第5引数にFILE_INFO_3構造体が返ることになります。

typedef struct _FILE_INFO_3 {
  DWORD fi3_id;
  DWORD fi3_permissions;
  DWORD fi3_num_locks;
  LMSTR fi3_pathname;
  LMSTR fi3_username;
} FILE_INFO_3, *PFILE_INFO_3, *LPFILE_INFO_3;

fi3_idは、開かれたファイルを識別する数値が格納されます。 fi3_permissionsは、ファイルがどのように開かれたのかを示す定数が格納されます。 fi3_num_locksは、ファイルのロック数が格納されます。 fi3_pathnameは、オープンされたファイルのパスが格納されます。 fi3_usernameは、ファイルをオープンしたユーザーの名前が格納されます。

今回のプログラムは、現在確立されている全てのセッションを列挙し、 選択されたセッションの情報とオープンされたファイルを表示します。 プログラムは、管理者として実行する必要があります。

#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 PSESSION_INFO_1 pSessionInfo = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		DWORD i;
		DWORD dwEntryCount;
		DWORD dwTotalEntries;

		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);
		
		if (NetSessionEnum(NULL, NULL, 0, 1, (LPBYTE *)&pSessionInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL) != NERR_Success)
			return -1;

		for (i = 0; i < dwEntryCount; i++)
			SendMessageW(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)pSessionInfo[i].sesi1_cname);
		
		return 0;
	}
	
	case WM_COMMAND: {
		DWORD        i;
		DWORD        dwEntryCount;
		DWORD        dwTotalEntries;
		DWORD        dwIndex;
		DWORD        dwTime;
		WCHAR        szBuf[256];
		PFILE_INFO_3 pFileInfo;
		
		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", pSessionInfo[dwIndex].sesi1_username);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		dwTime = pSessionInfo[dwIndex].sesi1_time;
		wsprintf(szBuf, L"接続時間 : %02d : %02d", dwTime / 60, dwTime % 60);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		dwTime = pSessionInfo[dwIndex].sesi1_idle_time;
		wsprintf(szBuf, L"アイドル時間 : %02d : %02d", dwTime / 60, dwTime % 60);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, L"ゲスト : %d", pSessionInfo[dwIndex].sesi1_user_flags & SESS_GUEST);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, L"開いているファイルの数 : %d", pSessionInfo[dwIndex].sesi1_num_opens);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		if (pSessionInfo[dwIndex].sesi1_num_opens > 0) {
			if (NetFileEnum(NULL, NULL, pSessionInfo[dwIndex].sesi1_username, 3, (LPBYTE *)&pFileInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL) != NERR_Success)
				return 0;
			for (i = 0; i < dwEntryCount; i++)
				SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)pFileInfo[i].fi3_pathname);
			NetApiBufferFree(pFileInfo);
		}
		
		return 0;
	}

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

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

	default:
		break;

	}

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

WM_CREATEでは、NetSessionEnumを呼び出してセッションを確立したユーザー名を列挙しています。 セッションを確立するには、共有フォルダへのパスをエクスプローラのアドレスバーに入力したり、 WNetAddConnection2を呼び出したりすればよいでしょう。 WM_COMMANDでは、選択されたセッションの情報を表示するだけですが、 オープンしているファイルが存在する場合はそのファイルのパスを列挙しています。

if (pSessionInfo[dwIndex].sesi1_num_opens > 0) {
	if (NetFileEnum(NULL, NULL, pSessionInfo[dwIndex].sesi1_username, 3, (LPBYTE *)&pFileInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL) != NERR_Success)
		return 0;
	for (i = 0; i < dwEntryCount; i++)
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)pFileInfo[i].fi3_pathname);
	NetApiBufferFree(pFileInfo);
}

sesi1_num_opensが0よりも大きい場合は、NetFileEnumでオープンされているファイルを列挙します。 第3引数にユーザー名を指定しているため、 このユーザーがオープンしたファイルのみが列挙されることになります。

セッションの削除

共有フォルダに接続した場合は、その接続先コンピュータにセッションが作成されるわけですが これは接続先がNetSessionDelを呼び出すことで削除できるようにもなっています。 こうした事が不意に行われた場合、共有フォルダに接続している側には何らかの悪影響が及ぶことはあるのでしょうか。 また、NetFileCloseというオープンされたファイルを閉じる関数もありますが、 これについてはどうなのでしょうか。

結論から述べると、これらの関数を呼び出しても接続している側には特に影響がないように思えます。 たとえば、エクスプローラから共有フォルダをブラウジングしている場合、 仮に接続先でNetSessionDelが呼び出されたとしても、 ブラウジングが強制的に終了することはありませんし、これまで通りファイルをクリックしてオープンすることも可能です。 また、アプリケーションからWNetAddConnection2を呼び出して明示的に接続を行い、 途中でNetSessionDelを呼ばれたとしても、ファイルへのアクセスは行えることになります。 これは恐らく、セッションが削除されても、ログオンセッションが削除されていないからでしょう。 NetSessionEnumやNetFileEnumが削除されたセッションや閉じられたファイルを列挙しないことから、 共有フォルダを公開する側としては、目的の動作を正常に行えたように思えますが、実際にはそうではないようです。



戻る