Link to home
Start Free TrialLog in
Avatar of angerer
angerer

asked on

connect to a network drive (unc path)

i have to load a file from a network drive within a service. i know that the api function FindFirstFileW excepts an UNC-path. but before i have to connect to the other machine. simply as i mount a drive in the windows-explorer.

which api function(s) do i have to use ??

thanks very much
bernhard



Dipl. Ing. Bernhard Angerer
IFT / Vienna University of Technology
Karlsplatz 13/311
A-1040 Wien
Tel: +43 1 58801 31110
Fax: +43 1 58801 31199
angerer@mail.ift.tuwien.ac.at
Avatar of ZifNab
ZifNab

angerer, can you be more specific?
ASKER CERTIFIED SOLUTION
Avatar of Almighty_Phoenix
Almighty_Phoenix

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of angerer

ASKER

ok - i will be more specific (you are right)

the thing is that my code is running as a nt-service. there are no drives mounted when no user is logged onto the system, so my service has to connect by its own to another machine. how can i connect to a unc-path ?


// START: this is from the unit SysUtils
function FileAge(const FileName: string): Integer;
var
  Handle: THandle;
  //FindData: TWin32FindData;
  FindData: TWin32FindDataW;
  LocalFileTime: TFileTime;
  hStr: WideString;
begin
  hStr := FileName;
  Handle := FindFirstFileW(PWideChar(hStr), FindData);
  //Handle := FindFirstFile(PChar(FileName), FindData);
  if Handle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(Handle);
    if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
    begin
      FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
      if FileTimeToDosDateTime(LocalFileTime, LongRec(Result).Hi,
        LongRec(Result).Lo) then Exit;
    end;
  end;
  Result := -1;
end;

function FileExists(const FileName: string): Boolean;
begin
  Result := FileAge(FileName) <> -1;
end;
// END: this is from the unit SysUtils


procedure TForm1.Button1Click(Sender: TObject);
begin
//\\ecipca.ift.tuwien.ac.at\Terminal
  if FileExists('\\ecipca.ift.tuwien.ac.at\Terminal\TERMINAL.TXT') then
    Edit1.Text := 'exists'
  else
    Edit1.Text := 'exists not';
end;


in this code i try to write my own 'FileExists' function that try to connect to the machine 'ecipca.ift.tuwien.ac.at'.


thanks
bernhard


I think I understand that you want to control the network in a certain way, but you will of course need to enumerate the names.

I have an example, which will do the trick (BOTH in 95/98/NT). I have seen examples that don't.

The original sources is made by Brad Stowers, but here you will also be met by an option to setup all available drives as NetWork drives.

Simply make a new application, make an OnFormCreate event procedure and paste the following lines to unit1:

unit unit1;

// Original sources by Brad Stowers
//
// Brad Stowers    bstowers@pobox.com     10/19/96
//
// Following added by Thomas Williams (c) 01/05/99
// -------------------------------------
// Automatic Network drive connection support.
// You can use ExpandUNCFileName(const FileName: string): string;
// but when accessing files on Network drives, it will behave as
// normal.

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, StdCtrls, ExtCtrls, Menus;

Const
  STAY_IN_NETWORK_ON_REBOOT = False;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    Button1: TButton;
    rgScope: TRadioGroup;
    GroupBox1: TGroupBox;
    GroupBox2: TGroupBox;
    cbTypeAny: TCheckBox;
    cbTypeDisk: TCheckBox;
    cbTypePrint: TCheckBox;
    cbUsageAll: TCheckBox;
    cbUsageConnectable: TCheckBox;
    cbUsageContainer: TCheckBox;
    cbNetDrives: TCheckBox;
    NetTree: TTreeView;
    procedure Button1Click(Sender: TObject);

    procedure BuildWindow;
    Function GetFreeDriveLetter: Char;
  public
    procedure Open_Do_Close_Enum(const ParentNode: TTreeNode;
                                 ResScope, ResType, ResUsage: DWORD;
                                 const NetContainerToOpen: PNetResource);
    function OpenEnum(const NetContainerToOpen: PNetResource;
                      ResScope, ResType, ResUsage: DWORD): THandle;
    function EnumResources(const ParentNode: TTreeNode;
                           ResScope, ResType, ResUsage: DWORD;
                           hNetEnum: THandle): UINT;
    procedure MapDrive(Res: TNetResource; StayInNetwork: Boolean);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
  ResScope,
  ResType,
  ResUsage: DWORD;
