EternalWindows
共有管理 / 共有フォルダの作成

フォルダやプリンタといった共有可能なリソースをアプリケーションから管理するには、 Network Share Management関数を呼び出すことになります。 今回は、特定のリソースを共有リソースに設定するNetShareAddを取り上げます。

NET_API_STATUS NetShareAdd(
  LPWSTR servername,
  DWORD level,
  LPBYTE buf,
  LPDWORD parm_err
);

servernameは、servernameは、この関数を実行するサーバー名を指定します。 NULLを指定した場合は、ローカルコンピュータで実行されます。 levelは、情報レベルを指定します。 bufは、共有情報を格納したバッファを指定します。 parm_errは、エラーの原因となったメンバのインデックスを受け取る変数のアドレスを指定します。 どのメンバに問題があるのかを特定したくない場合は、NULLを指定しても問題ありません。

情報レベルに2を指定した場合は、SHARE_INFO_2構造体を使用することになります。

typedef struct _SHARE_INFO_2 {
  LPWSTR shi2_netname;
  DWORD  shi2_type;
  LPWSTR shi2_remark;
  DWORD  shi2_permissions;
  DWORD  shi2_max_uses;
  DWORD  shi2_current_uses;
  LPWSTR shi2_path;
  LPWSTR shi2_passwd;
} SHARE_INFO_2, *PSHARE_INFO_2, *LPSHARE_INFO_2;

shi2_netnameは、共有名を指定します。 shi2_typeは、共有されるリソースのタイプを指定します。 リソースがフォルダである場合は、STYPE_DISKTREEを指定します。 shi2_remarkは、共有されるリソースのコメント文字列を指定します。 不要な場合は、NULLで問題ありません。 shi2_permissionsは、共有レベルというセキュリティで動作しているサーバーに対して、意味のある値を指定します。 共有リソースに対してアクセスするサーバーがWindowsだけである場合は、0で問題ありません。 shi2_max_usesは、共有リソースに接続できる最大のユーザー数を指定します。 SHI_USES_UNLIMITED(-1)を指定した場合は、無制限となります。 shi2_current_usesは、共有リソースに接続しているユーザー数を指定します。 shi2_pathは、共有リソースの実体を示すパスを指定します。 shi2_passwdは、共有リソースに設定するパスワードを指定します。

共有されたリソースを共有されていない状態に変更するには、NetShareDelを呼び出します。

NET_API_STATUS NetShareDel(
 LMSTR servername,
 LMSTR netname,
 DWORD reserved
)

servernameは、servernameは、この関数を実行するサーバー名を指定します。 NULLを指定した場合は、ローカルコンピュータで実行されます。 netnameは、共有名を指定します。 関数が成功すると、この共有名は参照することができなくなります。 reservedは、予約されているため0を指定します。 levelは、情報レベルを指定します。

今回のプログラムは、指定されたフォルダを共有フォルダに変更します。 NetShareAddとNetShareDelを呼び出す関係上、管理者として実行する必要があります。

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

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

