void zen::loginNetworkShare(const Zstring& dirpathOrig, bool allowUserInteraction) //throw() - user interaction: show OS password prompt { /* ATTENTION: it is not safe to retrieve UNC path via ::WNetGetConnection() for every type of network share: network type |::WNetGetConnection rv | lpRemoteName | existing UNC path -----------------------------|-------------------------|---------------------------------|---------------- inactive local network share | ERROR_CONNECTION_UNAVAIL| \\192.168.1.27\new2 | YES WebDrive | NO_ERROR | \\Webdrive-ZenJu\GNU | NO Box.net (WebDav) | NO_ERROR | \\www.box.net\DavWWWRoot\dav | YES NetDrive | ERROR_NOT_CONNECTED | <empty> | NO ____________________________________________________________________________________________________________ Windows Login Prompt Naming Conventions: network share: \\<server>\<share> e.g. \\WIN-XP\folder or \\192.168.1.50\folder user account: <Domain>\<user> e.g. WIN-XP\Zenju or 192.168.1.50\Zenju Windows Command Line: - list *all* active network connections, including deviceless ones which are hidden in Explorer: net use - delete active connection: net use /delete \\server\share ____________________________________________________________________________________________________________ Scenario: XP-shared folder is accessed by Win 7 over LAN with access limited to a certain user Problems: I. WNetAddConnection2() allows (at least certain) invalid credentials (e.g. username: a/password: a) and establishes an *unusable* connection II. WNetAddConnection2() refuses to overwrite an existing (unusable) connection created in I), but shows prompt repeatedly III. WNetAddConnection2() won't bring up the prompt if *wrong* credentials had been entered just recently, even with CONNECT_INTERACTIVE specified! => 2-step proccess */ auto connect = [&](NETRESOURCE& trgRes) //blocks heavily if network is not reachable!!! { //1. first try to connect without user interaction - blocks! DWORD rv = ::WNetAddConnection2(&trgRes, //__in LPNETRESOURCE lpNetResource, nullptr, //__in LPCTSTR lpPassword, nullptr, //__in LPCTSTR lpUsername, 0); //__in DWORD dwFlags //53L ERROR_BAD_NETPATH The network path was not found. //67L ERROR_BAD_NET_NAME //86L ERROR_INVALID_PASSWORD //1219L ERROR_SESSION_CREDENTIAL_CONFLICT Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared resource and try again. //1326L ERROR_LOGON_FAILURE Logon failure: unknown user name or bad password. //1236L ERROR_CONNECTION_ABORTED if (somethingExists(trgRes.lpRemoteName)) //blocks! return; //success: connection usable! -> don't care about "rv" if (rv == ERROR_BAD_NETPATH || //like ERROR_PATH_NOT_FOUND rv == ERROR_BAD_NET_NAME|| //like ERROR_FILE_NOT_FOUND rv == ERROR_CONNECTION_ABORTED) //failed to connect to a network that existed not too long ago; will later return ERROR_BAD_NETPATH return; //no need to show a prompt for an unreachable network device //2. if first attempt failed, we need to *force* prompt by using CONNECT_PROMPT if (allowUserInteraction) { //avoid problem II.) DWORD rv2= ::WNetCancelConnection2(trgRes.lpRemoteName, //_In_ LPCTSTR lpName, 0, //_In_ DWORD dwFlags, true); //_In_ BOOL fForce //2250L ERROR_NOT_CONNECTED //enforce login prompt DWORD rv3 = ::WNetAddConnection2(&trgRes, // __in LPNETRESOURCE lpNetResource, nullptr, // __in LPCTSTR lpPassword, nullptr, // __in LPCTSTR lpUsername, CONNECT_INTERACTIVE | CONNECT_PROMPT); //__in DWORD dwFlags (void)rv2; (void)rv3; } }; Zstring dirpath = removeLongPathPrefix(dirpathOrig); trim(dirpath, true, false); //1. locally mapped network share if (dirpath.size() >= 2 && iswalpha(dirpath[0]) && dirpath[1] == L':') { Zstring driveLetter(dirpath.c_str(), 2); //e.g.: "Q:" { DWORD bufferSize = 10000; std::vector<wchar_t> remoteNameBuffer(bufferSize); //map local -> remote //note: following function call does NOT block! DWORD rv = ::WNetGetConnection(driveLetter.c_str(), //__in LPCTSTR lpLocalName in the form "<driveletter>:" &remoteNameBuffer[0], //__out LPTSTR lpRemoteName, &bufferSize); //__inout LPDWORD lpnLength if (rv == ERROR_CONNECTION_UNAVAIL) //remoteNameBuffer will be filled nevertheless! { //ERROR_CONNECTION_UNAVAIL: network mapping is existing, but not connected Zstring networkShare = &remoteNameBuffer[0]; if (!networkShare.empty()) { NETRESOURCE trgRes = {}; trgRes.dwType = RESOURCETYPE_DISK; trgRes.lpLocalName = const_cast<LPWSTR>(driveLetter.c_str()); //lpNetResource is marked "__in", seems WNetAddConnection2 is not const correct! trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); // connect(trgRes); //blocks! } } } } //2. deviceless network connection else if (startsWith(dirpath, L"\\\\")) //UNC path { const Zstring networkShare = [&]() -> Zstring //extract prefix "\\server\share" { size_t pos = dirpath.find('\\', 2); if (pos == Zstring::npos) return Zstring(); pos = dirpath.find('\\', pos + 1); return pos == Zstring::npos ? dirpath : Zstring(dirpath.c_str(), pos); }(); if (!networkShare.empty()) { //::WNetGetResourceInformation seems to fail with ERROR_BAD_NET_NAME even for existing unconnected network shares! // => unconditionally try to connect to network share, seems we cannot reliably detect connection status otherwise NETRESOURCE trgRes = {}; trgRes.dwType = RESOURCETYPE_DISK; trgRes.lpRemoteName = const_cast<LPWSTR>(networkShare.c_str()); //trgRes is "__in" connect(trgRes); //blocks! } } }