begin
  NetTree.Items.Clear;

  case rgScope.ItemIndex of
    1: ResScope := RESOURCE_GLOBALNET;
    2: ResScope := RESOURCE_REMEMBERED;
  else
    ResScope := RESOURCE_CONNECTED;
  end;

  ResType := 0;
  if cbTypeAny.Checked then
    ResType := ResType or RESOURCETYPE_ANY;
  if cbTypeDisk.Checked then
    ResType := ResType or RESOURCETYPE_DISK;
  if cbTypePrint.Checked then
    ResType := ResType or RESOURCETYPE_PRINT;

  ResUsage := 0;
  if cbUsageConnectable.Checked then
    ResUsage := ResUsage or RESOURCEUSAGE_CONNECTABLE;
  if cbUsageContainer.Checked then
    ResUsage := ResUsage or RESOURCEUSAGE_CONTAINER;

  Open_Do_Close_Enum(NetTree.Items.Add(NIL, 'Network Resources'),
                     ResScope, ResType, ResUsage, NIL);
end;


procedure TForm1.Open_Do_Close_Enum(const ParentNode: TTreeNode;
                                    ResScope, ResType, ResUsage: DWORD;
                                    const NetContainerToOpen: PNetResource);
var
  hNetEnum: THandle;
begin
  hNetEnum := OpenEnum(NetContainerToOpen, ResScope, ResType, ResUsage);
  if (hNetEnum = 0) then exit;
  EnumResources(ParentNode, ResScope, ResType, ResUsage, hNetEnum);
  if (NO_ERROR <> WNetCloseEnum(hNetEnum)) then
    ShowMessage('WNetCloseEnum Error');
end;

function TForm1.OpenEnum(const NetContainerToOpen: PNetResource;
                         ResScope, ResType, ResUsage: DWORD): THandle;
var
  hNetEnum: THandle;
begin
  Result := 0;
  if (NO_ERROR <> WNetOpenEnum(ResScope, ResType, ResUsage,
                               NetContainerToOpen, hNetEnum))
  then
    showmessage('WNetOpenEnum Error')
  else
    Result := hNetEnum;
end;


