利用WNet系列函数枚举和映射网络资源

微软的WNet系列函数能搜索和访问本地网络上的共享资源,并能将文件夹映射为本地的网络驱动器。

下面对函数的用法和注意点进行一个总结。首先是对整个网络进行一个枚举,而本地网络又分为Microsoft Terminal Service,Microsfot Windows Network,Web Client Network。

枚举的时候会分别搜索这3类网络下面的子类。Microsfot Windows Network下面又包含了多个WorkGroup和域,每个WorkGroup和域以及共享服务器都是一个节点,只是他们的类型不同。


MSDN的代码展示了最基本的枚举所有节点的功能:

BOOL WINAPI EnumerateFunc(HWND hwnd,
HDC hdc,
LPNETRESOURCE lpnr)
{
DWORD dwResult, dwResultEnum;
HANDLE hEnum;
DWORD cbBuffer = 16384; // 16K is a good size
DWORD cEntries = -1; // enumerate all possible entries
LPNETRESOURCE lpnrLocal; // pointer to enumerated structures
DWORD i;
//
// Call the WNetOpenEnum function to begin the enumeration.
//
dwResult = WNetOpenEnum(RESOURCE_GLOBALNET, // all network resources
RESOURCETYPE_ANY, // all resources
0, // enumerate all resources
lpnr, // NULL first time the function is called
&hEnum); // handle to the resource
if (dwResult != NO_ERROR)
{
//
// Process errors with an application-defined error handler.
//
NetErrorHandler(hwnd, dwResult, (LPSTR)"WNetOpenEnum");
return FALSE;
}
//
// Call the GlobalAlloc function to allocate resources.
//
lpnrLocal = (LPNETRESOURCE) GlobalAlloc(GPTR, cbBuffer);
if (lpnrLocal == NULL)
return FALSE;
do
{
//
// Initialize the buffer.
//
ZeroMemory(lpnrLocal, cbBuffer);
//
// Call the WNetEnumResource function to continue
// the enumeration.
//
dwResultEnum = WNetEnumResource(hEnum, // resource handle
&cEntries, // defined locally as -1
lpnrLocal, // LPNETRESOURCE
&cbBuffer); // buffer size
//
// If the call succeeds, loop through the structures.
//
if (dwResultEnum == NO_ERROR)
{
for(i = 0; i < cEntries; i++)
{
// Call an application-defined function to
// display the contents of the NETRESOURCE structures.
//
DisplayStruct(hdc, &lpnrLocal[i]);
// If the NETRESOURCE structure represents a container resource,
// call the EnumerateFunc function recursively.
if(RESOURCEUSAGE_CONTAINER == (lpnrLocal[i].dwUsage
& RESOURCEUSAGE_CONTAINER))
if(!EnumerateFunc(hwnd, hdc, &lpnrLocal[i]))
TextOut(hdc, 10, 10, "EnumerateFunc returned FALSE.", 29);
}
}
// Process errors.
//
else if (dwResultEnum != ERROR_NO_MORE_ITEMS)
{
NetErrorHandler(hwnd, dwResultEnum, (LPSTR)"WNetEnumResource");
break;
}
}
//
// End do.
//
while(dwResultEnum != ERROR_NO_MORE_ITEMS);
//
// Call the GlobalFree function to free the memory.
//
GlobalFree((HGLOBAL)lpnrLocal);
//
// Call WNetCloseEnum to end the enumeration.
//
dwResult = WNetCloseEnum(hEnum);
if(dwResult != NO_ERROR)
{
//
// Process errors.
//
NetErrorHandler(hwnd, dwResult, (LPSTR)"WNetCloseEnum");
return FALSE;
}
return TRUE;
}

函数通过递归的方式,遍历WorkGroup和域下面的所有共享服务器的共享资源。但这段代码需要改进的有2个地方,WNetOpenEnum用于打开一个节点,如果是工作组是不需要认证的,但对于共享服务器如samba服务器来说,直接打开是会造成失败的,应为samba需要进行认证,这时候可以调用另外一个函数ConnectWNetResource来通过认证,代码也源自MSDN:

DWORD  ConnectWNetResource(LPNETRESOURCE lpnr,PTCHAR szUserName,PTCHAR szPassword)
{
DWORD dwResult; 
//
// Call the WNetAddConnection2 function to make the connection,
//   specifying a persistent connection.
//
dwResult = WNetAddConnection2(lpnr, // NETRESOURCE from enumeration 
(LPWSTR) szPassword,                  // no password 
(LPWSTR) szUserName,                  // logged-in user 
CONNECT_UPDATE_PROFILE);       // update profile with connect information 

// Process errors.
//  The local device is already connected to a network resource.
//
if (dwResult == ERROR_ALREADY_ASSIGNED) 

return FALSE; 


//  An entry for the local device already exists in the user profile.
//
else if (dwResult == ERROR_DEVICE_ALREADY_REMEMBERED) 

return FALSE; 

else if(dwResult != NO_ERROR) 

return FALSE; 

//
// Otherwise, report a successful connection.
//


    return 0;
}


