Re: Retrieving mapped drives from USB MSC device

Tech-Archive recommends: Fix windows errors by optimizing your registry



Sasha wrote:

I need to get USB device ID from USB MSC device string descriptors. I can enumerate all USB hubs and ports. I can then find what devices are connected to which ports and get their serial number in string descriptors. Now I need to find what mapped drives does this particular USB Mass Storage device have. I would really appreciate any advise as I am literally stuck. What you propose is to walk through all removable drives and query their information with IOCTL_STORAGE_QUERY_PROPERTY. Unfortunately, this does not give me USB device serial number that I need.

Enumerating the USBVIEW way is a bit intrusive. I have seen spurious surprise removal dialogs sometimes.
Yo can extract the serial number from the "SymbolicName".
I have enclosed a Delphi (Pascal) source which shows the trick.
unit SafeRemovalMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Controls, Forms,
  Dialogs, StdCtrls,
  JwaDbt;

type
  TSafeRemovalForm = class(TForm)
    Description: TLabel;
    DriveList: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure WMDeviceChange(var Msg: TWMDeviceChange); message WM_DEVICECHANGE;
    procedure DriveListDblClick(Sender: TObject);
  public
    DriveMountPoints: TStringList;
    procedure FillDriveList;
  end;

var
  SafeRemovalForm: TSafeRemovalForm;

implementation

uses
  JwaWinBase, JwaWinType,
  Cfg, CfgMgr32, SetupApi;

{$R *.dfm}

// encapsulate GetVolumeNameForVolumeMountPoint in a Delphi-style function

function GetVolumeNameForVolumeMountPointString(Name: string): string;
var
  Volume: array [0..MAX_PATH] of Char;
begin
  FillChar(Volume[0], SizeOf(Volume), 0);
  GetVolumeNameForVolumeMountPoint(PChar(Name), @Volume[0], SizeOf(Volume));
  Result := Volume;
end;

// fills the TStringList with the mount points of all removable drives

procedure FillInRemovableDriveMountPoints(MountPoints: TStrings);
const
  MAX_DRIVES = 26;
var
  I: Integer;
  dwDriveMask: DWORD;
  DriveName: string;
begin
  MountPoints.Clear;
  // get all mounted drive letters as bitmask
  dwDriveMask := GetLogicalDrives;
  DriveName := 'A:\';
  // check all drive letters
  for I := 0 to MAX_DRIVES - 1 do
    // if drive letter exists
    if (dwDriveMask and (1 shl I)) <> 0 then
    begin
      DriveName[1] := 'A';
      Inc(DriveName[1], I);
      // see if it is a removable drive
      if GetDriveType(PChar(DriveName)) = DRIVE_REMOVABLE then
        // store mount point string and corresponding drive letter in list
        MountPoints.AddObject(GetVolumeNameForVolumeMountPointString(DriveName), TObject(DriveName[1]));
    end;
end;

// Delphi style encapsulation for CM_Get_Device_ID

function GetDeviceID(Inst: DEVINST): string;
var
  Buffer: PTSTR;
  Size: ULONG;
begin
  CM_Get_Device_ID_Size(Size, Inst, 0);
  // Required! See DDK help for CM_Get_Device_ID
  Inc(Size);
  Buffer := AllocMem(Size * SizeOf(TCHAR));
  CM_Get_Device_ID(Inst, Buffer, Size, 0);
  Result := Buffer;
  FreeMem(Buffer);
end;

// simple extraction of the bus name from DeviceID string