function TForm1.EnumResources(const ParentNode: TTreeNode;
                              ResScope, ResType, ResUsage: DWORD;
                              hNetEnum: THandle): UINT;
  function ShowResource(const ParentNode: TTreeNode; Res: TNetResource): TTreeNode;
  var
    i: Integer;
  begin
    Result := NetTree.Items.AddChild(ParentNode, string(Res.lpRemoteName));
    //** Modification - Begin **
    //find //<Server>/<Resource>
    i:= 3;
    While (i<Length(Result.Text)) AND (Result.Text[i]<>'\') do inc(i);
    If (Copy(Result.Text,1,2)='\\')
      AND (i<Length(Result.Text))
      AND (Res.dwType OR RESOURCETYPE_DISK>0) //make sure it's a diskdrive
     AND cbNetDrives.Checked
    then MapDrive(Res,STAY_IN_NETWORK_ON_REBOOT);
    //** Modification - End **
  end;

const
  RESOURCE_BUF_ENTRIES = 2000;
var
  ResourceBuffer: array[1..RESOURCE_BUF_ENTRIES] of TNetResource;
  i, ResourceBuf, EntriesToGet: DWORD;
  NewNode: TTreeNode;
begin
  Result := 0;
  while TRUE do
  begin
    ResourceBuf := sizeof(ResourceBuffer);
    EntriesToGet := RESOURCE_BUF_ENTRIES;

    if (NO_ERROR <> WNetEnumResource(hNetEnum,
                                     EntriesToGet,
                                     @ResourceBuffer,
                                     ResourceBuf)) then
    begin
      case GetLastError() of
        NO_ERROR:
          // Drop out of the switch, walk the buffer
          break;
        ERROR_NO_MORE_ITEMS:
          // Return with 0 code because this only happens when we got
          //   RESOURCE_BUF_ENTRIES entries on the previous call to
          //   WNetEnumResource, and there were coincidentally exactly
          //   RESOURCE_BUF_ENTRIES entries total in the enum at the time of
          //   that previous call
          exit;
      else
        ShowMessage('WNetEnumResource Error');
        Result := 1;
        exit;
      end;
    end;

    for i := 1 to EntriesToGet do
    begin
      NewNode := ShowResource(ParentNode, ResourceBuffer[i]);
      if (ResourceBuffer[i].dwUsage and RESOURCEUSAGE_CONTAINER) <> 0 then
        Open_Do_Close_Enum(NewNode,
                           ResScope,
                           ResType,
                           ResUsage,
                           @ResourceBuffer[i]);
    end;
  end;
end;

procedure TForm1.BuildWindow;
Begin
  Width := 356;
  Height := 420;
  With TButton.Create(Self) do
  begin
    SetBounds(13,96,91,25);
    Caption := 'Enum Resources';
    Default := True;
    TabOrder := 3;
    OnClick := Button1Click;
    parent := Self;
  end;
  rgScope:= TRadioGroup.Create(Self);
  With rgScope do
  Begin
    SetBounds(8,8,105,73);
    Caption := 'Scope';
    Parent := Self;
    Items.Add('Connected');
    Items.Add('Global');
    Items.Add('Remembered');
    ItemIndex := 1;
    TabOrder := 0;
    TabStop := True;
  end;
  GroupBox1:= TGroupBox.Create(Self);
  With GroupBox1 do
  begin
    SetBounds(232,8,113,73);
    Caption := 'Resource Usage';
    TabOrder := 2;
    Parent := Self;
  end;
  cbUsageAll:= TCheckBox.Create(GroupBox1);
  With cbUsageAll do
  Begin
    SetBounds(8,16,97,17);
    Caption := 'All';
    TabOrder := 0;
    Parent := GroupBox1;
  end;
  cbUsageConnectable:= TCheckBox.Create(GroupBox1);
  With cbUsageConnectable do
  begin
    SetBounds(8,32,97,17);
    Caption := 'Connectable';
    State := cbChecked;
    TabOrder := 1;
    Parent := GroupBox1;
  end;
  cbUsageContainer:= TCheckBox.Create(GroupBox1);
  With cbUsageContainer do
  Begin
    SetBounds(8,48,97,17);
    Caption := 'Container';
    State := cbChecked;
    TabOrder := 2;
    Parent := GroupBox1;
  end;
  GroupBox2:= TGroupBox.Create(Self);
  With GroupBox2 do
  Begin
    SetBounds(128,8,97,73);
    Caption := 'Resource Type';
    TabOrder := 1;
    Parent := Self;
  End;
  cbTypeAny:= TCheckBox.Create(GroupBox2);
  With cbTypeAny do
  Begin
    SetBounds(8,16,80,17);
    Caption := 'Any';
    TabOrder := 0;
    Parent := GroupBox2;
  end;
  cbTypeDisk:= TCheckBox.Create(GroupBox2);
  With cbTypeDisk do
  Begin
    SetBounds(8,32,80,17);
    Caption:= 'Disk';
    State := cbChecked;
    TabOrder := 1;
    Parent := GroupBox2;
  end;
  cbTypePrint:= TCheckBox.Create(GroupBox2);
  With cbTypePrint do
  begin
    SetBounds(8,48,80,17);
    Caption := 'Print';
    TabOrder := 2;
    Parent := GroupBox2;
  end;
  NetTree:= TTreeView.Create(Self);
  With NetTree do
  Begin
    SetBounds(8,128,327,249);
    Parent:= Self;
    Indent := 19;
    TabOrder := 4;
  end;
  cbNetDrives:= TCheckBox.Create(Self);
  With cbNetDrives do
  Begin
    SetBounds(125,96,191,25);
    Checked := True;
    Caption := 'Find and add Network drives';
    Parent := Self;
  End;
End;

Function TForm1.GetFreeDriveLetter: Char;
var
  S: String;
  drives  : set of 0..25;
  drive   : integer;
Begin
  S:= '';
  DWORD( drives ) := GetLogicalDrives;
  for drive := 0 to 25 do
    if not(drive in drives) then
      S:= S + Chr( drive + Ord( 'A' ));
  If S<>'' then
    Result:= S[1]
  else
    Result:= #0;
End;

procedure TForm1.MapDrive(Res: TNetResource; StayInNetwork: Boolean);
Var
  Error: DWORD;
  NetStay: WORD;
  S: String;
  Drive: Array[0..1] of Char;
Begin
  Drive[0]:= GetFreeDriveLetter;
  If Drive[0]<>#0 then
  Begin
    Drive[1]:= ':';
    Res.lpLocalName:= Drive;
    If StayInNetwork then
      NetStay:= CONNECT_UPDATE_PROFILE
    else
      NetStay:= 0;
    Error:= WNetAddConnection2(
      Res,       // points to structure that specifies connection details
      nil,       // points to password string
      nil,       // points to user name string
      NetStay    // set of bit flags that specify connection options
     );
    Case Error of
      ERROR_ACCESS_DENIED:
        S:= 'Access to the network resource was denied.';
      ERROR_ALREADY_ASSIGNED:
        S:= 'The local device specified by lpLocalName is already connected to a network resource.';
      ERROR_BAD_DEV_TYPE:
        S:= 'The type of local device and the type of network resource do not match.';
      ERROR_BAD_DEVICE:
        S:= 'The value specified by lpLocalName is invalid.';
      ERROR_BAD_NET_NAME:
        S:= 'The value specified by lpRemoteName is not acceptable to any network resource provider. The resource name is invalid, or the named resource cannot be located.';
      ERROR_BAD_PROFILE:
        S:= 'The user profile is in an incorrect format.';
      ERROR_BAD_PROVIDER:
        S:= 'The value specified by lpProvider does not match any provider.';
      ERROR_BUSY:
        S:= 'The router or provider is busy, possibly initializing. The caller should retry.';
      ERROR_CANCELLED:
        S:= 'The attempt to make the connection was cancelled by the user through a dialog box from one of the network resource providers, or by a called resource.';
      ERROR_CANNOT_OPEN_PROFILE:
        S:= 'The system is unable to open the user profile to process persistent connections.';
      ERROR_DEVICE_ALREADY_REMEMBERED:
        S:= 'An entry for the device specified in lpLocalName is already in the user profile.';
      ERROR_EXTENDED_ERROR:
        S:= 'A network-specific error occured. Call the WNetGetLastError function to get a description of the error.';
      ERROR_INVALID_PASSWORD:
        S:= 'The specified password is invalid.';
      ERROR_NO_NET_OR_BAD_PATH:
        S:= 'A network component has not started, or the specified name could not be handled.';
      ERROR_NO_NETWORK:
        S:= 'There is no network present.';
    end;
  end else
  Begin
    Error:= 1;
    S:= 'There is no free drives left';
  End;
  If Error<>0 then messageDlg(S,mtError,[mbOk],0);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  BuildWindow;
end;

end.


Regards,
Williams
Avatar of angerer

ASKER

i am sorry,
i dont need to enumerate something.

the only thing what i have to do is to load a ascii-file from another machine from a windows-nt service.


i have found the following:

function AddConnection:boolean;
var ANetResource:TNetResource;
    PW, UN:array[0..200] of char;
begin
    with ANetResource do begin
          dwType:=RESOURCETYPE_DISK;
          lpLocalName:='K:';
          lpRemoteName:='\\MyNetworkPath\MyLW';
          lpProvider:=nil;
    end;
    StrCopy(PW,'mypasswordonserver');
    StrCopy(UN,'myusernameonserver');
    AddConnection:=(WNetAddConnection2(ANetResource, @PW, @UN,  0)=0);
end;


the function 'AddConnection' works perfect in a normal delphi application. but within a TServiceApplication it returns the errorcode 1312 ??? (i did not find the error-constant with this value.)

maybe you know some hints ? it should be really possible to do this also from a service.

thanks
bernhard

Hi Bernhard,

I don't think that this is a Delphi problem (so you might as well reject the proposed answer :-), it more like a NT issue. When you install a service it is by default installed to run under the System account. This account doesn't have access to the network! So what you have to do, is to open Control Panel / Services, find you service in the list, click Startup and specify a privilegde user (f.ex. Adminstrator) to run the service..
My guess is it will be working now...
I'm not sure about this, but I traced the error to be

ERROR_NO_SUCH_LOGON_SESSION
(A specified logon session does not exist. It may already have been terminated)

BUT first of all: WNetAddConnection2 does return a DWORD containing an error code, where zero is a correct response.
Have you tried to trace it?

Though I think Blackman is right about this, but if his example doesn't work you could also try:

DWORD WNetAddConnection
    LPTSTR  lpszRemoteName,      // address of network device name
    LPTSTR  lpszPassword,      // address of password
    LPTSTR  lpszLocalName       // address of local device name
   );

Like..

Name:= '\\<Server>\<Drive>'; //Type an example
Password:= '<Password>';
Drive:= '<Driveletter>:';
ErrorCode := WNetAddConnection(pChar(Name),pChar(Password),pChar(Drive));

This command has been built for backwards-combatiblity with elder OS's.

Regards,
Williams
Avatar of angerer

ASKER

Dear BlackMan !

you are very right.
i could manage to connect when the service was assigned with a user that had enough rights.

but my solution still has a problem with different users than the service that are logged on the system and are using the explorer.

the explorer recognizes the new drive letter - but if the user that is logged in does not have the rights that the service has ... thats a problem. (but it should be possible that a user can work on this machine :-) )

if you know a way how to suppress that a drive-letter is mounted when using the function 'WNetAddConnection2' than please tell me. would be perfect if i could access the file just with the unc-path !?

thanks
bernhard