WNetAddConnection2的作用就是连接一个指定的需要认证的服务器或者共享资源。第一个参数指定了共享资源的位置和类型,具体内容就不详述了,重点是,共享服务器的位置可以用其注册名字,如\\XX-PC来表示,也可以用其\\+IP地址来指定。值得注意的是,第三个参数dwFlag必须设置成CONNECT_UPDATE_PROFILE,这样本地系统才能缓存这个认证信息,使后面的访问操作不再重复认证。


这样上面那段代码应修改成这样:

dwResult = WNetOpenEnum(RESOURCE_GLOBALNET, // all network resources
RESOURCETYPE_ANY,   // all resources
RESOURCEUSAGE_ALL,        // enumerate all resources
lpnr,     // NULL first time the function is called
&hEnum);  // handle to the resource


if (dwResult != NO_ERROR)
{  
//
// Process errors with an application-defined error handler.
//
//NetErrorHandler(hwnd, dwResult, (LPWSTR)_T("WNetOpenEnum"));
 
if(dwResult == ERROR_ACCESS_DENIED){

if(ConnectWNetResource(lpnr,user,pwd) == FALSE)
return FALSE;


dwResult = WNetOpenEnum(RESOURCE_GLOBALNET, // all network resources
RESOURCETYPE_ANY,   // all resources
RESOURCEUSAGE_ALL,        // enumerate all resources
lpnr,     // NULL first time the function is called
&hEnum);  // handle to the resource

if (dwResult != NO_ERROR){
return FALSE;
}
}else{
return FALSE;
}
}


通过遍历得到的一个工作组或者域,通过调用WNetEnumResource来取得他下面包含的所有共享服务器,第一个参数是WNetOpenEnum获得的句柄,第二个参数会返回共享服务器的总数,第三个参数返回LPNETRESOURCE结构体的数组,每个元素对应一个服务器,现在我们来看看这个结构体的具体内容:

typedef struct  _NETRESOURCEW {
    DWORD    dwScope;
    DWORD    dwType;
    DWORD    dwDisplayType;
    DWORD    dwUsage;
    LPWSTR   lpLocalName;
    LPWSTR   lpRemoteName;
    LPWSTR   lpComment ;
    LPWSTR   lpProvider;
}NETRESOURCEW, *LPNETRESOURCEW;

dwDisplayType表示这个节点的类型,可以是服务器或域,工作组或者文件夹。dwType表示共享类型,打印机或者磁盘。dwUsage表示这个节点是可映射的还是一个容器。文件夹属于可映射的,而服务器,域和工作组都属于容器,无法映射。

如果是服务器类型节点,lpRemoteName表示服务器的名字,注意这个名字是唯一的在网络中标识此计算机的名字。如果需要,可调用getaddrinfo将这个网络名解析为此计算机的IP地址,对于文件夹类型的节点,则表示文件夹的具体路径。


如果你需要把文件夹映射成本地网络磁盘,那么必须设置lpLocalName,应该是这样的格式 X:,表示你想把该文件夹映射成本地的Z盘。实现的函数大致如下:

DWORD WINAPI AssigningDriveToShare(LPNETRESOURCE lpnr,PTCHAR szUserName,PTCHAR szPassword,PTCHAR szLocalName)
{
  
//lpnr->dwType = RESOURCETYPE_ANY;
lpnr->lpLocalName = szLocalName;
lpnr->lpProvider = NULL;
//
// Call the WNetAddConnection2 function to assign
//   a drive letter to the share.
//
///tmp/disk/app_path
DWORD res = WNetAddConnection2(lpnr, szPassword, szUserName, CONNECT_UPDATE_PROFILE);
//
// If the call succeeds, inform the user; otherwise,
//  print the error.
//
if(res == NO_ERROR){
//TCHAR buffer[MAX_PATH];
//::wsprintf(buffer,_T("Connection added %S\n"), lpnr->lpRemoteName);
   //MessageBox(NULL,buffer,0,0);
 
}
else{
 
if(res == ERROR_SESSION_CREDENTIAL_CONFLICT){
    return -1;
}
return 0;


}

实际上调用的还是WNetAddConnection2,需要单独注意的是,如果返回的错误代码是ERROR_SESSION_CREDENTIAL_CONFLICT,说明你可能通过手动的方式打开了被映射的目录,这时候映射操作会失败。


最后要说的是,通过这种枚举的方式找到你需要的资源,中间过程相对来说是比较慢的。如果你已经知道具体服务器的IP地址,那么可以把lpRemoteName设置成以字符串形式表示的IP地址,具体的配置如下:

nrLocal.dwDisplayType = RESOURCEDISPLAYTYPE_SERVER;
nrLocal.dwScope = RESOURCE_GLOBALNET;
nrLocal.dwType = RESOURCETYPE_ANY;
nrLocal.dwUsage = RESOURCEUSAGE_CONTAINER;
nrLocal.lpComment = NULL;
nrLocal.lpLocalName = NULL;
nrLocal.lpRemoteName = RemoteName;
nrLocal.lpProvider = NULL;


并调用EnumerateFunc,就能直接返回该服务器下面的所有共享文件夹,后面即可将其中的任意一个或者多个文件夹映射成本地磁盘了。

你可能感兴趣的:(利用WNet系列函数枚举和映射网络资源)