Home > Delphi > Map network path by code

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.

Map paths without drive letters

Map paths without drive letters

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.

Advertisements
  1. December 5, 2014 at 5:23 pm

    Could you please post an exemple on how to use this unit?

    • Jon Lennart Aasenden
      December 6, 2014 at 1:27 am

      MSDN should have more than enough documentation. Just look at the winAPI functions and find that – it should make the unit transparent

      • August 15, 2016 at 4:20 am

        Hi Jon,
        being a complete newbie to object oriented languages, I’m a bit lost here. I am Well familiar with low level languages as I come from an embedded hardware/software design background so picking things up relatively quickly.

        When you say MSDN WinAPI’s, I’ve done a Google search, but face an avalanche of information about everything from .NET to why Bill Gates’s jersey is brown! Could you please post a link to some example code that shows your code in action, thanks.

        I have spent most of today (I mean most, as in more than 5 hours) trying to get this code working without success. I have made good progress and can get everything compiled and up and running, but the machine refuses to connect (my instantiated (Mapped Network Drive) object Folder.Failed = false)

        Your assistance is highly appreciated, thank you. This code will resolve an issue with a program I have all but completed, except for this small yet confounding problem.

        • Jon Lennart Aasenden
          August 15, 2016 at 4:33 am

          I would suspect admin rights is required for the process or user

          • August 15, 2016 at 10:59 pm

            Hi,
            I have admin rights, and I can access the network drive/s using Windows explorer and/or VPN into the systems drive too. But I have so far not been successful in getting the Delphi program to access the network.

            Round 2 today.

            • Jon Lennart Aasenden
              August 16, 2016 at 8:52 am

              If its a service, then you have to give the service runner admin or elevated rights as well.
              So even if you are logged in as admin and compile as admin — the process wont get admin rights automatically.
              It could also be that parameters have changed since I used this code. We were using NT servers back then, and you should check MSDN and look at the parameters for the calls. A lot of API calls have been altered, especially for Windows 10 — so chances are it may be different.
              Try using your real username and password in the parameters, just to make sure. If that works, then the code is as it should be.
              If no user/pass is provided, windows uses the credentials of the current user, which for services by default is “system”.

              • August 16, 2016 at 8:30 pm

                Thanks for your assistance Jon (by the way I’m Graham from New Zealand). Greatly appreciated.

                I have tried using different machines on the network, some with credential requirements, and another without. None have worked so far.

                Thanks for the heads up, I did not realize that if I put nil credentials (as I have done at times) that Windows would use mine. I’m not sure then, when accessing a machine that needs no credentials, and it gets supplied some by Windows anyway, that that may stop it from working.

                As it turns out, I am using my Windows 7 machine to access a Windows NT system on the network. A colleague feels that I should not have to mount the network drive, but just be able to “UNC” straight in, but as I am still in “Learner” mode here, I am definitely interested in learning more about having a mounting point.

                As requested in my earlier message, could you please point me in the direction of example code that instantiates/uses your routine? I am reasonably sure the problem lies with my implementation of the code, and it is unlikely to be a problem with the code itself.

              • August 17, 2016 at 5:04 am

                Success!
                I am now able to connect and map network drives (both with and without credentials). My mistake was I was using \ (backslashes) after the network name, or between the network name and the relative path name. I didn’t realize your code automatically inserts the appropriate \ backslash.

                As part of this, I have found what I am certain (or as certain as an inexperienced user like myself can be) a bug in your code too. the line that reads

                if length(aNetworkPath)>0 then

                Should read

                if length(aNetworkPath)=0 then

                This is line 63 as seen in your code on this webpage. This also gave me a few grey hairs.

                As a final question, is there a means for me to extract the error code, as seen in your mres case in the code? The procedure setLastError is a protected procedure, so I cannot access it via my normal code. I could publish it, but this may introduce a new problem I am not aware of…

                Again though, I would still like to see some example code of this routine in action, as this will provide more details in the optimum use thereof, thank you.

                • Jon Lennart Aasenden
                  August 18, 2016 at 7:18 am

                  Use the normal winapi call “GetLastError” and you should be set 🙂

                • Jon Lennart Aasenden
                  August 29, 2016 at 9:30 pm

                  Shouldnt be a problem, just expose the errorcode. Its open and free code — tailor it to your needs my friend 🙂

    • Jon Lennart Aasenden
      December 9, 2014 at 10:29 am

      Filled out some more info

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: