Mega Code Archive

 
Categories / Delphi / System
 

How to identify drives assigned by the Subst command

Title: How to identify drives assigned by the Subst command. Question: Is it possible to recognize virtual drives? Answer: Recently I had two independent things that forced me focusing on this topic. While moving around on different computers for testing and developing my software I used the Subst command to make virtual drives with common names on the different machines (subst k: "C:\Documents and Settings\HoneggerF\My Documents\Proj", subst k: "F:\Winnt\Profiles\HoneggerF\Personal\Proj etc.). At the same time I tested several Virus Scanners. Some of these have an option to just scan local hard drives. When I selected this option I realized that these programs considered a virtual drive letter the same as a "true" local drive or partition. In other words they where rescanning parts of the disk because they did not make a distinction. Stupid isn't it? Then I remembered that some piece of code in one of my programs a.) under certain conditions searches all local drives for a particular database and b.) estimates free space available on local harddrives and partition. It is easy to guess, yeah I'm ignorant too and treat virtual drives the same. In 1987 I wrote a program for DOS 4.0 that also had this search capability and guess what, ha it was "subst" and "join" aware using a undocumented DOS function call. "OK", I said, "I will fix that, I'm convinced that there will be an appropriate API function similar to GetDriveType call!" But there was none. By analysing the subst.exe of NT 4.0, W2K and WinXP with Dependency Walker (thanks to Microsoft's SDK) I realized that QueryDosDevice must do the job. By further investigations this API I realized that this call for NT-based Windows returns for virtual drives the path prefixed with \??\, i.e. for the above case when called for drive letter k a call to QueryDosDevice would return \??\C:\Documents and Settings\HoneggerF\My Documents\Proj". So for NT base windows I came up with the following function to isolate real local drives and partitions. function IsRootDeviceNT(drive:char):boolean; var fp,dev: string; begin dev:=drive+':'; SetLength(fp,Max_Path+1); SetLength(fp,QueryDosDevice(pchar(dev),pchar(fp),length(fp))); result:=pos('\??\',fp)1; end; For Win95 based (better 16 Bit based) Windows the QueryDosDevice API call turned out to be useless. An analysis of Win95 and Win98 subst.exe showed, that it is still a 16-Bit DOS (MZ Header (Mark Zibowski) only) executable. Because I did not want to deliver a solution with undocumented DOS calls I did a research on MSDN for INT 21 long filename extensions introduced for Win95. The call INT 21, AX=$7160 (truename) that returns a given path name expanded, either replacing the normal or UNC path depending whether the drive letter is a subst or a map got my attraction. I finally arrived to the 16 Bit version of the above NT-function, given here in Borland Pascal: function IsRoot(Drive:Char):WORDBOOL; export; var name: string; truename: string; charbuf: array[0..260] of char; regs: tregisters; result: WORDBOOL; begin IsRoot:=true; name:=Upcase(Drive)+':\'#0; {http://msdn.microsoft.com/library/default.asp?url=/library/en-us/win9x/95func_0488.asp} regs.ax:=$7160; regs.cx:=0; regs.ds:=seg(name[1]); regs.si:=ofs(name[1]); regs.es:=seg(charbuf); regs.di:=ofs(charbuf); Intr($21,Regs); if (regs.Flags AND Fcarry) 0 then begin {on error we assume root} exit; end; dec(name[0]); {if the returned name is longer it cannot be root} result:=StrLen(charbuf) If result then begin {check if it is not a root alias} truename:=StrPas(StrUpper(charbuf)); result:=truename=name; end; IsRoot:=result; end; Wonderful I thought, but how can I use 16 Bit code in a 32 Bit application. Luckily I did the reverse some years ago, which means I know the correct keywords for the next step: 16 Bit thunk and thunking. What does this mean? Put your 16 Bit functions into a 16 Bit Dll and call these functions by means of the thunking interface. Thanks to Google I found a beautiful Delphi Magazine article http://www.thedelphimagazine.com/samples/thunk/thunk95.htm from Brian Long with accompanying Delphi code (QTThunkU unit) that helped me finishing these subst journey with ease. Here is my unit with the helper routines I needed to fix my program. For those who like to experiment I appended a mime encoded zip with a test project FHSubst (Shows virtual drives in a string grid, if any) and the 16 bit dll W95isrt.dll. But warning, this zip does not include the QTThunkU unit from Brian Long. After downloading and joining this unit to the project you have to change QTTunkU as described in the header of USubstHelp. Now that it is documented, I hope that future virus scanners arent subst ignorant any more, that future programs report correctly free space and that our beloved OS will get an different icon for virtual drives because GetDriveType got a companion GetDriveTypeEX. {--------------------------------------------------------------------------- USubstHelp Implements helper routines to detect path substitut drives --------------------------------------------------------------------------- Author: Flurin Honegger (FHO) Edit History: Who When What -------------------------------------------------------------------------- FHO 31.10.2002 First Built Dependencies on other files: see uses clause Needs 16 bit W95isrt.dll helper dll on non nt based soho os versions For QTThunkU (Brian Long) see http://www.thedelphimagazine.com/samples/thunk/thunk95.htm Set $Generic and comment the two lines in the unit initialization section. (* if @QT_Thunk = nil then raise EThunkError.Create('Flat thunks only supported under Windows 95'); *) --------------------------------------------------------------------------} unit USubstHlp; interface uses Classes, QTThunkU; function SubstDrivePath(drive:char):string; //get path name of drive substitutes. Is empty if none. function SubstitutedDrives:TStringList; //get a list of all substituted drives function IsRootDevice(drive:char):boolean; //returs true if the drive is not assoziated wit a path implementation uses Windows, SysUtils; const cIsNt:Boolean=True; function SubstDrivePath(drive:char):string; function SubstDrivePathNT(drive:char):string; var fp,dev: string; begin dev:=drive+':'; SetLength(fp,Max_Path+1); SetLength(fp,QueryDosDevice(pchar(dev),pchar(fp),length(fp))); if pos('\??\',fp)=1 then result:=StrPas(@fp[5]) else result:=''; end; function SubstDrivePath95(drive:char):string; var hW95isrt: HMODULE; begin Result:=''; hW95isrt:=LoadLibrary16('W95isrt.dll'); if (hW95isrt HInstance_Error) then begin SetLength(result,Max_Path+1); SetLength(result,Call16BitRoutine('SubstName',hW95isrt,ccPascal, [drive,pchar(result),length(result)], [sizeof(char),length(result),sizeof(longint)])); //remove network drives returned if pos('\\',result)=1 then result:=''; FreeLibrary16(hW95isrt); end; end; begin If cIsNt then result:=SubstDrivePathNT(Drive) else result:=SubstDrivePath95(Drive) end; function SubstitutedDrives:TStringList; var OldMode: uint; LogicalDrives,s: String; dp: pchar; begin //query data length setlength(LogicalDrives,GetLogicalDriveStrings(0,Nil)); //get zero terminated list GetLogicalDriveStrings(length(LogicalDrives),@LogicalDrives[1]); result:=TStringList.Create; OldMode:=SetErrorMode(SEM_FAILCRITICALERRORS); try dp:=@LogicalDrives[1]; while dp^ #0 do begin s:=SubstDrivePath(dp^); if s'' then result.Add(dp^+':\='+s); dp:=StrEnd(dp)+1; end; finally SetErrorMode(OldMode); end; end; function IsRootDevice(drive:char):boolean; function IsRootDeviceNT(drive:char):boolean; var fp,dev: string; begin dev:=drive+':'; SetLength(fp,Max_Path+1); SetLength(fp,QueryDosDevice(pchar(dev),pchar(fp),length(fp))); result:=pos('\??\',fp)1; end; function IsRootDevice95(drive:char):wordbool; var hW95isrt: HMODULE; begin Result:=True; hW95isrt:=LoadLibrary16('W95isrt.dll'); if (hW95isrt HInstance_Error) then begin result:=wordbool(LoWord(Call16BitRoutine('IsRoot',hW95isrt,ccPascal, [drive],[sizeof(char)]))); FreeLibrary16(hW95isrt); end; end; begin if cIsNt then Result:=IsRootDeviceNT(drive) else Result:=IsRootDevice95(drive); end; function IsNT:Boolean; var osv:_OSVERSIONINFO; begin osv.dwOSVersionInfoSize:=SizeOf(osv); GetVersionEX(osv); result:=osv.dwPlatformId=VER_PLATFORM_WIN32_NT; end; initialization cIsNt:=IsNt; end. {--------------------------cut after this line-----------------------------} MIME-Version: 1.0 Content-Type: application/octet-stream; name="FHSUbst.zip" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="FHSUbst.zip" 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 AAwAAgMAAF0iAAAAAA==