tags:

views:

1424

answers:

2

Good afternoon,

I am using the code below to map a network drive in a .net application.. well more precisely, try to do it. Whenever I do make the call to WNetAddConnection2W, I get a win32exception with the error code 67... which basically translates into 'The network name cannot be found'.. but I really do now know why... any ideas where this error might come from?

Here's the NetworkMapper class:

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

namespace Libraries.Utilities
{
    /// <summary>
    /// 
    /// </summary>
    public class NetworkDriveMapper
    {


        #region Public variables and propertys

        private string _localDrive = null;
        private string _shareName = "";

        /// <summary>
        /// Initializes a new instance of the <see cref="NetworkDriveMapper"/> class.
        /// </summary>
        public NetworkDriveMapper()
        {
            FindNextFreeDrive = false;
            PromptForCredentials = false;
            Force = false;
            Persistent = false;
            SaveCredentials = false;
        }

        /// <summary>
        /// Option to save credentials on reconnection...
        /// </summary>
        public bool SaveCredentials { get; set; }

        /// <summary>
        /// Option to reconnect drive after log off / reboot...
        /// </summary>
        public bool Persistent { get; set; }

        /// <summary>
        /// Option to force connection if drive is already mapped...
        /// or force disconnection if network path is not responding...
        /// </summary>
        public bool Force { get; set; }

        /// <summary>
        /// Option to prompt for user credintals when mapping a drive
        /// </summary>
        public bool PromptForCredentials { get; set; }

        /// <summary>
        /// Option to auto select the 'lpLocalName' property to next free driver letter when mapping a network drive
        /// </summary>
        public bool FindNextFreeDrive { get; set; }

