EternalWindows
共有管理 / 共有フォルダの列挙

NetShareAddなどで共有名が設定された共有フォルダは、NetShareEnumで列挙することができます。

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

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

共有フォルダに対して接続が発生すると、 その接続に関する情報をNetConnectionEnumで列挙することができます。 これにより、接続を行ったユーザーを特定できることになります。

NET_API_STATUS NetConnectionEnum(
  LMSTR servername,
  LMSTR qualifier,
  DWORD level,
  LPBYTE *bufptr,
  DWORD prefmaxlen,
  LPDWORD entriesread,
  LPDWORD totalentries,
  LPDWORD resume_handle
);

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

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

typedef struct _CONNECTION_INFO_1 {
  DWORD coni1_id;
  DWORD coni1_type;
  DWORD coni1_num_opens;
  DWORD coni1_num_users;
  DWORD coni1_time;
  LMSTR coni1_username;
  LMSTR coni1_netname;
} CONNECTION_INFO_1, *PCONNECTION_INFO_1, *LPCONNECTION_INFO_1;

coni1_idは、接続を識別する数値が格納されます。 coni1_typeは、接続しているリソースのタイプを表す定数が格納されます。 coni1_num_opensは、接続によって開かれたファイル数が格納されます。 coni1_num_usersは、接続を行っているユーザー数が格納されます。 coni1_timeは、接続が確立されてからの秒数が格納されます。 coni1_usernameは、接続を行っているユーザーの名前が格納されます。 coni1_netnameは、NetConnectionEnumの第2引数によって内容が変化します。 第2引数に共有名を指定した場合は、接続を行ったコンピュータ名が格納され、 第2引数にコンピュータ名を指定した場合は、接続先となる共有名が格納されます。

今回のプログラムは、システムに存在する共有フォルダを列挙します。 また、接続されているフォルダについては、その接続したユーザーが列挙されます。 接続の列挙は、管理者として実行しなければ失敗することになります。

#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 PSHARE_INFO_2 pShareInfo = 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 (NetShareEnum(NULL, 2, (LPBYTE *)&pShareInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL) != NERR_Success) {
			
		TCHAR szBuf[256];
			wsprintf(szBuf, TEXT("%d"), GetLastError());
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			return -1;
		}

		for (i = 0; i < dwEntryCount; i++)
			SendMessageW(hwndListBoxLeft, LB_ADDSTRING, 0, (LPARAM)pShareInfo[i].shi2_netname);
		
		return 0;
	}
	
	case WM_COMMAND: {
		DWORD              i;
		DWORD              dwEntryCount;
		DWORD              dwTotalEntries;
		DWORD              dwIndex;
		WCHAR              szBuf[256];
		PCONNECTION_INFO_1 pConnectionInfo;
		
		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", pShareInfo[dwIndex].shi2_path);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		
		wsprintf(szBuf, L"コメント : %s", pShareInfo[dwIndex].shi2_remark);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		wsprintf(szBuf, L"最大の接続数 : %d", pShareInfo[dwIndex].shi2_max_uses);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		
		wsprintf(szBuf, L"現在のユーザー数 : %d", pShareInfo[dwIndex].shi2_current_uses);
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);

		switch (pShareInfo[dwIndex].shi2_type) {
		case STYPE_DISKTREE:
			lstrcpyW(szBuf, L"リソースタイプ : STYPE_DISKTREE");
			break;
		case STYPE_PRINTQ:
			lstrcpyW(szBuf, L"リソースタイプ : STYPE_PRINTQ");
			break;
		case STYPE_DEVICE:
			lstrcpyW(szBuf, L"リソースタイプ : STYPE_DEVICE");
			break;
		case STYPE_SPECIAL:
			lstrcpyW(szBuf, L"リソースタイプ : STYPE_SPECIAL");
			break;
		case STYPE_IPC | STYPE_SPECIAL:
			lstrcpyW(szBuf, L"リソースタイプ : STYPE_IPC | STYPE_SPECIAL");
			break;
		default:
			lstrcpyW(szBuf, L"リソースタイプ : ");
			break;
		}
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)szBuf);
		
		if (pShareInfo[dwIndex].shi2_current_uses > 0) {
			if (NetConnectionEnum(NULL, pShareInfo[dwIndex].shi2_netname, 1, (LPBYTE *)&pConnectionInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL) != NERR_Success)
				return 0;
			for (i = 0; i < dwEntryCount; i++)
				SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)pConnectionInfo[i].coni1_username);
			NetApiBufferFree(pConnectionInfo);
		}
		
		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 (pShareInfo != NULL)
			NetApiBufferFree(pShareInfo);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

WM_CREATEでは、情報レベルに2を指定してNetShareEnumを呼び出しています。 これにより、SHARE_INFO_2構造体へのアドレスを取得できるため、 エントリの数だけ共有名を左のリストボックスに追加します。 左のリストボックスに追加された共有名が選択すると、 WindowPrcにWM_COMMANDが送られ、右のリストボックスにその共有フォルダの情報が表示されます。 WM_COMMANDの主な処理は、SHARE_INFO_2構造体の中身を表示することですが、 実際に接続を行っているユーザーが存在する場合は、 そのユーザーを列挙しています。

if (pShareInfo[dwIndex].shi2_current_uses > 0) {
	if (NetConnectionEnum(NULL, pShareInfo[dwIndex].shi2_netname, 1, (LPBYTE *)&pConnectionInfo, MAX_PREFERRED_LENGTH, &dwEntryCount, &dwTotalEntries, NULL) != NERR_Success)
		return 0;
	for (i = 0; i < dwEntryCount; i++)
		SendMessageW(hwndListBoxRight, LB_ADDSTRING, 0, (LPARAM)pConnectionInfo[i].coni1_username);
	NetApiBufferFree(pConnectionInfo);
}

shi2_current_usesが0より大きいということは、 pShareInfo[dwIndex]で表される共有フォルダに接続しているユーザーが存在していることを意味します。 よって、共有名を表すshi2_netnameを指定してNetConnectionEnumを呼び出すことにより、 pShareInfo[dwIndex]に対して確率された接続を列挙します。 接続を行ったユーザー名は、pConnectionInfo[i].coni1_usernameから分かります。

一部の共有名には、名前の最後に$が追加されているものがありますが、 このような共有は管理共有と呼ばれています。 管理共有も通常の共有と同じように、他のコンピュータからのアクセスを許可するために存在しますが、 管理共有へアクセスする場合は、アクセス先の管理者アカウントのユーザー名とパスワードが要求されます。 つまり、誰でもアクセス可能な共有ということではなく、 そのコンピュータについて予め知っている管理者用の共有ということになります。 管理共有のフォルダには重要なファイルが格納されていることが多いため、 悪意のあるコンピュータからのアクセスを防ぐ意味で、 管理共有を無効にする場合もよくあります。 この場合は、次のレジストリキーを参照することになります。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters

上記レジストリキーにAutoShareWksエントリ(Server OSではAutoShareServer)を追加して値を0にした場合、 再起動後には管理共有が無効になります。 逆に、1を指定した場合は管理共有が有効になります。 ちなみに、Windows VistaでUACが有効になっている場合は、 上記エントリの値に関係なく管理共有へのアクセスは失敗します。


戻る