Archive
Map network path by code
Ever wanted to map a network path by code? I mean, instead of mapping a network path to a drive letter, like you can do with using Windows Explorer. What If you could map a known network location without any other program or even Windows Explorer knowing about it? Well, this is what my little class does.
This class came about many years ago while I was coding a collection of services meant to receive, process and transport data to various places in a huge network. The win32 services all ran in the background (like services do) and there was no way to ensure that a specific network path was mapped and ready to use.
Is it useful?
Let me demonstrate with a simple task. You are hired to create a win32 service that accepts data on a listening Indy server. This data should be processed and stored in 3 locations on the network (a huge network). The user account that executes your service will have access rights for these 3 locations, but there is one problem — the locations can change without any warning – and the network entrypoint-paths are stored in the registry only.
So, how do you solve this? You can’t hardcode the target locations (you should avoid hardcoding as much as possible at all times), you can’t keep mapping network paths to drive-letters, because it will be a complete mess, and since stuff can change at any time — you need to map the path for each call to the service to be safe.
The answer is to map the network paths by code, with no drive letter involved. And here is a mini-class that does this!
TQTXNetworkPath = Class(TObject) Private FHostName: String; FRemotePath: String; FUser: String; FPassword: String; FConnected: Boolean; FOwned: Boolean; FLastError: String; FFailed: Boolean; FUNCData: packed array[0..4096] of char; FURI: String; protected procedure ClearLastError; Procedure setLastError(aValue:String); Public Property Active:Boolean read FConnected; Property HostName:String read FHostName; Property NetworkPath:String read FRemotePath; Property LastError:String read FLastError; Property Failed:Boolean read FFailed; Function getRelativePath(aFilename:String):String; Function Connect(aHostName:String; aNetworkPath:String; const aUsername:String=''; const aPassword:String=''):Boolean; Procedure Disconnect(const aForce:Boolean=False); Destructor Destroy;override; End; //########################################################################## // TQTXNetworkPath //########################################################################## Destructor TQTXNetworkPath.Destroy; Begin if FConnected then Disconnect(True); inherited; end; function TQTXNetworkPath.Connect(aHostName, aNetworkPath: String; const aUsername, aPassword: String): Boolean; var mNet: TNetResource; mRes: Cardinal; mTxt: String; begin clearLastError; result:=False; aHostName:=trim(aHostName); aNetworkPath:=trim(aNetworkPath); if length(aHostName)>0 then Begin (* Already connected? Disconnect and force closing of all files *) if FConnected then Disconnect(true); (* Build complete network path *) if length(aNetworkPath)>0 then FURI:='\\' + aHostname else FURI:=format('\\%s\%s',[aHostName,aNetworkPath]); (* Initialize UNC path *) ZeroMemory(@FUNCData,SizeOf(FUNCData)); Move(pointer(@FURI[1])^,pointer(@FUNCData)^,length(FURI) * SizeOf(char) ); (* initialize network resource data *) ZeroMemory(@mNet,SizeOf(mNet)); mNet.dwScope:=RESOURCE_GLOBALNET; mNet.dwType:=RESOURCETYPE_DISK; mNet.dwDisplayType:=RESOURCEDISPLAYTYPE_GENERIC; mNet.dwUsage:=RESOURCEUSAGE_CONNECTABLE; mNet.lpRemoteName:=@FUNCData; (* Now attempt to connect the sucker *) mRes:=WNetAddConnection2(mNet, pchar(aPassword), pchar(aUserName),CONNECT_TEMPORARY); if mRes<>NO_ERROR then Begin ZeroMemory(@FUNCData,SizeOf(FUNCData)); setLength(FURI,0); Case mRes of ERROR_ACCESS_DENIED: mTxt:='Access_denied:'; ERROR_ALREADY_ASSIGNED: mTxt:='Already assigned:'; ERROR_BAD_DEV_TYPE: mTxt:='Bad device type:'; ERROR_BAD_DEVICE: mTxt:='Bad device:'; ERROR_BAD_NET_NAME: mTxt:='Bad network Name:'; ERROR_BAD_PROFILE: mTxt:='Bad profile:'; ERROR_BAD_PROVIDER: mTxt:='Bad provider:'; ERROR_BUSY: mTxt:='Busy:'; ERROR_CANCELLED: mTxt:='Canceled:'; ERROR_CANNOT_OPEN_PROFILE: mTxt:='Cannot_open_profile:'; ERROR_DEVICE_ALREADY_REMEMBERED:mTxt:='Device_already_remembered:'; ERROR_EXTENDED_ERROR: mTxt:='Extended error:'; ERROR_INVALID_PASSWORD: mTxt:='Invalid password:'; ERROR_NO_NET_OR_BAD_PATH: mTxt:='No_Net_or_bad_path:'; ERROR_NO_NETWORK: mTxt:='No_Network:'; end; setLastError(Format('Failed to connect [%s], %s', [mTxt,SysErrorMessage(mRes)])); end else Begin FOwned:=True; FConnected:=True; FHostName:=aHostName; FUser:=aUserName; FPassword:=aPassword; FRemotePath:=aNetworkPath; end; end else setLastError('Failed to connect, invalid hostname error'); end; procedure TQTXNetworkPath.Disconnect(const aForce: Boolean); var mRes: Integer; mCount: Integer; Begin (* Close network connection if connected *) If (FConnected=True) and (FOwned=True) then Begin (* Attempt to close the connection *) mRes:=WNetCancelConnection(@FUNCData,aForce); (* Since files can be open temporarily, we try to wait a little before we re-try to close again. A maximum of 100 attempts is set *) if mRes=ERROR_OPEN_FILES then Begin mCount:=0; While mRes=ERROR_OPEN_FILES do Begin inc(mCount); if mCount=100 then break; Sleep(100); mRes:=WNetCancelConnection(@FUNCData,aForce); end; end; FConnected:=False; FOwned:=False; setLength(FHostName,0); setLength(FRemotePath,0); setLength(FUser,0); setLength(FPassword,0); setLength(FURI,0); end; end; procedure TQTXNetworkPath.ClearLastError; Begin FFailed:=False; setLength(FLastError,0); end; Procedure TQTXNetworkPath.setLastError(aValue:String); Begin FLastError:=trim(aValue); FFailed:=Length(FLastError)>0; end; Function TQTXNetworkPath.getRelativePath(aFilename:String):String; Begin if FConnected then result:=FURI + aFilename else result:=aFilename; end;
How does it work?
While the class should be self-explanatory (and remember, if you are to lazy to use MSDN to read about what WinAPI calls do, then you really should work harder to be a good programmer), here are a few words about the workings.
First, the relative path stuff.
As you probably know, network paths are not like ordinary local paths. A local fixed path can be something like:
C:\MyFolder\Nextfolder\ThirdFolder\Somefile.dat
A network path is more abstract and designed to be relative to a mount-point:
$MOUNTPOINT\MyFolder\Nextfolder\ThirdFolder\Somefile.dat
In many cases the mountpoint ($MOUNTPOINT above) doesnt even have to be included, you can reference the machine name directly, like this:
\\somePC\shared folders\myshared folder\Somefile.dat
In other words, when working with paths that can change at all times, you really want to work exclusively with relative paths. Directories that are relative to the folder you are mounting.
When using the class above, the first method you want to use is the Connect() method. As you see it accepts two parameters: the name of the machine (server), followed by the network path you want to use. It also has two optional parameters for username and password. Note: The username and password is for the account you want to mount the drive for. It doesnt have to be your current account. In some network setups it can be wise to isolate access via proxy accounts to better control who has access to what.
Anyways, this network path is your entry-point (mount point) and whatever files you operate on are, naturally, relative to that. To know the relative path for a file, use the method getRelativePath().
Really not more to say about the class. It takes care of everything and is compatible with findFirst/findNext file-searching (all things normal windows and pascal). Once mounted, the mountpoint can be used anywhere in your process.
Recent
The vatican vault
- January 2022
- October 2021
- March 2021
- November 2020
- September 2020
- July 2020
- June 2020
- April 2020
- March 2020
- February 2020
- January 2020
- November 2019
- October 2019
- September 2019
- August 2019
- July 2019
- June 2019
- May 2019
- April 2019
- March 2019
- February 2019
- January 2019
- December 2018
- November 2018
- October 2018
- September 2018
- August 2018
- July 2018
- June 2018
- May 2018
- April 2018
- March 2018
- February 2018
- January 2018
- December 2017
- November 2017
- October 2017
- August 2017
- July 2017
- June 2017
- May 2017
- April 2017
- March 2017
- February 2017
- January 2017
- December 2016
- November 2016
- October 2016
- September 2016
- August 2016
- July 2016
- June 2016
- May 2016
- April 2016
- March 2016
- January 2016
- December 2015
- November 2015
- October 2015
- September 2015
- August 2015
- June 2015
- May 2015
- April 2015
- March 2015
- February 2015
- January 2015
- December 2014
- November 2014
- October 2014
- September 2014
- August 2014
- July 2014
- June 2014
- May 2014
- April 2014
- March 2014
- February 2014
- January 2014
- December 2013
- November 2013
- October 2013
- September 2013
- August 2013
- July 2013
- June 2013
- May 2013
- February 2013
- August 2012
- June 2012
- May 2012
- April 2012
You must be logged in to post a comment.