 .NET 1.1Mapping a Drive Letter Programmatically
Some legacy applications do not permit the use of UNC paths when accessing network folders, instead requiring that a drive letter be mapped. When interacting with such software it may be necessary to map a drive letter and later remove mappings using C#.
UNC Paths and Drive Letters
Modern computers are often connected to a network with access to many other remote resources, including shared folders, printers and other devices. These resources are usually referenced with a unique path name defined using the Uniform Naming Convention (UNC). UNC paths generally specify the name of a machine, or cluster of devices, and the name of a folder, file or other resource that is to be accessed. An example of a UNC path that you may use in Windows is "\\BlackWasp\ImageGallery\Wasp.ico". This path leads to the Wasp.ico file, which is held in a shared folder named "ImageGallery" on the "BlackWasp" server.
Earlier in the history of PC operating systems, DOS and Windows-base software relied on the use of drive letters to access files. In the same way that C: usually refers to the main hard disk drive in a PC, other letters would be mapped to networked resources. This permitted applications to open and save files to local and networked folders using the same naming conventions. For example, the "\\BlackWasp\ImageGallery" could be mapped to I:, allowing the file described earlier to be accessed using the path, "I:\Wasp.ico".
The problem with the drive letter approach is that it only permits a maximum of twenty-six local and remote folders to be accessed simultaneously. In a modern office environment this may not be enough. Handily, the use of the UNC is supported by Windows and by the .NET framework without additional effort. However, if you need to interact with older software you may need to add and remove drive letters programmatically.
In this article I will describe some of the manners in which you can map drive letters using C# code. For a demonstration of these methods, download the sample application using the link at the top of this article.
Mapping Drive Letters
The .NET framework does not provide a means of mapping a drive letter. Instead, a Windows API function must be executed using Platform Invocation Services, also known as P/Invoke. To use P/Invoke, classes from the InteropServices namespace are required. To make the code in the samples below more readable, it makes sense to add a using statement for this namespace:
using System.Runtime.InteropServices;
Referencing WNetAddConnection Methods
There are two API functions that can be used to directly map a drive letter to a folder represented by a UNC path. These are called WNetAddConnection2 and WNetAddConnection3. These functions supersede a previous method named WNetAddConnection, which is now present only to allow the correct operation of 16-bit applications.
The two functions are found in the mpr library of the Windows API. We can add references to the functions using the following code:
[DllImport("mpr.dll")]
static extern UInt32 WNetAddConnection2(ref NETRESOURCE lpNetResource,
string lpPassword, string lpUsername, uint dwFlags);
[DllImport("mpr.dll")]
static extern UInt32 WNetAddConnection3(IntPtr hWndOwner, ref NETRESOURCE
lpNetResource, string lpPassword, string lpUserName, uint dwFlags);
You can see that the two declarations only differ in one respect. When using WNetAddConnection3, an additional parameter is provided. This parameter, named hWndOwner in the sample above, receives the handle of a window. If the call to the API function results in the display of interactive dialog boxes, the parent window of these dialog boxes will be the one with the specified handle. In all other respects, the two methods are essentially identical.
When selecting between the two functions, use WNetAddConnection2 where there is no visible user interface or where no user interaction will be required. Use WNetAddConnection3 when calling the function from a form within a Windows application.
Defining the NETRESOURCE Structure
The first parameter of WNetAddConnection2, and the second of WNetAddConnection3, accepts a NETRESOURCE structure. This structure contains information relating to the remote resource that you wish to connect to and the drive letter that you want to assign. Before you can use the mapping commands you must declare this structure, ensuring that it exactly matches that expected by the API. To create the structure, add the following:
[StructLayout(LayoutKind.Sequential)]
public struct NETRESOURCE
{
public uint dwScope;
public uint dwType;
public uint dwDisplayType;
public uint dwUsage;
public string lpLocalName;
public string lpRemoteName;
public string lpComment;
public string lpProvider;
}
Although the structure defines eight fields, only four of these are required when mapping drive letters. The first, dwType, is used to specify that we want to map to a network folder. This value is always set to a constant value named RESOURCETYPE_DISK. This constant must be defined within the code as follows:
const uint RESOURCETYPE_DISK = 1;
The lpLocalName field holds the drive letter that will be assigned to the mapped path, which is specified in lpRemoteName. The fourth value that must be provided is lpProvider. This string value determines the network provider that will be used to make the connection. However, normally this is set to null to specify that the operating system should automatically determine the provider to use.
Connection Flags
When the connection functions are called, a series of flags can be passed to the command to affect its behaviour. There are six flags, each defined in the table below:
| Flag Name | Value | Description |
|---|
| CONNECT_UPDATE_PROFILE | 0x1 | If this flag is set, the operating system is instructed to remember the mapping of the drive letter in the user's profile. This means that if the user logs off, when they log on again at a later date, an attempt to restore the connection will be made. | | CONNECT_INTERACTIVE | 0x8 | When this flag is set, the operating system is permitted to ask the user for authentication information before attempting to map the drive letter. | | CONNECT_PROMPT | 0x10 | When set, this flag indicates that any default user name and password credentials will not be used without first giving the user the opportunity to override them. This flag is ignored if CONNECT_INTERACTIVE is not also specified. | | CONNECT_REDIRECT | 0x80 | This flag forces the redirection of a local device when making the connection. For the functionality described in this article the flag has no effect. It is included here for completeness. | | CONNECT_COMMANDLINE | 0x800 | This flag indicates that if the operating system needs to ask for a user name and password, it should do so using the command line rather than by using dialog boxes. This flag is ignored if CONNECT_INTERACTIVE is not also specified. It is not available to Windows 2000 or earlier versions of the operating system. | | CONNECT_CMD_SAVECRED | 0x1000 | If set, this flag specifies that any credentials entered by the user will be saved. If it is not possible to save the credentials or the CONNECT_INTERACTIVE is not also specified then the flag is ignored. |
We can create these values in the program by defining the following constants:
const uint CONNECT_UPDATE_PROFILE = 0x1;
const uint CONNECT_INTERACTIVE = 0x8;
const uint CONNECT_PROMPT = 0x10;
const uint CONNECT_REDIRECT = 0x80;
const uint CONNECT_COMMANDLINE = 0x800;
const uint CONNECT_CMD_SAVECRED = 0x1000;
Calling WNetAddConnection2
We have now prepared all of the constants and structures that are used by the WNetAddConnection functions. Let's now look at calling the simpler of the two methods, WNetAddConnection2. To do so, we use the following syntax:
result = WNetAddConnection2(ref lpNetResource, lpPassword, lpUserName, dwFlags);
The result value returns an unsigned 32-bit integer. A returned zero indicates that the process completed successfully. All other values represent a standard system error code that explains why the call failed. This value should be checked as the function does not throw exceptions to indicate failure.
lpNetResource holds a NETRESOURCE value. This item contains the details of the shared folder and the local drive letter to be mapped. Note that this parameter must be passed by reference.
lpPassword and lpUserName should contain the login credentials of the user that you wish to use to make the connection to the shared resource. These values may be different to that of the currently logged in interactive user, or of the user that the process is executing under. If you pass null to both of these values, the operating system will automatically use the details from the current user context.
dwFlags is an unsigned integer containing the flags that will be used to control the call's exact behaviour. The flags are described in the earlier. They should be combined using logical bitwise operations.
Example 1 - Simple Mapping
The simplest way to call the WNetAddConnection2 function is to pass the details of the network resource but not specify any user credentials or flags. Using this method, the system will simply try to map a drive letter using the current credentials without prompting the user for login details. The example below tries to map Z: to the path "\\BlackWasp\ImageGallery". To try it, change the drive letter to one that is not already mapped on your system and the UNC path to a shared resource that you have access to.
NETRESOURCE networkResource = new NETRESOURCE();
networkResource.dwType = RESOURCETYPE_DISK;
networkResource.lpLocalName = "Z:";
networkResource.lpRemoteName = @"\\BlackWasp\ImageGallery";
networkResource.lpProvider = null;
uint result = WNetAddConnection2(ref networkResource, null, null, 0);
MessageBox.Show(result.ToString());
Example 2 - Specifying User Credentials
In the second example, we will specify the user name and password to use when connecting the new drive letter to the shared folder. This method is useful when you have requested these details from the user at an earlier time, particularly if you are mapping several drive letters using the same login details. You should not use this method with hard-coded credentials, as in the sample code, as this could pose a security risk.
To try this sample, disconnect the previously mapped drive and modify the call to WNetAddConnection2 as follows. You should include a login name and password that is valid for the UNC path.
uint result = WNetAddConnection2(ref networkResource, "password", "username", 0);
Example 3 - Requesting Credentials
In the third example we will allow the operating system to request the logon credentials to use when accessing the shared resource. We will also instruct the O/S to remember these login details and add the drive mapping to the user's profile. This is achieved by modifying the call as follows:
uint flags = CONNECT_INTERACTIVE | CONNECT_PROMPT
| CONNECT_CMD_SAVECRED | CONNECT_UPDATE_PROFILE;
uint result = WNetAddConnection2(ref networkResource, null, null, flags);
Calling WNetAddConnection3
Calling the WNetAddConnection3 function is almost identical to WNetAddConnection2, except for the addition parameter for a window handle. If you are calling the function from directly within a form's class, you can simply pass the form's handle using the Handle property, as in the next sample. If you are calling from any other class you will need to obtain a window handle first.
NETRESOURCE networkResource = new NETRESOURCE();
networkResource.dwType = RESOURCETYPE_DISK;
networkResource.lpLocalName = "Z:";
networkResource.lpRemoteName = @"\\BlackWasp\ImageGallery";
networkResource.lpProvider = null;
uint result = WNetAddConnection3(this.Handle, ref networkResource, null, null, 0);
MessageBox.Show(result.ToString());
Removing a Drive Mapping
Once you have finished using a drive letter mapping, you may want to disconnect it. If you do, you can use the WNetCancelConnection2 function of the Windows API. In this case there is only a single reference to be created, as there is no definition that includes passing a window handle.
Referencing WNetCancelConnection2
To declare the WNetCancelConnection2 function, use the following code:
[DllImport("mpr.dll")]
static extern uint WNetCancelConnection2(string lpName, uint dwFlags, bool bForce);
Calling WNetCancelConnection2
The cancel command requires three parameters. The first, "lpName", accepts a string containing the drive letter that you wish to disconnect. The second parameter specifies the flags that modify the operation of the command. Only two options are permitted. Passing zero simply disconnects the drive letter, whereas passing CONNECT_UPDATE_PROFILE also removes the drive mapping from the user's profile so that it will not be reconnected after a reboot.
The final parameter, "bForce", is a Boolean value. Usually this should be false. This prevents the mapping from being removed if it is in use. Setting the parameter to true forces disconnection, possibly causing processes using the drive letter to fail.
The following example permanently cancels the Z: mapping, as long as it is not in use:
uint result = WNetCancelConnection2("Z:", CONNECT_UPDATE_PROFILE, false);
MessageBox.Show(result.ToString());
|