0

I am writing a program to map a network drive (WebDAV) from a graphical UI (Windows Forms).

I am using the Windows Networking API that lives in the mpr.dll.

Now, when the user enters a wrong password WNetAddConnection2 returns the NotAuthenticated result. So far so good. I throw a custom exception in my wrapper in this case. When the user then goes ahead and enters a correct password I still get a NotAuthenticated. Why is this?

Edit: Also interesting: If I set a breakpoint to the second try statement and let the debugger sit there for about 30 seconds the mapping works after I continue execution.

Edit 2: The problem only occurs under Windows 7 and 10

Here is some code to illustrate what I am doing


using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace WNetStackOverflow
{
    class Program
    {
        static void Main(string[] args)
        {
            char driveLetter = 'Z';
            string userName = "user";
            string password = "passwor";

            try
            {
                Console.WriteLine("First attempt (wrong pass)");
                WNetWrapper.CreateNewNetDrive(driveLetter, userName, password);
            }
            catch (WNetException e)
            {
                Console.WriteLine($"First attempt failed: {e.Error}");
                Console.WriteLine(e);
            }

            try
            {
                Console.WriteLine("Second attempt (correct pass)");
                WNetWrapper.CreateNewNetDrive(driveLetter, userName, password + "d");
                Console.WriteLine("Success!");
            }
            catch (WNetException e)
            {
                Console.WriteLine($"Second attempt failed: {e.Error}");
                Console.WriteLine(e);
            }

            Console.ReadKey();
        }
    }

    internal static class WNetWrapper
    {
        [DllImport("mpr.dll")]
        private static extern WNetError WNetAddConnection2(
            NetResource netResource,
            string password,
            string username,
            uint flags);

        [DllImport("mpr.dll")]
        public static extern int WNetGetLastError(
            ref int lpError,
            StringBuilder lpErrorBuf,
            int nErrorBufSize,
            StringBuilder lpNameBuf,
            int nNameBufSize);

        const string WebDavUri = "yourDomain.com";
        public static void CreateNewNetDrive(char driveLetter, string userName, string password)
        {
            var netResource = new NetResource
            {
                dwScope = ResourceScope.Globalnet,
                dwDisplayType = ResourceDisplayType.Share,
                dwType = ResourceType.Disk,
                lpRemoteName = $"https://{userName}.{WebDavUri}",
                lpLocalName = driveLetter + ":"
            };

            var returnCode = WNetAddConnection2(netResource, password, userName, 1);

            if (returnCode != WNetError.NoError)
            {
                var errorInformation = GetErrorInformation(returnCode);
                errorInformation.Add("DriveLetter", driveLetter.ToString());
                errorInformation.Add("UserName", userName);
                throw new WNetException("Failed to create net drive", returnCode, errorInformation);
            }
        }

        private static IDictionary GetErrorInformation(WNetError wNetError)
        {
            var data = new Dictionary<string, object>
            {
                {"ErrorCode", $"{wNetError} (ErrorCode {(int) wNetError})"}
            };

            var sbErrorBuf = new StringBuilder(500);
            var sbNameBuf = new StringBuilder(500);

            var errorNumber = 0;
            WNetGetLastError(ref errorNumber, sbErrorBuf, sbErrorBuf.Capacity, sbNameBuf, sbNameBuf.Capacity);

            if (errorNumber != 0)
            {
                data.Add("WNetLastErrorNumber", errorNumber);
                data.Add("WNetLastErrorMessage", sbErrorBuf);
            }

            Console.WriteLine($"ErrorNumber {errorNumber}, ErrorMessage {sbErrorBuf}");

            return data;
        }
    }

    internal enum WNetError
    {
        NoError = 0,
        BadNetName = 67,
        AlreadyAssigned = 85,
        NoMoreItems = 259,
        BadDevice = 1200,
        NotAuthenticated = 1244
    }

    internal class WNetException : Exception
    {
        public WNetError Error { get; }

        public WNetException(string message, WNetError error, IDictionary data) : base(message)
        {
            Error = error;

            foreach (var key in data.Keys)
            {
                Data[key] = data[key];
            }
        }
    }
    internal enum ResourceScope
    {
        Connected = 1,
        Globalnet,
        Remembered,
        Recent,
        Context
    }
    internal enum ResourceType
    {
        Any,
        Disk,
        Print,
        Reserved
    }

    [StructLayout(LayoutKind.Sequential)]
    internal class NetResource
    {
        public ResourceScope dwScope = 0;
        public ResourceType dwType = 0;
        public ResourceDisplayType dwDisplayType = 0;
        public ResourceUsage dwUsage = 0;
        public string lpLocalName;
        public string lpRemoteName;
        public string lpComment;
        public string lpProvider;
    }

    public enum ResourceDisplayType
    {
        Generic,
        Domain,
        Server,
        Share,
        File,
        Group,
        Network,
        Root,
        Shareadmin,
        Directory,
        Tree,
        Ndscontainer
    }

    [Flags]
    public enum ResourceUsage
    {
        Connectable = 0x00000001,
        Container = 0x00000002,
        Nolocaldevice = 0x00000004,
        Sibling = 0x00000008,
        Attached = 0x00000010,
        All = Connectable | Container | Attached
    }

}
ChrisM
  • 1,148
  • 8
  • 22
  • Hard to guess, nobody can run this code. The complaint sounds pretty far-fetched, consider swapping the two pieces of code so you can at least be sure that the presumed correct password is in fact correct. Not using CharSet = CharSet.Unicode in the DllImport declaration is a mistake. – Hans Passant Dec 08 '17 at 12:38
  • @HansPassant Do you have documentation at hand explaining the charset mistake? I can see changing it alters behaviour (even though the problem is not fixed) and I would like to do some further reading. – ChrisM Dec 08 '17 at 12:55
  • 1
    Not at hand, no. But standard in Windows, winapi functions that take string arguments have two versions. WNetAddConnection2A() takes 8-bit character strings, useful to old code that isn't Unicode-aware yet. WNetAddConnection2W() takes utf16 encoded strings, same encoding as System.String uses. When you don't override CharSet in the declaration then you get the "old code" version. And run the risk that the password cannot be encoded using Encoding.Default, thus always causes a mismatch. – Hans Passant Dec 08 '17 at 12:59
  • @HansPassant Okay, understood, thanks. BUT: I need to specify CharSet.None OR CharSet.Ansi in order for my code to work. Why is that? – ChrisM Dec 08 '17 at 13:06
  • Well, that is certainly very unhealthy. I suppose you are getting closer to the underlying problem, smells like a memory corruption problem. I don't see it in the snippet. – Hans Passant Dec 08 '17 at 13:14
  • @HansPassant I added the complete code, except for the username, password and real webdav url. Maybe you can see the problem there? Please also note my edited comment in the original question – ChrisM Dec 08 '17 at 13:19
  • 1
    The NetResource declaration needs CharSet as well and has to agree with the CharSet for the DllImport declaration. So nothing major there. I don't know what ails this code. – Hans Passant Dec 08 '17 at 13:25
  • @HansPassant Right, when I set CharSet to the NetResource declaration also, the code behaves as originally stated. I am starting to think that there is a bug in the dll :( – ChrisM Dec 08 '17 at 13:48
  • @ChrisM - please repost this question to windows support forums: http://answers.microsoft.com/en-us/windows/forum/windows_7 and http://answers.microsoft.com/en-us/windows/forum/windows_10 – Tanya Solyanik Jan 05 '18 at 21:06
  • I opened a thread there: https://answers.microsoft.com/en-us/windows/forum/windows_10-networking/wnetaddconnection2-seems-to-cache-password/e422b20a-2c62-45a2-86f4-570f4d32a087?tm=1515409458213 – ChrisM Jan 08 '18 at 11:05
  • @ChrisM Did you eventually find a solution? I'm experiencing similar issues and found your odyssey through different forums without a solution. Any chance, you found a solution yourself in the meantime? – CodeX Jan 22 '21 at 22:10

0 Answers0