Over the last couple of weeks a number of questions came up in regards to getting access to network drives from within your Web Connection applications. Drive mapping errors can be really insidious because you often don't know what's actually causing the problem as the problems usually occur right during application startup where it's difficult to debug. Additionally it's not obvious that the error is drive mapping vs. some other issue like permissions or some other failure in the application.
This may seem trivial but when you are developing applications and when you are deploying applications often provides a very different runtime environment, which can cause problems when it comes to mapping drives. The reason is that Web applications usually run under a system context, rather than a user context.
IIS User Context
So in IIS a Web Connection COM Server or IIS launched standalone EXE file are typically running inside of the security context of an Application Pool. The security context is determined by the Application Pool Identity which is set in the Application Pool's Advanced settings in the IIS Management Console:
We recommend that you set the Application Pool identity and if you're running the server as a COM Server leave the DCOM settings as the Launching user which inherits these settings into the COM server. This way configuration is left in a single place as the security flows from IIS into the COM object.
Regardless of whether you use a system account like SYSTEM or Network Service, or a full local or domain account, the accounts loaded by default do not have a user profile (although you can enabled that but I don't recommend it) which means standard mechanisms of loading common startup and system settings are not applied. One thing that this means is that drive mappings are never persisted across multiple logins as that's part of a user profile.
So while you may have mapped a network drive with your user account that network drive – even if persistently mapped – will not be visible by a different account, or even the same account when loaded under IIS. This means you need to make sure that either your application, or the Windows subsystem that the account runs under loads up any drive mappings.
Mapping Drives Or UNC Paths?
There are a couple of ways you can access network drives: You can use mapped drives where a network share and path are mapped to a drive letter, or you can use the raw UNC paths to access the resources directly.
UNC paths appear to be simpler at a glance because they are a direct connection to a remote resource. A UNC path looks like this:
and allows you to directly reference a folder or file using a somewhat verbose syntax.
But there are a few problems with UNC paths. First and foremost permissions are often an issue – if you are not referencing the remote path with the same credentials that you are logged on under on the remote machine, a UNC path won't work as you can't easily attach authentication with your file access request.
The other issue is that performance often is not very good. There are mixed reports on this – some people have found that UNC paths work quickly and as fast as mapped drives, but in my experience over slower connections UNC paths tend to be drastically slower in connecting to remote resources. Actual tranfer speeds tend to be find, but connection speeds can often be slow and it appears the the name resolutions are not cached resulting in slow connection delays.
Mapped drives let you map a drive letter to remote network resources. Typically you map a drive letter to a share on a remote server. In Windows this is done using the NET USE command:
net use i: \\server\cdrive "password" /User:"username" /persistent:yes
This maps a i: drive to a remote resource.
As mentioned the tricky part with Drive mappings is to know where they are visible. When net use is issued it's valid only for that specific user's context. This is why if you map a drive to your desktop user while logged is not going to be visible to the SYSTEM account or even your own account when running in the non-interactive console.
What this means is that if you want to map drives you have to map the drives from within the correct context. For a Web application running in IIS this means setting up the mapping either as part of the startup code of the application or as part of Windows startup scripts that are fired when a Windows session is started.
Mapping from within an Application
Personally I prefer to handle drive mapping as part of the application to keep the drive dependecies configured in the same place as many other configuration settings. The key is to do the configuration before you access any resources that require these drives.
Using Web Connection it's easy to do this in the OnInit() or OnLoad() of the server code:
PROTECTED FUNCTION OnInit ... MapNetworkDrive("i:","\\wwserver\data","username","password") *** Check whether it worked by looking at some resource IF !DIRECTORY("i:\") this.Trace("i: drive couldn't be mapped") ENDIF ENDFUNC
MapNetworkDrive() is a helper function from wwApi.prg that essentially shells out to Windows and calls the net use command. It runs in the context of your application so assuming you have rights to map a drive you should be able to map the drive here. It's a good idea to check for some known resource afterwards to see if the drive mapping worked since the function itself gives no feedback. If it fails call the Trace() function to log the info into wwTraceLog.txt so you can see the failure if it occurs and potentially stop loading the application by erroring out with an ERROR() call.
The function is pretty simple:
************************************************************************ * MapNetworkDrive **************************************** *** Function: Maps a network drive *** Assume: *** Pass: lcDrive - i: *** lcSharePath - UNC path to map \\server\share *** lcUsername - user name (if empty uses current creds) *** lcPassword - password *** Return: .T. if the drive exists after mapping ************************************************************************ FUNCTION MapNetworkDrive(lcDrive, lcSharePath, lcUsername, lcPassword) IF RIGHT(lcDrive,1) != ":" lcDrive = lcDrive + ":" ENDIF lcRun = [net use ] + lcDrive + [ "] + lcSharePath + [" ] IF !EMPTY(lcUsername) lcUserName = ["] + lcPassword + [" /USER:"] + lcUsername + ["] ELSE lcUserName = "" ENDIF lcUsername = lcUserName + " /persistent:yes" lcRun = lcRun + lcUsername RUN &lcRun *** Check to see if the folder exists now RETURN DIRECTORY(lcDrive) ENDFUNC
Using System Policy Startup Scripts
One problem with the application mapping is that the drive is mapped everytime the application starts. While it doesn't hurt to remap drives, there is some overhead in this process as it's slow and if you have multiple instances firing up at the same time there may be some interference causing the map to fail.
Another potentially safer way is to use System Policy to create a startup script that runs a batch file that creates the maps. These scritpts are fired once per Windows session so there's less overhead and no potential for multiple applications trying to map drives simultaneously.
To do this:
- Open Edit Group Policy
- Go to Computer Configuration/Windows Settings/Startup
- Point at a Batch file that includes the net use commands to map your drives
Drive mapping can be a major headache in Web applications if you are not careful and plan ahead for the custom execution environment in which system hosted applications run. Essentially make sure you explicitly map your drives either as part of the application's startup or as part of the console system startup whenever a Windows session is started. I hope this short post clarifies some of the issues you might have to deal with in the context of Web Connection applications.