Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Network Drives and TStShellTreeView

Status
Not open for further replies.

paulsv

Programmer
Jun 29, 2007
16
GB
I'm using a TStShellTreeView from Shellshock in D6 to let the user select a
folder to load a file from. This works fine except when the user selects a
folder directly under 'My Network Places'. The reason it goes wrong is that
the path returned is

c:\documents and settings\Username\NetHood\e on Paul's Desktop (L3s2i7)

which I can't use as a path to procedures like fileexists, etc. If I had
selected a folder underneath this, I get a totally different style of path
returned:

\\L3s2i7\e\Docs

which works fine as expected.

How can I convert the first path into the form of the 2nd? I have tried
various shell treeview components and they all seem to do the same thing.

Any help much appreciated.

Paul
 
It should work but if you remove the discriptive text.
I am assuming this is present in the string.
If it is, have you tried extractfilepath() which should return just the path part of the text

c:\documents and settings\Username\NetHood\e

The TShell treeview sample component will return a Unix style path.
But I have to do this to ensure a vaid network path for XP machines
Code:
 S := shelltreeview.Path;
   // remove the drive designation from the path to enable write to WinXP machines
   P := Pos('\C\',uppercase(S));
   if P > 0 then
      delete(S,P,2);

Steve [The sane]: Delphi a feersum engin indeed.
 
Thx for the reply. I tried using ExtractFilePath but it removes the drive designation (e) on the end, presumably as it thinks this is the file name. I also tried hard coding the path to 'c:\documents and settings\Username\NetHood\e' and this didn't work either.
 
is the 'e' a folder or a drive?

Steve [The sane]: Delphi a feersum engin indeed.
 
It helps to understand what's going on if you look at what windows does in an explorer window (split pane - folders on left - files in ritght pane ) :p

To follow, it also helps if in Tools > Folder Options > View > "Display the full path in the title bar" is checked or just make sure the address bar is visible.

Now, in the folder view, drill down to and click "NetHood". You will see ""C:\Documents and Settings\<username>\NetHood" in the title bar.

Click on "c on <computername>", where "c" is the drive letter. Look at your address bar or title bar. You are no longer looking at anything under "C:\Documents and Settings\username\NetHood". You will see it has jumped to "\\<computername>\C"

That's because folder "..\NetHood\c on <computername>" contains nothing more than a link (or shortcut). File "Target.lnk" to be specific. If you view it with a binary editor, you'll see it points to \\<computername>\C which is exactly where it took you when you clicked it.

The bottom line is you're looking in the wrong place.

The correct way to do this is to iterate through all the networked computernames, then iterate through all the shared drives of each computer. This can be very time consuming in real-time, so it is more efficient just to work from a list of shares.

That makes pulling up something like "\\BobsPC\D\Borland Shared\Images\Buttons\alarm.bmp" much easier.

HTH

Roo
Delphi Rules!
 
Roo: This path "\\BobsPC\D\Borland Shared\Images\Buttons\alarm.bmp" is exactly what I was getting at (if not exactly the posters problem) On an NT/XP system you will not be able to write that file, though you will be able to read it.
Not sure how that ties in with your link thing.




Steve [The sane]: Delphi a feersum engin indeed.
 
Thx Roo. I think I understand what you are saying but I'm not sure if it helps me :) As I'm using a standard shell tree view component I can't stop users selecting drives like this, hence my original question as to how to convert one form of path to another. Do you have a suggestion as to how I might get round this problem?
 
Have you looked at the Filename utilites setion in the help files. ExpandUNCFilename for example.


Steve [The sane]: Delphi a feersum engin indeed.
 
ExpandUNCFilename just returns the same thing - the link in c:\documents and settings...

I haven't tried all the others but I bet they are no different :-(
 
It really depends on what you want to do with it: look at it, open, copy, move or delete it. It will also depends on permissions. My solution once was to find an available drive letter, map the drive letter to it with an API call, do my thing, then release the drive letter.

All I'm saying is you can't get there from "NetHood".

sggaunt - your question
is the 'e' a folder or a drive?
is not really a valid question because the answer is; "It's a Share Name, assigned by the admin who set up the share." It might be a root folder, maybe not. It might be named "Z", it might be named "My Big Data Folder".


To see what I mean, open explorer, pick a random folder, right-click, select Properties, then Sharing, check "Share this folder". You can then type whatever you want in "Share name". THAT will be the name as seen from every computer that views your shared folders. NOTE: requires admin privileges. For users to change files, "Allow network users to change my files" must also be checked.

(Don't forget to click "Cancel" or you've done it!)

Our problem is that you're looking for a simple answer to a complex issue.

Roo
Delphi Rules!
 
I am using the component to allow the user to save a file in a location of their chosing, and it feels perfectly reasonable to expect them to be able to select a shared location on the network.

I get the impression that the only answer is not to use a shell component, although what I use in it's place is not clear to me atm.
 
I understand your dilemma and I don't think the problem is with your component. Your task is to determine if the path returned by your component is actually a physical path or just a shortcut that points to it. I was just trying to help you understand the difference and I'm better at coding than explaining intricacies of the file system. Let me see if I can find the code I wrote. It's been a while and I'm running short of time. I'm supposed to be getting ready for a trip.

Roo
Delphi Rules!
 
Ok, found it in my 2002 archives. This project remote connected to our client's network location and took an inventory of all the software we supported. It then created an upgrade patch file specifically for each location, by compiling the delphi source. The part you're interested in was all in a single unit. Here's a call to the function to show how it was used:

Code:
    if IsUncPath(ParamStr(1)) then                                     {!!Added rjm 07-30-02}
      if GetUnc2DriveLetter(ParamStr(1), 'CORS', NewVerDir) then
        if FileExists(copy(NewVerDir, 1, 2) + '\CORS\CAAP\SOURCE\DCC32.EXE') then
          Memo1.Lines.Add(ParamStr(1) + ' successfully mapped to ' + copy(NewVerDir, 1, 2))
        else
          ErrorOut('Unable to map UNC path : ' + ParamStr(1) + ' to ' + copy(NewVerDir, 1, 2))
      else
        ErrorOut('Unable to map UNC drive!')
    else                                                              {!!Added rjm 07-30-02}
      NewVerDir:= ParamStr(1);
Here's the complete unit. Basically, I found it easier to map an available local drive letter to the UNC path, do my read/write directly to it as a drive, then release the shared drive and exit. It worked at over 500 locations we needed to upgrade. You will see the mapping in the initialization of the unit, and the release in finalization.

You won't need most of it but sort out what you can use. I wish I had time to cut and paste, but...
Code:
unit MapNetDrive2;
{$D-}

interface

function IsUncPath(UncPath: string) : boolean;
function GetUnc2DriveLetter(UncPathIn, Terminator: String; var Path: String): boolean;
function CancelUncDrive : boolean;
function ConvertToUNCPath(MappedDrive: string) : string;
function DriveIsMapped(UncPath: String; var Path: String): boolean;
//function GetAssignedDriveLetter(n : integer) : char;

implementation

uses
  Windows, Forms, Dialogs, Sysutils, FileCtrl;

const
  AvailDrvs : set of char = ['C'..'Z'];

var
  AssignedDriveLetters : string;

function AssignedCount : integer;
begin
  result:= length(AssignedDriveLetters);
end;

function IsUncPath(UncPath: string) : boolean;
begin
  result:= (pos('\\', UncPath) = 1) and DirectoryExists(UncPath)
end;

procedure RemoveUnMapableDrive(ch: char);
Begin
  Exclude(AvailDrvs, ch);   //AvailDrvs:= AvailDrvs - [ch];
end;

function ConvertToUNCPath(MappedDrive: string) : string;
var
  RemoteString : array[0..255] of char;
  lpRemote : PChar;
  StringLen : DWord; //Integer;
begin
 lpRemote := @RemoteString;
 StringLen := 255;
 WNetGetConnection(Pchar(ExtractFileDrive(MappedDrive)),
                   lpRemote,
                   StringLen);
 Result := RemoteString;
end;

function DriveIsMapped(UncPath: String; var Path: String): boolean;
var
  ch: Char;
  root: String;
begin
  result:= false;
  root:= 'C:\';
  for ch := 'C' to 'Z' do
    if not (ch in AvailDrvs) then begin
      root[1] := ch;
      if GetDriveType(Pchar(root))= DRIVE_REMOTE then
        if Uppercase(ConvertToUNCPath(root)) = Uppercase(UncPath) then begin
          Path[1]:= root[1]; //just change the drive letter...
          result:= true;
          exit
        end
    end;
end; //DriveIsMapped

(*
function GetAssignedDriveLetter(n : integer) : char;
begin
  if n <= AssignedCount then
    result:= AssignedDriveLetter[n]
  else
    result:= #0;
end;
*)

var
  LogFile : string;

procedure WriteLog(Msg: string);
var f: textfile;
begin
  assignfile(f, LogFile);
  if FileExists(LogFile) then
    append(f)
  else
    rewrite(f);
  writeln(f, FormatDateTime('mm/dd/yy hh:mm:ss "' + Msg + '"', now));
  closefile(f);
end; //WriteLog

function CancelUncDrive : boolean;
var drive: String;
begin
  result:= true;
  if AssignedCount >0 then begin
    drive:= AssignedDriveLetters[AssignedCount] + ':';
    if WNetCancelConnection(pchar(drive), true) = NO_ERROR then begin
      WriteLog('WNetCancelConnection: ' + Drive + ' sucessfully disconnected');
      Delete(AssignedDriveLetters, pos(Drive[1], AssignedDriveLetters), 1)
    end else
    begin
      WriteLog('*** WNetCancelConnection: Unable to disconnect ' + Drive + '! ***');
      result:= false
    end;
  end else
    WriteLog('*** WNetCancelConnection: Called with nothing to disconnect ' + Drive + '! ***');
end;

function FirstAvailDrive: char;
var
  ch: Char;
  root: String;
Begin
  root:= 'C:\';
  for ch := 'C' to 'Z' do
    if ch in AvailDrvs then begin
      root[1] := ch;
      if GetDriveType(Pchar(root))= 1 then
        Break; //The root directory does not exist, so USE IT!!!
    end;
  result:= ch
end; //FirstAvailDrive

function TestPath(const Path: string) : boolean;
var Seconds, Start: TDateTime;
begin
  Start:= Now;
  repeat
    Application.ProcessMessages;
    Seconds:= (Now - Start) * 86400;
  until (Seconds > 1) or DirectoryExists(Path);
  if DirectoryExists(Path) then begin
    WriteLog('TestPath: Directory '+ path + ' found ok.');
    result:= true
  end else
  begin
    WriteLog('*** TestPath: Directory '+ path + ' does not exist! ***');
    result:= false
  end
end; //TestPath

function GetExtendedError(var Error : DWord) : string;
var
  rc : word;
  ErrMsg : array[0..256] of char;
  Provider: string;
begin
  Provider:= 'Microsoft Network';
  Rc:= WNetGetLastError(Error, ErrMsg, sizeof(ErrMsg), pchar(Provider), length(Provider));
  if Rc=0 then result:= ErrMsg else result:= inttostr(error)
end;

function NewMap(drive, UncPath: string) : boolean;
var
  NRW: TNetResource;
  rc : DWord;
begin
  result:= true;
  with NRW do begin
    dwType := RESOURCETYPE_ANY;
    lpLocalName := pchar(drive); // map to this driver letter
    lpRemoteName := pchar(UncPath);
    lpProvider := 'Microsoft Network'; // Must be filled in.  If an empty string is used, it will use the lpRemoteName.
  end;
  rc:= WNetAddConnection2(NRW, pchar(''), pchar(''), CONNECT_UPDATE_PROFILE);
  case rc of
    NO_ERROR:                  result:=true;
    ERROR_ACCESS_DENIED:       showMessage('Access to the network resource was denied.');
    ERROR_ALREADY_ASSIGNED:    showMessage('The local device specified by lpLocalName is already connected to a network resource.');
    ERROR_BAD_DEV_TYPE:        showMessage('The type of local device and the type of network resource do not match.');
    ERROR_BAD_DEVICE:          showMessage('The value specified by lpLocalName is invalid.');
    ERROR_BAD_NET_NAME:        showMessage('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:         showMessage('The user profile is in an incorrect format.');
    ERROR_BAD_PROVIDER:        showMessage('The value specified by lpProvider does not match any provider.');
    ERROR_BUSY:                showMessage('The router or provider is busy, possibly initializing. The caller should retry.');
    ERROR_CANCELLED:           showMessage('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: showMessage('The system is unable to open the user profile to process persistent connections.');
    ERROR_DEVICE_ALREADY_REMEMBERED: showMessage('An entry for the device specified in lpLocalName is already in the user profile.');
    ERROR_EXTENDED_ERROR:      showMessage('Extended error: [' + GetExtendedError(Rc) + ']');
    ERROR_INVALID_PASSWORD:    showMessage('The specified password is invalid.');
    ERROR_NO_NET_OR_BAD_PATH:  showMessage('A network component has not started, or the specified name could not be handled.');
    ERROR_NO_NETWORK:          showMessage('There is no network present.');
    ERROR_DEVICE_IN_USE:       showMessage('The device is in use by an active process and cannot be disconnected.');
    ERROR_NOT_CONNECTED:       showMessage('The name specified by the lpName parameter is not a redirected device, or the system is not currently connected to the device specified by the parameter.');
    ERROR_OPEN_FILES:          showMessage('There are open files, and the fForce parameter is FALSE.');
    else                       showMessage('GetUnc2DriveLetter: Unable to map drive - Undetermined error occured!!!');
  end; //case
end;

function GetUnc2DriveLetter(UncPathIn, Terminator: String; var Path: String): boolean;
//diagnostic version
var
  UncPathOut: String;
  Drive: String;
  n: integer;
  ch : char;
begin
  n:=0;
  ch:=#0;
  result:=false; //assume failure...
  WriteLog('GetUnc2DriveLetter(' + UncPathIn + ', ' + Terminator + ', '+ path + ')');
  UncPathOut:= UNCPathIn;
  try
    ch:= Upcase(FirstAvailDrive);
    if ch in AvailDrvs then begin
      drive:= ch + ':';
      n:= pos(uppercase(Terminator), uppercase(UncPathIn)) - 1;
      Path:= Drive + copy(UncPathIn, n, length(UncPathIn));
      UncPathOut:= copy(UncPathIn, 1, n - 1);
      If DriveIsMapped(UncPathOut, Path) then
        //Path now contains the already mapped drive letter
        WriteLog('GetUnc2DriveLetter: UNC ' + UncPathOut + ' already mapped as ' + path)
      else if NewMap(drive, UncPathOut) then begin
        AssignedDriveLetters:= AssignedDriveLetters + ch;
        WriteLog('GetUnc2DriveLetter: AssignedDriveLetters now ' + AssignedDriveLetters);
        result:=true
      end else
      begin  // try again with next letter
        RemoveUnMapableDrive(ch);
        WriteLog('*** GetUnc2DriveLetter: ' + Ch + ': removed from list of mapable drives - will try again: ***');
        Result:= GetUnc2DriveLetter(UncPathOut + '\' + Terminator, Terminator, Path);
      end
    end else
    begin
      showMessage('GetUnc2DriveLetter: No available drive letters!');
      WriteLog('*** GetUnc2DriveLetter: No available drive letters to try! ***');
      result:= false;
      exit;
    end;
  except
    result:=false;
    WriteLog('*** GetUnc2DriveLetter: Failed with exception : ___ ***');
    showMessage('GetUnc2DriveLetter: Unable to map drive' + ch + ':\');
  end;
  if TestPath(Path) then //Now, try to access the drive to make SURE it is there...
    result:= true
  else if InputQuery('Unable to map ' + UNCPathOut + ' to ' + Drive + '\',
                     'Please map a drive and enter the drive letter:',
                     Drive) then
  begin
    Path:= Upcase(Drive[1]) + ':' + copy(UncPathIn, n, length(UncPathIn));
    WriteLog('GetUnc2DriveLetter: Trying user defined path - ' + path);
    //will work if user enters 'U' or 'U:' or 'U:\'
    result:= TestPath(Path)
  end else
    result:= false;
end; //GetUnc2DriveLetter

procedure GetAllAvailableDrives;
var
  ch: Char;
  root: String;
Begin
  root:= 'C:\';
  for ch := 'C' to 'Z' do
    if ch in AvailDrvs then begin
      root[1] := ch;
      if GetDriveType(Pchar(root)) <> 1 then
        RemoveUnMapableDrive(ch)
    end;
end;

initialization
begin      
  LogFile:= ChangeFileExt(Application.ExeName, '.LOG');
  DeleteFile(LogFile);
  WriteLog('Init:');
  WriteLog(paramstr(0) + ' ' + paramstr(1) + ' ' +  paramstr(2));
  setlength(AssignedDriveLetters, 0);
  GetAllAvailableDrives;
end;

finalization
begin
  while AssignedCount <> 0 do
  begin
    CancelUncDrive;
    application.ProcessMessages;
  end;
  WriteLog('Done');
end

end.

Roo
Delphi Rules!
 
Roo: How well did this work? I ask because I developed a network connect component myself, based on the same API calls.
But I found it to be at best slow and at worse unreliable.
I think the main reason for this is because, the network was not always on.
This means that at every power up things have to sort themselves out again, previously mapped drives get 'lost' etc.
If a network is always on it may be the best way.

But for my applications, when the 'Shellview' components were added to Delphi, I found this to be a blessing, connection time is much quicker, and drives cannot get unmapped because there is no need for mapping.
There can be a problem with Path formats, but I have found ways around this as shown.


Steve [The sane]: Delphi a feersum engin indeed.
 
Thx for the code Roo. I had a look but found it quite hard to work out how to convert it for my own use. Your explanations though made me look into how I might be able to read the target of a link, so I did a general search on 'delphi read shortcut' and this popped out:


When I took out the check for a .lnk extension it worked a treat and did the conversion perfectly! Many thanks for all your help and sggaunt too :)

Paul
 
Paul, Great! I knew it was more than you needed but hoped you could put some old code to good use, if not now in some future project. I put a LOT of work into that and it served its purpose then was set aside once we got all those stores on the same versions with our version control software in place.

Steve, This was a very large cooperation in Houston (hint) and server stability was never an issue.


Roo
Delphi Rules!
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top