Spti - unit detection - drive letter

Hi all,

I’m trying to write an engine to access cd, using spti.
I hope I’m in the right forum…

In the following code (in Delphi… sorry for C\C++ users) , I obtain the cd devices list, and their link.


Procedure TDriverInterfaceSPTI.GetUnitsFromClassDevices;
{--------------------------------------------
  Scan CD-Rom class
  ****
  Sources :
  http://msdn.microsoft.com/library/en-us/devio/base/setupdigetclassdevs.asp
---------------------------------------------}
const
  FuncName : string = 'TDriverInterfaceSPTI.GetUnitsFromClassDevices';
var
  DevList : HDEVINFO;                           // Liste des périphériques de type CD
  DevI : Integer;                               // compteur, pour le Ieme périph de la liste
  Success : Boolean;
  DeviceInterfaceData: TSPDeviceInterfaceData;
  DevData: TSPDevInfoData;
  BytesReturned: DWORD;
  FunctionClassDeviceData: PSPDeviceInterfaceDetailData;
  aUnit : TUnitSPTI;
Begin
  Self.DebugAddMsg(FuncName, '');
  
  Self.Units.Clear;                                                             // Erase unit list

  { get cd devices list }
  DevList := SetupDiGetClassDevs(@CDROMCLASSGUID, nil, 0, DIGCF_PRESENT or DIGCF_INTERFACEDEVICE);
  if (DevList = Pointer(INVALID_HANDLE_VALUE)) then Begin
    Self.DebugAddMsg(FuncName, 'Impossible d''obtenir le handle sur la liste des cd');
    Exit;
  End;

  { informations about the device in the list }
  DevI := 0;
  Repeat
    DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);
    Success := SetupDiEnumDeviceInterfaces(DevList, nil, CDROMCLASSGUID, DevI, DeviceInterfaceData);
    if Success then begin
      DevData.cbSize := SizeOf(DevData);
      BytesReturned := 0;
      SetupDiGetDeviceInterfaceDetail(DevList, @DeviceInterfaceData, nil, 0, BytesReturned, @DevData);
      if (BytesReturned <> 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then begin
        FunctionClassDeviceData := AllocMem(BytesReturned);
        FunctionClassDeviceData.cbSize := 5;
        if SetupDiGetDeviceInterfaceDetail(DevList, @DeviceInterfaceData, FunctionClassDeviceData, BytesReturned, BytesReturned, @DevData) then Begin
          MessageBox(0, PChar(@FunctionClassDeviceData.DevicePath), '', MB_OK);
          aUnit := TUnitSPTI.Create(Self, PChar(@FunctionClassDeviceData.DevicePath));
          Self.Units.Add(aUnit);
        End;
      End;
      Inc(DevI,1);
    End;
  Until not Success;
End;

Well, in this code example, i obtain a messagebox telling me :


"\\?\ide#cdrommatshita_ujda720_dvd#cdrw_______________1.00____#5&306ef82c&0&0.0.0#{53f56308-b6bf-11d0-94f2-00a0c91efb8b}"

Now, I’d like to know which drive letter has this devices (on my system, it’s D:)
The drives path are stored in the registry in this section :

HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices

If someone knows how I can obtain the drive letter in relation with my link.

Thanks in advance

Maybe you can read the registry?

Anyway, the proper way to actually do this is to use the mount manager device. In particular you would use DeviceIOControl with IOCTL_MOUNTMGR_QUERY_POINTS. This should give you all of the various names (including drive letter if it exists) for a particular device. Unfortunately I don’t know how to use this and cannot find any samples in Google.

Here’s a very vague description from MS:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/storage/hh/storage/k307_2d7b44e3-c40f-4626-aad0-5e1cf8843685.xml.asp

I hope someone can shed some light into this.

PS: If it’s in C/C++ I can translate into Delphi for you.

I almost forgot…

An old fashion work around is to drop the modern method of enumerating using device class, and instead to enumerate by opening a handle to each drive letter (i.e. a for loop of a…z) and testing each one to see whether it’s a CDROM device type. Example:

function TForm1.Is_it_CDROM_drive(cDriveLetter : char; show_msgs : Boolean) : Boolean;
var
   szRootName : array[0..4] of char;
   uDriveType : cardinal;
begin
     szRootName[0]:=cDriveLetter;
     szRootName[1]:=':';
     szRootName[2]:='\';
     szRootName[3]:=chr(0);

     uDriveType := GetDriveType(szRootName);

     case uDriveType of
     DRIVE_CDROM:
     begin
          result := true;
     end
     else
     begin
          if show_msgs then
             MessageDlg('Drive type is not CDROM drive type.', mtError, [mbOk], 0);
          result := false;
     end;
     end;
end;

procedure TForm1.FormCreate(Sender: TObject);
{ Iterates CDROM letters from A to Z to find CDROM type drives.
  Then puts the letter into a combo box. }
var drive_letter : char;
    dev_inq_result : T_dev_inq_result;
begin
     for drive_letter:='A' to 'Z' do
     begin
          if Is_it_CDROM_drive(drive_letter, false) then
          begin
               dev_inq_result:=GetDeviceInquiryInfo(drive_letter);
               if dev_inq_result.success then
               begin
                    CB_drive_letter.Items.Add('(' + drive_letter + ':) / ' + dev_inq_result.device_str);
               end
               else
               begin
                    CB_drive_letter.Items.Add(drive_letter);
               end;
          end;
     end;
     if CB_drive_letter.Items.Count>0 then
        CB_drive_letter.ItemIndex:=0;
end;

Thanks Truman for your help.

I know the 2nd source.
It’s the way I’m using in AllFirmwares Online Detect.
But there is a problem : if there exists a connected drive, which don’t have a drive letter, you can’t see it :disagree:

I’m actually using a microsoft code, which works :
http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q305184

Robert Marquardt, the author of JvHidControllerClass.pas indicates me to use his new units available on http://www.sf.net/projects/jedi-apilib .
He writes me too “Drive letters are matched through GetVolumeNameForVolumeMountPoint (which is a Win XP API).”

So I will investigate this way.

Some cd\dvd programmers tells me to use the function CreateFile, and to try to open Hosts (\.\Scsi%d) directly to try to communicates with drives.
It was the old way we use with wnaspi32.dll…

Others tell me to use \.\PhysicalDrive%d

It seems one of them could eb only use with admin rights :S
it was a case i din’t think.
A colleague tells me too, that Nero releases a patch to bypass this problem. is it true?

if you think to another solution, I’m interesting.
I’m looking too for people who could give me advices and why not help.

thanks to you Truman, and to all other people who read this topic

Not so easy :frowning:
I obtain the mount point as indicated bellow.
the guid which is given reffers to the cd class, if i understood the structure, but doesn’t refer to cd devices.

well…


HANDLE
WINAPI
FindFirstVolumeW(
    LPWSTR lpszVolumeName,
    DWORD cchBufferLength
    )

/*++

Routine Description:

    This routine kicks off the enumeration of all volumes in the system.

Arguments:

    lpszVolumeName  - Returns the first volume name in the system.

    cchBufferLength - Supplies the size of the preceeding buffer.

Return Value:

    A valid handle or INVALID_HANDLE_VALUE.

--*/

{
    HANDLE                  h;
    MOUNTMGR_MOUNT_POINT    point;
    PMOUNTMGR_MOUNT_POINTS  points;
    BOOL                    b;
    DWORD                   bytes;

    h = CreateFileW(MOUNTMGR_DOS_DEVICE_NAME, 0,
                    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                    INVALID_HANDLE_VALUE);
    if (h == INVALID_HANDLE_VALUE) {
        return INVALID_HANDLE_VALUE;
    }

    RtlZeroMemory(&point, sizeof(point));

    points = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG(TMP_TAG),
                             sizeof(MOUNTMGR_MOUNT_POINTS));
    if (!points) {
        CloseHandle(h);
        SetLastError(ERROR_NOT_ENOUGH_MEMORY);
        return INVALID_HANDLE_VALUE;
    }

    b = DeviceIoControl(h, IOCTL_MOUNTMGR_QUERY_POINTS, &point, sizeof(point),
                        points, sizeof(MOUNTMGR_MOUNT_POINTS), &bytes, NULL);
    while (!b && GetLastError() == ERROR_MORE_DATA) {
        bytes = points->Size;
        RtlFreeHeap(RtlProcessHeap(), 0, points);
        points = RtlAllocateHeap(RtlProcessHeap(), MAKE_TAG(TMP_TAG), bytes);
        if (!points) {
            CloseHandle(h);
            SetLastError(ERROR_NOT_ENOUGH_MEMORY);
            return INVALID_HANDLE_VALUE;
        }

        b = DeviceIoControl(h, IOCTL_MOUNTMGR_QUERY_POINTS, &point,
                            sizeof(point), points, bytes, &bytes, NULL);
    }

    CloseHandle(h);

    if (!b) {
        RtlFreeHeap(RtlProcessHeap(), 0, points);
        return INVALID_HANDLE_VALUE;
    }

    b = FindNextVolumeW((HANDLE) points, lpszVolumeName, cchBufferLength);
    if (!b) {
        RtlFreeHeap(RtlProcessHeap(), 0, points);
        return INVALID_HANDLE_VALUE;
    }

    return (HANDLE) points;
}