function ExtractBus(DeviceID: string): string;
begin
  Result := Copy(DeviceID, 1, Pos('\', DeviceID) - 1);
end;

// get the "SymbolicName" registry entry of a device
// for an USB device this string contains VID, PID and SerialNumber string

function GetSymbolicName(Inst: DEVINST): string;
var
  Len: DWORD;
  Key: HKEY;
  // a hopefully sufficiently large buffer
  Buffer: array [0..4095] of Char;
begin
  CM_Open_DevNode_Key(Inst, KEY_READ, 0,
    REGDISPOSITION(RegDisposition_OpenExisting), Key, 0);
  Buffer[0] := #0;
  if Key <> INVALID_HANDLE_VALUE then
  begin
    Len := SizeOf(Buffer);
    RegQueryValueEx(Key, 'SymbolicName', nil, nil, @Buffer[0], @Len);
    RegCloseKey(Key);
  end;
  Result := Buffer;
end;

// extract a 4 digit hex number from SymbolicName
// example "\??\USB#Vid_08ec&Pid_0010#0918121014000B59#{a5dcbf10-6530-11d2-901f-00c04fb951ed}"

function ExtractNum(const SymbolicName, Prefix: string): Integer;
var
  S: string;
  N: Integer;
begin
  S := LowerCase(SymbolicName);
  N := Pos(Prefix, S);
  if N > 0 then
  begin
    S := '$' + Copy(SymbolicName, N + Length(Prefix), 4);
    Result := StrToInt(S);
  end
  else
    Result := 0;
end;

function ExtractVID(const SymbolicName: string): Integer;
begin
  Result := ExtractNum(SymbolicName, 'vid_');
end;

function ExtractPID(const SymbolicName: string): Integer;
begin
  Result := ExtractNum(SymbolicName, 'pid_');
end;

function ExtractSerialNumber(SymbolicName: string): string;
var
  N: Integer;
begin
  N := Pos('#', SymbolicName);
  if N >= 0 then
  begin
    SymbolicName := Copy(SymbolicName, N + 1, Length(SymbolicName));
    N := Pos('#', SymbolicName);
    if N >= 0 then
    begin
      SymbolicName := Copy(SymbolicName, N + 1, Length(SymbolicName));
      N := Pos('#', SymbolicName);
      if N >= 0 then
        Result := Copy(SymbolicName, 1, N - 1)
      else
        Result := '';
    end;
  end
  else
    Result := '';
end;

// find the "bus" DeviceID for a given mount point

function GetDriveInstanceID(MountPointName: string; var DeviceInst: DEVINST): Boolean;
const
  GUID_DEVINTERFACE_VOLUME: TGUID = '{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}';
var
  StorageGUID: TGUID;
  PnPHandle: HDEVINFO;
  DevData: TSPDevInfoData;
  DeviceInterfaceData: TSPDeviceInterfaceData;
  FunctionClassDeviceData: PSPDeviceInterfaceDetailData;
  Success: LongBool;
  Devn: Integer;
  BytesReturned: DWORD;
  Inst: DEVINST;
  S, FileName, MountName, DevID: string;
begin
  Result := False;
  DeviceInst := 0;
  // enumerate all volumes
  StorageGUID := GUID_DEVINTERFACE_VOLUME;
  PnPHandle := SetupDiGetClassDevs(@StorageGUID, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
  if PnPHandle = Pointer(INVALID_HANDLE_VALUE) then
    Exit;
  Devn := 0;
  repeat
    DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);
    Success := SetupDiEnumDeviceInterfaces(PnPHandle, nil, StorageGUID, Devn, DeviceInterfaceData);
    if Success then
    begin
      DevData.cbSize := SizeOf(DevData);
      BytesReturned := 0;
      SetupDiGetDeviceInterfaceDetail(PnPHandle, @DeviceInterfaceData, nil, 0, BytesReturned, @DevData);
      if (BytesReturned <> 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
      begin
        FunctionClassDeviceData := AllocMem(BytesReturned);
        try
          FunctionClassDeviceData.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
          if SetupDiGetDeviceInterfaceDetail(PnPHandle, @DeviceInterfaceData,
            FunctionClassDeviceData, BytesReturned, BytesReturned, @DevData) then
          begin
            FileName := PTSTR(@FunctionClassDeviceData.DevicePath[0]);
            // get the grandparent DevNode which is the "bus" device
            // like "USB". This is the DevNode for CM_Request_Device_Eject and
            // several other useful operations
            Inst := DevData.DevInst;
            CM_Get_Parent(Inst, Inst, 0);
            CM_Get_Parent(Inst, Inst, 0);
            DevID := GetDeviceID(Inst);

            // no need in this example to check for USB only
            // if ExtractBus(DevID) = 'USB' then
            begin
              S := '\';
              S := PTSTR(@FunctionClassDeviceData.DevicePath) + S;
              MountName := GetVolumeNameForVolumeMountPointString(S);
              if MountName = MountPointName then
              begin
                DeviceInst := Inst;
                Result := True;
                Exit;
              end;
            end;
          end;
        finally
          FreeMem(FunctionClassDeviceData);
        end;
      end;
    end;
    Inc(Devn);
  until not Success;
  SetupDiDestroyDeviceInfoList(PnPHandle);
end;

//============================================================================

procedure TSafeRemovalForm.FormCreate(Sender: TObject);
begin
  // never forget to load the dynamically linked APIs
  LoadSetupApi;
  LoadConfigManagerApi;
  DriveMountPoints := TStringList.Create;
  DriveMountPoints.Sorted := True;
  // initialize drive list
  FillDriveList;
end;

procedure TSafeRemovalForm.FormDestroy(Sender: TObject);
begin
  DriveMountPoints.Free;
  UnloadConfigManagerApi;
  UnloadSetupApi;
end;

procedure TSafeRemovalForm.WMDeviceChange(var Msg: TWMDeviceChange);
begin
  // watch for volumes being added or removed and update drive list
  if (Msg.Msg = WM_DEVICECHANGE) and
    (((Msg.Event = DBT_DEVICEARRIVAL) and
      (PDevBroadcastHeader(Msg.dwData).dbcd_devicetype = DBT_DEVTYP_VOLUME)) or
    (Msg.Event = DBT_DEVICEREMOVECOMPLETE)) then
    FillDriveList;
end;

procedure TSafeRemovalForm.FillDriveList;
var
  S: string;
  I: Integer;
  Inst: DEVINST;
  SymbolicName: string;
begin
  // update the list of drive mount points
  FillInRemovableDriveMountPoints(DriveMountPoints);

  // show the list of drive letters from the drive mount point list
  DriveList.Items.BeginUpdate;
  DriveList.Items.Clear;
  S := 'A:';
  for I := 0 to DriveMountPoints.Count - 1 do
  begin
    S[1] := Char(DriveMountPoints.Objects[I]);
    GetDriveInstanceID(DriveMountPoints[I], Inst);
    SymbolicName := GetSymbolicName(Inst);
    DriveList.Items.AddObject(S + ' ' + ExtractSerialNumber(SymbolicName), TObject(Inst));
  end;
  DriveList.Items.EndUpdate;
end;

procedure TSafeRemovalForm.DriveListDblClick(Sender: TObject);
var
  VetoType: PNP_VETO_TYPE;
  // VetoBuffer: array [0..MAX_PATH-1] of TCHAR;
  I: Integer;
begin
  if DriveList.ItemIndex <> -1 then
    // find the mount point name for the drive letter clicked
    for I := 0 to DriveMountPoints.Count - 1 do
      if Char(DriveMountPoints.Objects[I]) = DriveList.Items[DriveList.ItemIndex][1] then
      begin
        VetoType := 0;
        // try to do a silent safe removal
        // for drives not able to do a safe removal the function simply fails
        // FillChar(VetoBuffer[0], SizeOf(VetoBuffer), 0);
        // CM_Request_Device_Eject(DEVINST(DriveList.Items.Objects[I]),
        //  @VetoType, @VetoBuffer[0], Length(VetoBuffer), 0);

        // do a safe removal with dialog
        CM_Request_Device_Eject(DEVINST(DriveList.Items.Objects[DriveList.ItemIndex]),
         @VetoType, nil, 0, 0);
      end;
end;

end.


Relevant Pages

  • Importing files from multiple drives
    ... Dim stDocName As String ... Private Const DRIVE_UNKNOWN = 0 ... 'Returns all mapped drives ...
    (microsoft.public.access.formscoding)
  • RE: UNC
    ... Private Const DRIVE_UNKNOWN = 0 ... ByVal lpszRemoteName As String, ... 'Returns all mapped drives ... Dim lngRet As Long ...
    (microsoft.public.access.modulesdaovba)
  • Re: Help with code (Importing files)
    ... Dim stDocName As String ... Private Const DRIVE_UNKNOWN = 0 ... 'Returns all mapped drives ...
    (microsoft.public.access.forms)
  • RE: Storing file path in Access database problem
    ... lpszRemoteName As String, ... 'Returns all mapped drives ... Dim lngRet As Long ... Dim strDrives As String * 255 ...
    (microsoft.public.access.forms)
  • Re: running external Dos commands or programs
    ... System.IO.Directory.InternalGetFileDirectoryNames(String fullPath, String ... However if I manually map to Z: ... It seems to be that the application can read local drives but not network ... > public int Scope; ...
    (microsoft.public.dotnet.vjsharp)