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

前端 未结 3 1274
长情又很酷
长情又很酷 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:07

    This is a quick and dirty translation of this sample code to remove a drive, from support.microsoft.com. It does however work only for users with admin permissions on my system.

    For more information on working with USB devices in general follow the link in this answer by concept03.

    function OpenVolume(ADrive: char): THandle;
    var
      RootName, VolumeName: string;
      AccessFlags: DWORD;
    begin
      RootName := ADrive + ':\'; (* '\'' // keep SO syntax highlighting working *)
      case GetDriveType(PChar(RootName)) of
        DRIVE_REMOVABLE:
          AccessFlags := GENERIC_READ or GENERIC_WRITE;
        DRIVE_CDROM:
          AccessFlags := GENERIC_READ;
      else
        Result := INVALID_HANDLE_VALUE;
        exit;
      end;
      VolumeName := Format('\\.\%s:', [ADrive]);
      Result := CreateFile(PChar(VolumeName), AccessFlags,
        FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
      if Result = INVALID_HANDLE_VALUE then
        RaiseLastWin32Error;
    end;
    
    function LockVolume(AVolumeHandle: THandle): boolean;
    const
      LOCK_TIMEOUT = 10 * 1000; // 10 Seconds
      LOCK_RETRIES = 20;
      LOCK_SLEEP = LOCK_TIMEOUT div LOCK_RETRIES;
    
    // #define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
      FSCTL_LOCK_VOLUME = (9 shl 16) or (0 shl 14) or (6 shl 2) or 0;
    var
      Retries: integer;
      BytesReturned: Cardinal;
    begin
      for Retries := 1 to LOCK_RETRIES do begin
        Result := DeviceIoControl(AVolumeHandle, FSCTL_LOCK_VOLUME, nil, 0,
          nil, 0, BytesReturned, nil);
        if Result then
          break;
        Sleep(LOCK_SLEEP);
      end;
    end;
    
    function DismountVolume(AVolumeHandle: THandle): boolean;
    const
    // #define FSCTL_DISMOUNT_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
      FSCTL_DISMOUNT_VOLUME = (9 shl 16) or (0 shl 14) or (8 shl 2) or 0;
    var
      BytesReturned: Cardinal;
    begin
      Result := DeviceIoControl(AVolumeHandle, FSCTL_DISMOUNT_VOLUME, nil, 0,
        nil, 0, BytesReturned, nil);
      if not Result then
        RaiseLastWin32Error;
    end;
    
    function PreventRemovalOfVolume(AVolumeHandle: THandle;
      APreventRemoval: boolean): boolean;
    const
    // #define IOCTL_STORAGE_MEDIA_REMOVAL CTL_CODE(IOCTL_STORAGE_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS)
      IOCTL_STORAGE_MEDIA_REMOVAL = ($2d shl 16) or (1 shl 14) or ($201 shl 2) or 0;
    type
      TPreventMediaRemoval = record
        PreventMediaRemoval: BOOL;
      end;
    var
      BytesReturned: Cardinal;
      PMRBuffer: TPreventMediaRemoval;
    begin
      PMRBuffer.PreventMediaRemoval := APreventRemoval;
      Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_MEDIA_REMOVAL,
        @PMRBuffer, SizeOf(TPreventMediaRemoval), nil, 0, BytesReturned, nil);
      if not Result then
        RaiseLastWin32Error;
    end;
    
    function AutoEjectVolume(AVolumeHandle: THandle): boolean;
    const
    // #define IOCTL_STORAGE_EJECT_MEDIA CTL_CODE(IOCTL_STORAGE_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS)
      IOCTL_STORAGE_EJECT_MEDIA = ($2d shl 16) or (1 shl 14) or ($202 shl 2) or 0;
    var
      BytesReturned: Cardinal;
    begin
      Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_EJECT_MEDIA, nil, 0,
        nil, 0, BytesReturned, nil);
      if not Result then
        RaiseLastWin32Error;
    end;
    
    function EjectVolume(ADrive: char): boolean;
    var
      VolumeHandle: THandle;
    begin
      Result := FALSE;
      // Open the volume
      VolumeHandle := OpenVolume(ADrive);
      if VolumeHandle = INVALID_HANDLE_VALUE then
        exit;
      try
        // Lock and dismount the volume
        if LockVolume(VolumeHandle) and DismountVolume(VolumeHandle) then begin
          // Set prevent removal to false and eject the volume
          if PreventRemovalOfVolume(VolumeHandle, FALSE) then
            AutoEjectVolume(VolumeHandle);
        end;
      finally
        // Close the volume so other processes can use the drive
        CloseHandle(VolumeHandle);
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      EjectVolume('E');
    end;
    
    0 讨论(0)
  • 2020-12-28 10:10

    This doesn't eject the drive, but it flushes the drive buffers and makes it safe to remove. It requires administrative rights under Vista and higher (and XP if running as a limited rights user, IIRC). It probably should have a try..finally to make sure that CloseHandle gets called; I leave that as an exercise to the reader, since code formattig is tight here without horizontal scrolling. :-)

    unit USBDriveFlush;
    
    interface
    
      uses Windows;
    
    type
      // Taken from JEDI JwaWinIoctl
      PSTORAGE_HOTPLUG_INFO = ^STORAGE_HOTPLUG_INFO;
      {$EXTERNALSYM PSTORAGE_HOTPLUG_INFO}
      _STORAGE_HOTPLUG_INFO = record
        Size: DWORD; // version
        MediaRemovable: BOOLEAN; // ie. zip, jaz, cdrom, mo, etc. vs hdd
        MediaHotplug: BOOLEAN;   // ie. does the device succeed a lock 
                                 // even though its not lockable media?
        DeviceHotplug: BOOLEAN;  // ie. 1394, USB, etc.
        WriteCacheEnableOverride: BOOLEAN; // This field should not be 
                                 // relied upon because it is no longer used
      end;
      {$EXTERNALSYM _STORAGE_HOTPLUG_INFO}
      STORAGE_HOTPLUG_INFO = _STORAGE_HOTPLUG_INFO;
      {$EXTERNALSYM STORAGE_HOTPLUG_INFO}
      TStorageHotplugInfo = STORAGE_HOTPLUG_INFO;
      PStorageHotplugInfo = PSTORAGE_HOTPLUG_INFO;    
    
      function FlushUSBDrive(const Drive: string): Boolean;
    
    implementation
    
    function FlushUSBDrive(const Drive: string): Boolean;
    var
      shpi : TStorageHotplugInfo;
      retlen : DWORD; //unneeded, but deviceiocontrol expects it
      h : THandle;
    begin
      Result := False;
      h := CreateFile(PChar('\\.\' + Drive),
                      0,
                      FILE_SHARE_READ or FILE_SHARE_WRITE,
                      nil,
                      OPEN_EXISTING,
                      0,
                      0);
      if h <> INVALID_HANDLE_VALUE then
      begin
        shpi.Size := SizeOf(shpi);
    
        if DeviceIoControl(h,
                           IOCTL_STORAGE_GET_HOTPLUG_INFO,
                           nil,
                           0,
                           @shpi,
                           SizeOf(shpi),
                           retlen,
                           nil) then
        begin
          //shpi now has the existing values, so you can check to
          //see if the device is already hot-pluggable
          if not shpi.DeviceHotplug then
          begin
            shpi.DeviceHotplug:= True;
    
            //Need to use correct administrator security privilages here
            //otherwise it'll just give 'access is denied' error
            Result := DeviceIoControl(h,
                                      IOCTL_STORAGE_SET_HOTPLUG_INFO,
                                       @shpi,
                                       SizeOf(shpi),
                                       nil,
                                       0,
                                       retlen,
                                       nil);
          end;
        end;
        CloseHandle(h);
      end;
    end;
    

    Sample use:

    if FlushUSBDrive('G:') then
      ShowMessage('Safe to remove USB drive G:')
    else
      ShowMessage('Flush of drive G: failed!' +
        SysErrorMessage(GetLastError()));
    
    0 讨论(0)
  • 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.
    
    0 讨论(0)
提交回复
热议问题