if you wants more, PM me :wink:

harder is to convert C types into delphi :slight_smile:
thansk for your help :slight_smile:

Robert Marquardt, the author of JvHidControllerClass.pas indicates me to use his new units available on http://www.sf.net/projects/jedi-apilib .
He writes me too “Drive letters are matched through GetVolumeNameForVolumeMountPoint (which is a Win XP API).”

So I will investigate this way.
There’s a problem, your CDROM devices from enumeration are not Volume Mount Points. The function does almost the opposite, i.e. gives you the Unique Volume Name from a given Volume Mount Point (i.e. from a drive letter or logical mount path). From MS:
http://msdn.microsoft.com/library/?url=/library/en-us/fileio/fs/getvolumenameforvolumemountpoint.asp

It seems one of them could eb only use with admin rights :S
it was a case i din’t think.
A colleague tells me too, that Nero releases a patch to bypass this problem. is it true?
Yes, but not a patch, rather they wrote a wrapper (driver) that takes admin only function calls from user mode (your program) and calls it for you and passes the results back. It can do this because it’s a driver which by default is in kernel mode.

As for reading the registry, you’re right, too difficult and the problem is that the structure could change in future Windows.

Thanks for the snippet of code. I was told by someone that this would work. I take it that it didn’t work. I’ll check it out if I have time.

