How can i remove a USB flash disk programmatically using delphi?

前端 未结 3 1282
长情又很酷
长情又很酷 2020-12-28 09:45

How can I detect and remove a USB flash disk programatically using delphi?

I have seen some of the examples in this website, but they lack clear explanation on how t

3条回答
  •  夕颜
    夕颜 (楼主)
    2020-12-28 10:15

    The key for remove a USB drive is use the CM_Request_Device_Eject function,

    Check this sample delphi app, based on this article How to Prepare a USB Drive for Safe Removal and which uses the JEDI API Library & Security Code Library

    {$APPTYPE CONSOLE}
    
    {$R *.res}
    
    uses
      JwaWinIoctl,
      Cfg,
      CfgMgr32,
      SetupApi,
      Windows,
      SysUtils;
    
    
    function GetDrivesDevInstByDeviceNumber(DeviceNumber : LONG; DriveType : UINT; szDosDeviceName: PChar) : DEVINST;
    var
     StorageGUID : TGUID;
     IsFloppy : Boolean;
     hDevInfo : SetupApi.HDEVINFO;
     dwIndex  : DWORD;
     res      : BOOL;
     pspdidd  : PSPDeviceInterfaceDetailData;
     spdid    : SP_DEVICE_INTERFACE_DATA;
     spdd     : SP_DEVINFO_DATA;
     dwSize   : DWORD;
     hDrive   : THandle;
     sdn      : STORAGE_DEVICE_NUMBER;
     dwBytesReturned : DWORD;
    begin
      Result:=0;
        IsFloppy := pos('\\Floppy', szDosDeviceName)>0; // who knows a better way?
        case DriveType of
        DRIVE_REMOVABLE:
                    if ( IsFloppy ) then
                      StorageGUID := GUID_DEVINTERFACE_FLOPPY
                    else
                      StorageGUID := GUID_DEVINTERFACE_DISK;
    
        DRIVE_FIXED:  StorageGUID := GUID_DEVINTERFACE_DISK;
        DRIVE_CDROM:    StorageGUID := GUID_DEVINTERFACE_CDROM;
        else
            exit
      end;
    
        // Get device interface info set handle for all devices attached to system
        hDevInfo := SetupDiGetClassDevs(@StorageGUID, nil, 0, DIGCF_PRESENT OR DIGCF_DEVICEINTERFACE);
      if (NativeUInt(hDevInfo) <> INVALID_HANDLE_VALUE) then
      try
        // Retrieve a context structure for a device interface of a device information set
        dwIndex := 0;
        //PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
        spdid.cbSize := SizeOf(spdid);
    
        while true do
        begin
          res := SetupDiEnumDeviceInterfaces(hDevInfo, nil, StorageGUID, dwIndex, spdid);
          if not res then
            break;
    
          dwSize := 0;
          SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, nil, 0, dwSize, nil); // check the buffer size
    
          if ( dwSize<>0) then
          begin
           pspdidd := AllocMem(dwSize);
           try
            pspdidd.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
            ZeroMemory(@spdd, sizeof(spdd));
            spdd.cbSize := SizeOf(spdd);
            res := SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, pspdidd, dwSize, dwSize, @spdd);
            if res then
            begin
              // open the disk or cdrom or floppy
              hDrive := CreateFile(pspdidd.DevicePath, 0, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
             if ( hDrive <> INVALID_HANDLE_VALUE ) then
              try
                  // get its device number
                  dwBytesReturned := 0;
                  res := DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, sizeof(sdn), dwBytesReturned, nil);
                  if res  then
                  begin
                    if ( DeviceNumber = sdn.DeviceNumber) then
                    begin  // match the given device number with the one of the current device
                      Result:= spdd.DevInst;
                      exit;
                    end;
                  end;
              finally
                CloseHandle(hDrive);
              end;
            end;
           finally
             FreeMem(pspdidd);
           end;
          end;
          Inc(dwIndex);
        end;
      finally
       SetupDiDestroyDeviceInfoList(hDevInfo);
      end;
    end;
    
    procedure EjectUSB(const DriveLetter:char);
    var
      szRootPath, szDevicePath : PChar;
      szVolumeAccessPath : PChar;
      hVolume : THandle;
      DeviceNumber : LONG;
      sdn  : STORAGE_DEVICE_NUMBER;
      dwBytesReturned : DWORD;
      res : BOOL;
      resCM : Cardinal;
      DriveType : UINT;
      szDosDeviceName : array [0..MAX_PATH-1] of Char;
      DevInst  : CfgMgr32.DEVINST;
      VetoType : PNP_VETO_TYPE;
      VetoName : array [0..MAX_PATH-1] of WCHAR;
      bSuccess : Boolean;
      DevInstParent : CfgMgr32.DEVINST;
      tries :  Integer;
    begin
        szRootPath := PChar(DriveLetter+':\');
        szDevicePath := PChar(DriveLetter+':');
      szVolumeAccessPath := PChar(Format('\\.\%s:',[DriveLetter]));
    
      DeviceNumber:=-1;
        // open the storage volume
      hVolume := CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ OR FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
        if (hVolume <> INVALID_HANDLE_VALUE) then
       try
        //get the volume's device number
        dwBytesReturned := 0;
        res := DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, SizeOf(sdn), dwBytesReturned, nil);
        if res then
          DeviceNumber := sdn.DeviceNumber;
       finally
         CloseHandle(hVolume);
       end;
       if DeviceNumber=-1 then exit;
    
        // get the drive type which is required to match the device numbers correctely
        DriveType := GetDriveType(szRootPath);
    
        // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
        QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH);
    
        // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
        DevInst := GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName);
    
        if ( DevInst = 0 ) then
       exit;
    
      VetoType := PNP_VetoTypeUnknown;
        bSuccess := false;
    
        // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
        DevInstParent := 0;
        resCM := CM_Get_Parent(DevInstParent, DevInst, 0);
    
        for tries:=0 to 3 do // sometimes we need some tries...
      begin
            FillChar(VetoName[0], SizeOf(VetoName), 0);
    
            // CM_Query_And_Remove_SubTree doesn't work for restricted users
            //resCM = CM_Query_And_Remove_SubTree(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
            //resCM = CM_Query_And_Remove_SubTree(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART);  // with messagebox (W2K, Vista) or balloon (XP)
    
            resCM := CM_Request_Device_Eject(DevInstParent, @VetoType, @VetoName[0], Length(VetoName), 0);
            resCM := CM_Request_Device_Eject(DevInstParent,nil, nil, 0, 0); // optional -> shows messagebox (W2K, Vista) or balloon (XP)
    
            bSuccess := (resCM=CR_SUCCESS) and (VetoType=PNP_VetoTypeUnknown);
            if ( bSuccess )  then
                break;
    
            Sleep(500); // required to give the next tries a chance!
        end;
    
        if ( bSuccess ) then
            Writeln('Success')
      else
          Writeln('Failed');
    end;
    
    begin
      try
        LoadSetupApi;
        LoadConfigManagerApi;
        EjectUSB('F');
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      Readln;
    
    end.
    

提交回复
热议问题