System Delphi

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==