Robert Marquardt, the author of JvHidControllerClass.pas indicates me to use his new units available on http://www.sf.net/projects/jedi-apilib .
He writes me too “Drive letters are matched through GetVolumeNameForVolumeMountPoint (which is a Win XP API).”

So I will investigate this way.
Just realised what he meant. You can use this to enumerate all drive letters to give you all of the Unique Volume Names. Then compare the results with the DevicePath (with a bit of editting) you get from SetupDiGetDeviceInterfaceDetail.

:stuck_out_tongue: I’ve finally written one 4 u, which maps device to drive letters using the above method, i.e. SetupDixxx & GetVolumeNameForVolumeMountPoint functions.

I’m sharing this with everyone. The code is written under Visual Studio .NET 2002/Visual C++. It enumerates all available CD/DVD devices, including ones with no mounted letters, and if any, displays the mapped drive letter.

http://www.cdtool.pwp.blueyonder.co.uk/MapCDLettersbyAtoZ.zip

Note, I’m too lazy, it can be improved further by using GetLogicalDrives or GetLogicalDriveStrings functions to enumerate only those available drive letters, instead of ‘A’ to ‘Z’.

I just wonder why there are no such code on the net and everywhere says it cannot be done? :confused:

thanks a lot for your time and this example :wink:

Hi Boys and Girls :wink:

I want mount partition with IOCTL_MOUNTMGR_CREATE_POINT, but i can’t :frowning:


typedef struct _tagMountMngrMountPoint
{
  ULONG  SymbolicLinkNameOffset;
  USHORT  SymbolicLinkNameLength;
  ULONG  UniqueIdOffset;
  USHORT  UniqueIdLength;
  ULONG  DeviceNameOffset;
  USHORT  DeviceNameLength;
} MOUNTMGR_MOUNT_POINT, *PMOUNTMGR_MOUNT_POINT;

typedef struct _tagMountPoint
{
    MOUNTMGR_MOUNT_POINT MngrMountPoint;
    WCHAR res0[MAX_PATH];
    WCHAR res1[MAX_PATH];
} MOUNTPOINT, *PMOUNTPOINT;

...
...

MOUNTPOINT                MountPoint;
DWORD Returned;

HANDLE hManager = CreateFile(
                _T("\\\\.\\MountPointManager"),
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_OVERLAPPED,
                NULL
                );
            
if (hManager != INVALID_HANDLE_VALUE)
{
    sprintf(MountPoint.res0, _T("\\DosDevices\\%c:"), driveLetter);
    MountPoint.MngrMountPoint.SymbolicLinkNameLength    = sizeof(MountPoint.res0) * 2;
    MountPoint.MngrMountPoint.SymbolicLinkNameOffset    = sizeof(MOUNTMGR_MOUNT_POINT);

    sprintf(MountPoint.res1, _T("\\Device\\Harddisk%d\\Partition%d"), nDisk, nPart);
    MountPoint.MngrMountPoint.DeviceNameLength            = sizeof(MountPoint.res1) * 2; 
    MountPoint.MngrMountPoint.DeviceNameOffset            = sizeof(MOUNTMGR_MOUNT_POINT);

    if(!DeviceIoControl(
        hManager,
        IOCTL_MOUNTMGR_CREATE_POINT,
        &MountPoint,
        sizeof(MountPoint),
        NULL,
        0,
        &Returned,
        NULL
        )
    )
    {
        DbgPlain (DBG, _T("Failed to create symbolic link"));
    }
}
...
...

Where i make mistake? What is wrong?

Please, help me.

With regards
Motokultivator