BOOL ModifySharedSecurity(LPWSTR lpszFilePath);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BOOL         bShareAdd = TRUE;
	WCHAR        szShareName[] = L"share";
	WCHAR        szFolderPath[] = L"C:\\sample";
	DWORD        dwResult;
	SHARE_INFO_2 shareInfo;

	if (bShareAdd) {	
		shareInfo.shi2_netname      = szShareName;
		shareInfo.shi2_type         = STYPE_DISKTREE;
		shareInfo.shi2_remark       = NULL;
		shareInfo.shi2_permissions  = 0;
		shareInfo.shi2_max_uses     = SHI_USES_UNLIMITED;
		shareInfo.shi2_current_uses = 0;
		shareInfo.shi2_path         = szFolderPath;
		shareInfo.shi2_passwd       = NULL;
		
		dwResult = NetShareAdd(NULL, 2, (LPBYTE)&shareInfo, NULL);
		if (dwResult != NERR_Success) {
			if (dwResult == NERR_DuplicateShare)
				MessageBox(NULL, TEXT("フォルダは既に共有されています。"), NULL, MB_ICONWARNING);
			else
				MessageBox(NULL, TEXT("フォルダの共有に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
		
		MessageBox(NULL, TEXT("フォルダを共有しました。"), TEXT("OK"), MB_OK);
		
		if (!ModifySharedSecurity(szFolderPath))
			MessageBox(NULL, TEXT("セキュリティの修正に失敗しました。"), TEXT("OK"), MB_OK);

		SHChangeNotify(SHCNE_NETSHARE, SHCNF_PATH, szFolderPath, NULL);
	}
	else {
		dwResult = NetShareDel(NULL, szShareName, 0);
		if (dwResult != NERR_Success) {
			if (dwResult == NERR_NetNameNotFound)
				MessageBox(NULL, TEXT("存在しない共有名です。"), NULL, MB_ICONWARNING);
			else
				MessageBox(NULL, TEXT("共有の削除に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
		
		MessageBox(NULL, TEXT("フォルダの共有を削除しました。"), TEXT("OK"), MB_OK);

		SHChangeNotify(SHCNE_NETUNSHARE, SHCNF_PATH, szFolderPath, NULL);
	}

	return 0;
}

BOOL ModifySharedSecurity(LPWSTR lpszFolderPath)
{
	PACL            pDacl;
	EXPLICIT_ACCESS explicitAccess;

	BuildExplicitAccessWithName(&explicitAccess, TEXT("Guest"), GENERIC_READ | GENERIC_EXECUTE, GRANT_ACCESS, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE);
	SetEntriesInAcl(1, &explicitAccess, 0, &pDacl);

	if (SetNamedSecurityInfo(szFolderPath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pDacl, NULL) != ERROR_SUCCESS) {
		LocalFree(pDacl);
		return FALSE;
	}

	LocalFree(pDacl);

	return TRUE;
}

bShareAddがTRUEの場合は、NetShareAddを呼び出して、 szFolderPathで識別されるフォルダにszShareNameの共有名を設定します。 これにより、c:\sampleというフォルダを、これからは\\RemoteName\shareというように識別できることになります。 つまり、共有フォルダへアクセスする側は、 フォルダの実体がどこにあるのかを知っておく必要はなく、 リモートコンピュータの名前と共有名を知っておくだけで、共有フォルダにアクセスできるようになります。

ModifySharedSecurityは、共有フォルダにGuestアカウントがアクセスできるように、 セキュリティ記述子を修正する関数です。 共有フォルダへアクセスする側が、リモートコンピュータ上にログオンしていない場合は、 Guestアカウントしてアクセスが行われることになるため、 このような場合でもアクセスが成功するようにしておきます。 情報レベルが502であるSHARE_INFO_502構造体には、セキュリティ記述子を受け取るメンバがありますが、 このメンバにNULLを指定しても適切なセキュリティ記述子を指定しても、 共有フォルダのセキュリティ記述子は変化しないため、 SetNamedSecurityInfoによる明示的な設定が必要になります。 SetNamedSecurityInfoには、第1引数に共有名を指定して第2引数にSE_LMSHAREを指定することもできそうですが、 何故か共有フォルダのセキュリティ記述子は変化しないようです。

フォルダを共有フォルダに変更した場合は、エクスプローラ上のフォルダのアイコンも変化しなければなりませんが、 これは直ちには行われません。 理由は、エクスプローラがフォルダの変更を検出していないからです。 よって、シェル(エクスプローラ)に通知を行うSHChangeNotifyを呼び出すことになります。 第1引数にSHCNE_NETSHAREを指定すれば、共有フォルダの変更を通知する意味となるため、 第2引数にファイルパスの指定を示すSHCNF_PATHを指定し、 第3引数にファイルパスを指定します。 SHChangeNotifyの呼び出しには、shlobj.hのインクルードとshell32.libへのリンクが必要になります。

ShowShareFolderUIについて

フォルダを共有フォルダに設定するにはNetShareAddを呼び出しますが、 これをUI経由で行いたい場合はShowShareFolderUIを呼び出すことになります。 この関数は、共有用シェル拡張DLLであるntshrui.dllに実装されており、 LoadLibraryとGetProcAddressで明示的にリンクする必要があります。

#include <windows.h>

typedef HRESULT (STDAPICALLTYPE *LPFNSHOWSHAREFOLDERUI)(HWND, LPCWSTR);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HMODULE               hmod;
	HRESULT               hr;
	LPFNSHOWSHAREFOLDERUI lpfnShowShareFolderUI;
	
	hmod = LoadLibrary(TEXT("ntshrui.dll"));
	if (hmod == NULL) {
		MessageBox(NULL, TEXT("ntshrui.dllが見つかりません。"), NULL, MB_ICONWARNING);
		return 0;
	}

	lpfnShowShareFolderUI = (LPFNSHOWSHAREFOLDERUI)GetProcAddress(hmod, "ShowShareFolderUI");
	if (lpfnShowShareFolderUI == NULL) {
		MessageBox(NULL, TEXT("関数がエクスポートされていません。"), NULL, MB_ICONWARNING);
		FreeLibrary(hmod);
		return 0;
	}
	
	CoInitialize(NULL);

	hr = lpfnShowShareFolderUI(NULL, L"C:\\sample");
	if (hr == S_OK)
		MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("関数の実行に失敗しました。"), NULL, MB_ICONWARNING);

	CoUninitialize();

	return 0;
}

ShowShareFolderUIの第1引数はウインドウハンドルを指定できますが、 NULLを指定しても問題ありません。 第2引数は、共有フォルダに設定したいフォルダのパスを指定します。 既に共有されているフォルダを指定しても問題ありません。 ShowShareFolderUIを呼び出すには、事前にCoInitializeを呼び出す必要があります。

ShowShareFolderUIが表示するダイアログは、モードレスダイアログです。 つまり、ユーザーがダイアログに応答する前に制御を返すことになりますから、 上記のようなウインドウを持たないアプリケーションは、この関数を呼び出すべきではありません。 上記コードでは、ShowShareFolderUIが制御を返すと同時にアプリケーションが終了しないよう、 意図的にMessageBoxを呼び出しています。



戻る