        /// <summary>
        /// Drive to be used in mapping / unmapping (eg. 's:')
        /// </summary>
        public string LocalDrive
        {
            get { return _localDrive; }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    _localDrive = null;
                }
                else
                {
                    _localDrive = value.Substring(0, 1) + ":";
                }
            }
        }

        /// <summary>
        /// Share address to map drive to. (eg. '\\Computer\C$')
        /// </summary>
        public string ShareName
        {
            get { return _shareName; }
            set { _shareName = value; }
        }

        /// <summary>
        /// Returns a string array of currently mapped network drives
        /// </summary>
        public string[] MappedDrives
        {
            get
            {
                var driveArray = new List<string>();
                foreach (string driveLetter in Directory.GetLogicalDrives())
                {
                    if (PathIsNetworkPath(driveLetter))
                    {
                        driveArray.Add(driveLetter);
                    }
                }
                return (driveArray.ToArray());
            }
        }

        #endregion

        #region Public functions

        /// <summary>
        /// Map network drive
        /// </summary>
        public void MapDrive()
        {
            mapDrive(null, null);
        }

        /// <summary>
        /// Map network drive (using supplied Username and Password)
        /// </summary>
        /// <param name="username">Username passed for permissions / credintals ('Username' may be passed as null, to map using only a password)</param>
        /// <param name="password">Password passed for permissions / credintals</param>
        public void MapDrive(string username, string password)
        {
            mapDrive(username, password);
        }

        /// <summary>
        /// Set common propertys, then map the network drive
        /// </summary>
        /// <param name="localDrive">lpLocalName to use for connection</param>
        /// <param name="shareName">Share name for the connection (eg. '\\Computer\Share')</param>
        /// <param name="force">Option to force dis/connection</param>
        public void MapDrive(string localDrive, string shareName, bool force)
        {
            _localDrive = localDrive;
            _shareName = shareName;
            Force = force;
            mapDrive(null, null);
        }

        /// <summary>
        /// Set common propertys, then map the network drive
        /// </summary>
        /// <param name="localDrive">Password passed for permissions / credintals</param>
        /// <param name="force">Option to force dis/connection</param>
        public void MapDrive(string localDrive, bool force)
        {
            _localDrive = localDrive;
            Force = force;
            mapDrive(null, null);
        }

        /// <summary>
        /// Unmap network drive
        /// </summary>
        public void UnMapDrive()
        {
            unMapDrive();
        }

        /// <summary>
        /// Unmap network drive
        /// </summary>
        public void UnMapDrive(string localDrive)
        {
            _localDrive = localDrive;
            unMapDrive();
        }

        /// <summary>
        /// Unmap network drive
        /// </summary>
        public void UnMapDrive(string localDrive, bool force)
        {
            _localDrive = localDrive;
            Force = force;
            unMapDrive();
        }

        /// <summary>
        /// Check / restore persistent network drive
        /// </summary>
        public void RestoreDrives()
        {
            restoreDrive(null);
        }

        /// <summary>
        /// Check / restore persistent network drive
        /// </summary>
        public void RestoreDrive(string localDrive)
        {
            restoreDrive(localDrive);
        }

        /// <summary>
        /// Display windows dialog for mapping a network drive (using Desktop as parent form)
        /// </summary>   
        public void ShowConnectDialog()
        {
            displayDialog(IntPtr.Zero, 1);
        }

        /// <summary>
        /// Display windows dialog for mapping a network drive
        /// </summary>
        /// <param name="parentFormHandle">Form used as a parent for the dialog</param>
        public void ShowConnectDialog(IntPtr parentFormHandle)
        {
            displayDialog(parentFormHandle, 1);
        }

        /// <summary>
        /// Display windows dialog for disconnecting a network drive (using Desktop as parent form)
        /// </summary>   
        public void ShowDisconnectDialog()
        {
            displayDialog(IntPtr.Zero, 2);
        }

        /// <summary>
        /// Display windows dialog for disconnecting a network drive
        /// </summary>
        /// <param name="parentFormHandle">Form used as a parent for the dialog</param>
        public void ShowDisconnectDialog(IntPtr parentFormHandle)
        {
            displayDialog(parentFormHandle, 2);
        }

        /// <summary>
        /// Returns the share name of a connected network drive
        /// </summary>
        /// <param name="localDrive">Drive name (eg. 'X:')</param>
        /// <returns>Share name (eg. \\computer\share)</returns>
        public string GetMappedShareName(string localDrive)
        {
            // collect and clean the passed lpLocalName param
            if (localDrive == null || localDrive.Length == 0)
                throw new Exception("Invalid 'localDrive' passed, 'localDrive' parameter cannot be 'empty'");
            localDrive = localDrive.Substring(0, 1);

            // call api to collect lpLocalName's share name 
            int i = 255;
            var bSharename = new byte[i];
            int iCallStatus = WNetGetConnection(localDrive + ":", bSharename, ref i);
            switch (iCallStatus)
            {
                case 1201:
                    throw new Exception(
                        "Cannot collect 'ShareName', Passed 'DriveName' is valid but currently not connected (API: ERROR_CONNECTION_UNAVAIL)");
                case 1208:
                    throw new Exception("API function 'WNetGetConnection' failed (API: ERROR_EXTENDED_ERROR:" +
                                        iCallStatus.ToString() + ")");
                case 1203:
                case 1222:
                    throw new Exception(
                        "Cannot collect 'ShareName', No network connection found (API: ERROR_NO_NETWORK / ERROR_NO_NET_OR_BAD_PATH)");
                case 2250:
                    throw new Exception(
                        "Invalid 'DriveName' passed, Drive is not a network drive (API: ERROR_NOT_CONNECTED)");
                case 1200:
                    throw new Exception(
                        "Invalid / Malfored 'Drive Name' passed to 'GetShareName' function (API: ERROR_BAD_DEVICE)");
                case 234:
                    throw new Exception("Invalid 'Buffer' length, buffer is too small (API: ERROR_MORE_DATA)");
            }

            // return collected share name
            return Encoding.GetEncoding(1252).GetString(bSharename, 0, i).TrimEnd((char) 0);
        }

        /// <summary>
        /// Returns true if passed drive is a network drive
        /// </summary>
        /// <param name="localDrive">Drive name (eg. 'X:')</param>
        /// <returns>'True' if the passed drive is a mapped network drive</returns>
        public bool IsNetworkDrive(string localDrive)
        {
            // collect and clean the passed lpLocalName param
            if (localDrive == null || localDrive.Trim().Length == 0)
                throw new Exception("Invalid 'localDrive' passed, 'localDrive' parameter cannot be 'empty'");
            localDrive = localDrive.Substring(0, 1);

            // return status of drive type
            return PathIsNetworkPath(localDrive + ":");
        }

        #endregion

        #region Private functions

        // map network drive
        private void mapDrive(string username, string password)
        {
            // if drive property is set to auto select, collect next free drive   
            if (FindNextFreeDrive)
            {
                _localDrive = nextFreeDrive();
                if (string.IsNullOrEmpty(_localDrive))
                    throw new Exception("Could not find valid free drive name");
            }

            // create struct data to pass to the api function
            var stNetRes = new netResource
                               {
                                   dwScope = 2,
                                   dwType = RESOURCETYPE_DISK,
                                   dwDisplayType = 3,
                                   dwUsage = 1,
                                   lpRemoteName = _shareName,
                                   lpLocalName = _localDrive
                               };

            // prepare flags for drive mapping options
            int iFlags = 0;
            if (SaveCredentials)
                iFlags += CONNECT_CMD_SAVECRED;
            if (Persistent)
                iFlags += CONNECT_UPDATE_PROFILE;
            if (PromptForCredentials)
                iFlags += CONNECT_INTERACTIVE + CONNECT_PROMPT;

            // prepare username / password params
            if (username != null && username.Length == 0)
                username = null;
            if (password != null && password.Length == 0)
                password = null;

            // if force, unmap ready for new connection
            if (Force)
            {
                try
                {
                    unMapDrive();
                }
                catch
                {
                }
            }

            // call and return
            int i = WNetAddConnection2W(ref stNetRes, password, username, iFlags);
            if (i > 0)
                throw new Win32Exception(i);
        }

        // unmap network drive  
        private void unMapDrive()
        {
            // prep vars and call unmap
            int iFlags = 0;
            int iRet = 0;

            // if persistent, set flag
            if (Persistent)
            {
                iFlags += CONNECT_UPDATE_PROFILE;
            }

            // if local drive is null, unmap with use connection
            if (_localDrive == null)
            {
                // unmap use connection, passing the share name, as local drive
                iRet = WNetCancelConnection2W(_shareName, iFlags, Convert.ToInt32(Force));
            }
            else
            {
                // unmap drive
                iRet = WNetCancelConnection2W(_localDrive, iFlags, Convert.ToInt32(Force));
            }

            // if errors, throw exception
            if (iRet > 0)
                throw new Win32Exception(iRet);
        }

        // check / restore a network drive
        private void restoreDrive(string driveName)
        {
            // call restore and return
            int i = WNetRestoreConnection(0, driveName);

            // if error returned, throw
            if (i > 0)
                throw new Win32Exception(i);
        }

        // display windows dialog
        private void displayDialog(IntPtr wndHandle, int dialogToShow)
        {
            // prep variables
            int i = -1;
            int iHandle = 0;

            // get parent handle
            if (wndHandle != IntPtr.Zero)
                iHandle = wndHandle.ToInt32();

            // choose dialog to show bassed on 
            if (dialogToShow == 1)
                i = WNetConnectionDialog(iHandle, RESOURCETYPE_DISK);
            else if (dialogToShow == 2)
                i = WNetDisconnectDialog(iHandle, RESOURCETYPE_DISK);

            // if error returned, throw
            if (i > 0)
                throw new Win32Exception(i);
        }

        // returns the next viable drive name to use for mapping
        private string nextFreeDrive()
        {
            // loop from c to z and check that drive is free
            string retValue = null;
            for (int i = 67; i <= 90; i++)
            {
                if (GetDriveType(((char) i).ToString() + ":") == 1)
                {
                    retValue = ((char) i).ToString() + ":";
                    break;
                }
            }

            // return selected drive
            return retValue;
        }

        #endregion

        #region API functions / calls

        private const int CONNECT_CMD_SAVECRED = 0x00001000;
        private const int CONNECT_INTERACTIVE = 0x00000008;
        private const int CONNECT_PROMPT = 0x00000010;
        private const int CONNECT_UPDATE_PROFILE = 0x00000001;
        private const int RESOURCETYPE_DISK = 1;

        [DllImport("mpr.dll", EntryPoint = "WNetAddConnection2W", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern int WNetAddConnection2W(ref netResource netRes, string password, string username,
                                                    int flags);

        [DllImport("mpr.dll", EntryPoint = "WNetCancelConnection2W", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern int WNetCancelConnection2W(string name, int flags, int force);

        [DllImport("mpr.dll", EntryPoint = "WNetConnectionDialog", SetLastError = true)]
        private static extern int WNetConnectionDialog(int hWnd, int type);

        [DllImport("mpr.dll", EntryPoint = "WNetDisconnectDialog", SetLastError = true)]
        private static extern int WNetDisconnectDialog(int hWnd, int type);

        [DllImport("mpr.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        private static extern int WNetRestoreConnection(int hWnd, string localDrive);

        [DllImport("mpr.dll", EntryPoint = "WNetGetConnection", SetLastError = true)]
        private static extern int WNetGetConnection(string localDrive, byte[] remoteName, ref int bufferLength);

        [DllImport("shlwapi.dll", EntryPoint = "PathIsNetworkPath", SetLastError = true)]
        private static extern bool PathIsNetworkPath(string localDrive);

        [DllImport("kernel32.dll", EntryPoint = "GetDriveType", SetLastError = true)]
        private static extern int GetDriveType(string localDrive);

        [StructLayout(LayoutKind.Sequential)]
        private struct netResource
        {
     #region Data Members (8) 

            public int dwScope;
            public int dwType;
            public int dwDisplayType;
            public int dwUsage;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpLocalName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpRemoteName;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpComment;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string lpProvider;

     #endregion Data Members 
        }

        // standard

        #endregion
    }
}

Basically what I do is the following:

var networkDriveMapper = new Utilities.NetworkDriveMapper
                {
                    PromptForCredentials = false,
                    Persistent = true,
                    FindNextFreeDrive = false,
                    Force = false
                };

                networkDriveMapper.MapDrive("B:", @"\\server\share", false);

And then the private void mapDrive(string username, string password) method throws the Win32Exception with the error code 67 ...

Anyone else using the the mpr.dll / WNetAddConnection2W entrypoint? And/Or knows how to circumvent/fix this error?

Cheers and thanks, -Jörg

Update: I originally had an Error 1204 ('The specified network provider name is invalid').. but the 2 answers so far kinda helped me a little bit, but not solved the entire problem of not being able to map network drives properly...

A: 

I believe your netResource structure is in the wrong order (vs. kb173011)

    [StructLayout(LayoutKind.Sequential)]
    private struct netResource
    {
        public int dwScope;
        public int dwType;
        public int dwDisplayType;
        public int dwUsage;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpLocalName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpRemoteName;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpComment;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string lpProvider;
    }
Rowland Shaw
Ok that changed it a bit.. now I get error code 1200: "The specified device name is invalid".. afaik the 'device' is 'B:' in my case.. which still doesn't make sense... :-/
Jörg B.
That makes more sense to me, as in days of yore, "B:" was reserved for floppy disk drives - does it work with (say) R:?
Rowland Shaw
Unfortunately not.. tried with 'R:' ... same error code.. 1200.
Jörg B.
You're marshalling as a Unicode string, but calling the ANSI entry point by the looks - either change to use the Unicode entry point (W suffix), or change the structure to marshal as the correct datatype
Rowland Shaw
A: 

To solve the error code 1200 I changed the definition of WNetAddConnection2A to WNetAddConnection2W, change the entrypoint too and set the charset to Unicode.

I didn't get to see the mapped drive in My Computer, but when I used your function to disconnect mapped drives, the drive was in the list.

ZippyV
ZippyV, well that changed the error once more.. I am getting now Error #67 ('The network name cannot be found'). However, when entering the exact same network lpRemoteName into e.g. the explorer, it works perfectly fine... hrm :-/
Jörg B.