Archive for June, 2006

Mapping a network drive in .NET

No Comments »

I recently had to perform the following operations in an ASP.NET project I was working on:

  • Retrieve the contents of certain directories on a remote server
  • Process the retrieved files
  • Distribute the result of the above process to a number of webservers

I first created a quick and dirty console application which achieved this. Its main thread spawned additional processes running ‘net use LOCALNAME: UNCPATH PASSWORD /user:USERNAME’ to map the required remote drives after which it performed the copying.
My first attempt to wrap this in ASP.NET consisted in trying to continue to use the ‘net use’ approach. This required some configuration of proper impersonation, but otherwise it seemed to work fine at first glance – the mapping processes returned no error codes. However, upon trying to access the mapped drives, the application died with an ‘Unable to find part of the path’-exception. I’m not really into what is going on with ‘net use’, but my investigations showed that the mappings performed by the child processes weren’t reflected back in the parent process. The approach worked in the console app, but not when used from ASP.NET (even though I impersonated the exact same user who had run the console app).
Next, I discussed the problem with a couple of colleagues. The outcome was that I tried to achieve my original goals through the use of FTP. A colleague lended me some code he had written when performing a seemingly similar task. However, upon investigation I discovered that FTP wasn’t properly configured on the servers I was targeting with my application, so I abandoned the idea (these were production servers and I really didn’t want to have to mess with their configuration).
In the end I turned to the Windows API.
I had previously eschewed the concept of interfacing to unmanaged code. However, it turned out to be quite simple once I got an idea of how to map the .NET datatypes to unmanaged types and vice versa. I imported the signatures of two functions for creating connections to networking resources:

[DllImport("mpr.dll")]
public static extern int WNetAddConnection2(
[MarshalAs(UnmanagedType.LPArray)] NETRESOURCE[] lpNetResource,
[MarshalAs(UnmanagedType.LPStr)] string lpPassword,
[MarshalAs(UnmanagedType.LPStr)] string UserName,
int dwFlags);
[DllImport("mpr.dll")]
public static extern UInt32 WNetCancelConnection2(
[MarshalAs(UnmanagedType.LPStr)] string lpName,
UInt32 dwFlags,
bool fForce);

Once this was in place, putting the functions to work was quite straightforward:

private int UnMapDrive(string localname)
{
return Convert.ToInt32(WNetCancelConnection2(localname, 0, false));
}


private int MapDrive(string mapto, string drivepath, string password, string user)
{
NETRESOURCE [] nr = new NETRESOURCE[1];
nr[0] = new NETRESOURCE();
nr[0].dwType = 1;
int dwFlags = 1;
nr[0].lpLocalName = mapto;
nr[0].lpRemoteName = drivepath;
nr[0].lpProvider = null;
int res = WNetAddConnection2(nr, password, user, dwFlags );
if(res == 85) /* system error code 85 ~ ERROR_ALREADY_ASSIGNED - The local device name is already in use. */
{
LogManager.LogLine(TranslateErrorCodeToDescription(res));
LogManager.LogLine("Unmapping...");
int unmapresult = UnMapDrive(mapto);
if(unmapresult != 0)/*If unmapping fails, bail out.*/
{
numErrors++;
LogManager.LogLine("Unmapping failed with error code " + unmapresult);
LogManager.LogLine(TranslateErrorCodeToDescription(unmapresult));
BailOut(1);
}
else
{
LogManager.LogLine("Unmapping succeeded");
}
/* If unmapping succeded, try mapping again */
LogManager.LogLine("Retrying");
res = WNetAddConnection2(nr, password, user, dwFlags );
}
return Convert.ToInt32(res);
}

This works like a charm and the approach has since been used by a couple of my colleagues.