Recent

Author Topic: Eject an usb drive in windows  (Read 14069 times)

Ericktux

  • Sr. Member
  • ****
  • Posts: 345
Eject an usb drive in windows
« on: September 26, 2016, 05:01:55 pm »
Good day my friends  :), one question how i can eject a usb stick and hard drive usb.

i use lazarus 1.6 and win 8.1.
regards.

Fungus

  • Sr. Member
  • ****
  • Posts: 353
Re: Eject an usb drive in windows
« Reply #1 on: September 26, 2016, 05:59:26 pm »
Here's the C version: https://support.microsoft.com/en-us/kb/165721 - just convert it to pascal :D

rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: Eject an usb drive in windows
« Reply #2 on: September 26, 2016, 06:14:21 pm »
O, but we can do better than that :D

Here are some Delphi versions. Just convert them to FPC/Lazarus :)
http://stackoverflow.com/questions/434688/how-can-i-remove-a-usb-flash-disk-programmatically-using-delphi

Ericktux

  • Sr. Member
  • ****
  • Posts: 345
Re: Eject an usb drive in windows
« Reply #3 on: September 26, 2016, 07:02:00 pm »
thanks my friends.
I'll try.

Ericktux

  • Sr. Member
  • ****
  • Posts: 345
Re: Eject an usb drive in windows
« Reply #4 on: September 26, 2016, 08:35:14 pm »
Hi , I tried use the code shared, in principle works for USB flash drives  :D, but not for hard disk usb.  :( :(
as I can solve.  :( :(

this is my code:

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   windows, Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Edit1: TEdit;
  17.     procedure Button1Click(Sender: TObject);
  18.   private
  19.     { private declarations }
  20.   public
  21.     { public declarations }
  22.   end;
  23.  
  24. var
  25.   Form1: TForm1;
  26.  
  27. implementation
  28.  
  29. {$R *.lfm}
  30.  
  31. { TForm1 }
  32.  
  33. function OpenVolume(ADrive: char): THandle;
  34. var
  35.   RootName, VolumeName: string;
  36.   AccessFlags: DWORD;
  37. begin
  38.   RootName := ADrive + ':\'; (* '\'' // keep SO syntax highlighting working *)
  39.   case GetDriveType(PChar(RootName)) of
  40.     DRIVE_REMOVABLE:
  41.       AccessFlags := GENERIC_READ or GENERIC_WRITE;
  42.     DRIVE_CDROM:
  43.       AccessFlags := GENERIC_READ;
  44.   else
  45.     Result := INVALID_HANDLE_VALUE;
  46.     exit;
  47.   end;
  48.   VolumeName := Format('\\.\%s:', [ADrive]);
  49.   Result := CreateFile(PChar(VolumeName), AccessFlags,
  50.     FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
  51.   if Result = INVALID_HANDLE_VALUE then
  52.     RaiseLastWin32Error;
  53. end;
  54.  
  55. function LockVolume(AVolumeHandle: THandle): boolean;
  56. const
  57.   LOCK_TIMEOUT = 10 * 1000; // 10 Seconds
  58.   LOCK_RETRIES = 20;
  59.   LOCK_SLEEP = LOCK_TIMEOUT div LOCK_RETRIES;
  60.  
  61. // #define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
  62.   FSCTL_LOCK_VOLUME = (9 shl 16) or (0 shl 14) or (6 shl 2) or 0;
  63. var
  64.   Retries: integer;
  65.   BytesReturned: Cardinal;
  66. begin
  67.   for Retries := 1 to LOCK_RETRIES do begin
  68.     Result := DeviceIoControl(AVolumeHandle, FSCTL_LOCK_VOLUME, nil, 0,
  69.       nil, 0, BytesReturned, nil);
  70.     if Result then
  71.       break;
  72.     Sleep(LOCK_SLEEP);
  73.   end;
  74. end;
  75.  
  76. function DismountVolume(AVolumeHandle: THandle): boolean;
  77. const
  78. // #define FSCTL_DISMOUNT_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
  79.   FSCTL_DISMOUNT_VOLUME = (9 shl 16) or (0 shl 14) or (8 shl 2) or 0;
  80. var
  81.   BytesReturned: Cardinal;
  82. begin
  83.   Result := DeviceIoControl(AVolumeHandle, FSCTL_DISMOUNT_VOLUME, nil, 0,
  84.     nil, 0, BytesReturned, nil);
  85.   if not Result then
  86.     RaiseLastWin32Error;
  87. end;
  88.  
  89. function PreventRemovalOfVolume(AVolumeHandle: THandle;
  90.   APreventRemoval: boolean): boolean;
  91. const
  92. // #define IOCTL_STORAGE_MEDIA_REMOVAL CTL_CODE(IOCTL_STORAGE_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS)
  93.   IOCTL_STORAGE_MEDIA_REMOVAL = ($2d shl 16) or (1 shl 14) or ($201 shl 2) or 0;
  94. type
  95.   TPreventMediaRemoval = record
  96.     PreventMediaRemoval: BOOL;
  97.   end;
  98. var
  99.   BytesReturned: Cardinal;
  100.   PMRBuffer: TPreventMediaRemoval;
  101. begin
  102.   PMRBuffer.PreventMediaRemoval := APreventRemoval;
  103.   Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_MEDIA_REMOVAL,
  104.     @PMRBuffer, SizeOf(TPreventMediaRemoval), nil, 0, BytesReturned, nil);
  105.   if not Result then
  106.     RaiseLastWin32Error;
  107. end;
  108.  
  109. function AutoEjectVolume(AVolumeHandle: THandle): boolean;
  110. const
  111. // #define IOCTL_STORAGE_EJECT_MEDIA CTL_CODE(IOCTL_STORAGE_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS)
  112.   IOCTL_STORAGE_EJECT_MEDIA = ($2d shl 16) or (1 shl 14) or ($202 shl 2) or 0;
  113. var
  114.   BytesReturned: Cardinal;
  115. begin
  116.   Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_EJECT_MEDIA, nil, 0,
  117.     nil, 0, BytesReturned, nil);
  118.   if not Result then
  119.     RaiseLastWin32Error;
  120. end;
  121.  
  122. function EjectVolume(ADrive: char): boolean;
  123. var
  124.   VolumeHandle: THandle;
  125. begin
  126.   Result := FALSE;
  127.   // Open the volume
  128.   VolumeHandle := OpenVolume(ADrive);
  129.   if VolumeHandle = INVALID_HANDLE_VALUE then
  130.     exit;
  131.   try
  132.     // Lock and dismount the volume
  133.     if LockVolume(VolumeHandle) and DismountVolume(VolumeHandle) then begin
  134.       // Set prevent removal to false and eject the volume
  135.       if PreventRemovalOfVolume(VolumeHandle, FALSE) then
  136.         AutoEjectVolume(VolumeHandle);
  137.     end;
  138.   finally
  139.     // Close the volume so other processes can use the drive
  140.     CloseHandle(VolumeHandle);
  141.   end;
  142. end;
  143.  
  144. procedure TForm1.Button1Click(Sender: TObject);
  145. var
  146.   s: string;
  147. begin
  148.   s:=Edit1.Text;
  149.   if EjectVolume(s[1]) then ShowMessage('ejected successful');
  150. end;
  151.  
  152. procedure TForm1.FormCreate(Sender: TObject);
  153. begin
  154.   ActiveControl :=Button1;
  155. end;
  156.  
  157. end.


rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: Eject an usb drive in windows
« Reply #5 on: September 26, 2016, 08:51:38 pm »
There are two things wrong with the code as far as I can see.
(and yes, the code on SO has these problems too)

You don't check for DRIVE_FIXED in OpenVolume() (and for me a USB Harddrive gives GetDriveType() = 3 which is DRIVE_FIXED). So:

Code: Pascal  [Select][+][-]
  1.   case GetDriveType(PChar(RootName)) of
  2.     DRIVE_REMOVABLE: AccessFlags := GENERIC_READ or GENERIC_WRITE;
  3.     DRIVE_FIXED: AccessFlags := GENERIC_READ or GENERIC_WRITE;
  4.     DRIVE_CDROM: AccessFlags := GENERIC_READ;
  5.   else
  6.     Result := INVALID_HANDLE_VALUE;
  7.     exit;
  8.   end;
Second... you don't fill in the Result in EjectVolume(). You should do this:
Code: Pascal  [Select][+][-]
  1.       if PreventRemovalOfVolume(VolumeHandle, FALSE) then
  2.         Result := AutoEjectVolume(VolumeHandle);
After that I do get a message that the drive is ejected successful but when I look at the "Safely remove hardware" icon, it is still there. So I'm not sure what's going wrong there.
« Last Edit: September 26, 2016, 08:54:14 pm by rvk »

Ericktux

  • Sr. Member
  • ****
  • Posts: 345
Re: Eject an usb drive in windows
« Reply #6 on: September 26, 2016, 09:19:02 pm »
thanks rvk, just i try it again, but just not work for hard disk usb  :( ,is true This rare the code.  %)

rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: Eject an usb drive in windows
« Reply #7 on: September 27, 2016, 01:28:08 pm »
There are basically two method for ejecting/dismounting an USB-device.
One is IOCTL_STORAGE_EJECT_MEDIA
The other is the CM_Request_Device_Eject function.

You can see that the first answer in the SO-question is for IOCTL_STORAGE_EJECT_MEDIA (which you translated).
The second answer is CM_Request_Device_Eject (but isn't as easily translated). You could try if that one does remove the drive from the explorer.

I do think your code (with my correction) works correctly. I get a "ejected successful" so it does work. But with a USB-drive you apparently also need to remove the volume from the explorer. (I think there is a function for that but haven't found it yet)

I also noticed that when you right click a USB-stick in the explorer there is an Eject function. There is no such Eject function when a USB-Drive is attached. So maybe that's also the difference in IOCTL_STORAGE_EJECT_MEDIA/CM_Request_Device_Eject function (as in IOCTL_STORAGE_EJECT_MEDIA emulated the Eject from explorer which isn't present for a USB-Drive).

The problem also might be due to the fact a DRIVE_REMOVABLE can only have one partition on it while a DRIVE_FIXED can have multiple. So calling this for "only" F: wouldn't really eject it (because it can also have other partitions). In that case you would need to call these function by "device-number" and not by "drive-letter".

Further investigation is needed...
« Last Edit: September 27, 2016, 01:31:28 pm by rvk »

Thaddy

  • Hero Member
  • *****
  • Posts: 14367
  • Sensorship about opinions does not belong here.
Object Pascal programmers should get rid of their "component fetish" especially with the non-visuals.

rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: Eject an usb drive in windows
« Reply #9 on: September 27, 2016, 03:04:04 pm »
Did you checkout thison SO?
http://stackoverflow.com/questions/434688/how-can-i-remove-a-usb-flash-disk-programmatically-using-delphi
You didn't read my post ??

O, but we can do better than that :D

Here are some Delphi versions. Just convert them to FPC/Lazarus :)
http://stackoverflow.com/questions/434688/how-can-i-remove-a-usb-flash-disk-programmatically-using-delphi

Either way... the IOCTL_STORAGE_EJECT_MEDIA-method used in the first answer doesn't remove the USB-harddisk from the explorer (only USB-sticks) and the second answer isn't easily translated to Lazarus/FPC.
« Last Edit: September 27, 2016, 03:14:38 pm by rvk »

ASerge

  • Hero Member
  • *****
  • Posts: 2241
Re: Eject an usb drive in windows
« Reply #10 on: September 27, 2016, 04:03:09 pm »
But with a USB-drive you apparently also need to remove the volume from the explorer. (I think there is a function for that but haven't found it yet)
Like this?
Code: Pascal  [Select][+][-]
  1. uses ShlObj;
  2. ...
  3. SHChangeNotify(SHCNE_DRIVEREMOVED, SHCNF_PATHA, PAnsiChar('Z:\'), nil);
  4.  

rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: Eject an usb drive in windows
« Reply #11 on: September 27, 2016, 04:33:34 pm »
Yeah, something like that (but not exactly).

The USB-harddisk is still accessible via driveletter even after removing it with SHCNE_DRIVEREMOVED from explorer.exe. So it's not really dismounted. I think the problem is that a real-harddisk needs to be removed in another way like with the CM_Request_Device_Eject method. All mounted volumes from the disk need to be iterated and dismounted before actually "ejecting" the drive. (But the CM_Request_Device_Eject method isn't that easy to post but it can be done)

Ericktux

  • Sr. Member
  • ****
  • Posts: 345
Re: Eject an usb drive in windows
« Reply #12 on: September 27, 2016, 07:57:58 pm »
I found similar code, but same result:    :( :(

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   windows, Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, ShlObj;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     Edit1: TEdit;
  17.     procedure Button1Click(Sender: TObject);
  18.   private
  19.     { private declarations }
  20.   public
  21.     { public declarations }
  22.   end;
  23.  
  24. var
  25.   Form1: TForm1;
  26.  
  27. implementation
  28.  
  29. {$R *.lfm}
  30.  
  31. { TForm1 }
  32.  
  33. function OpenVolume(ADrive: char): THandle;
  34. var
  35.   RootName, VolumeName: string;
  36.   AccessFlags: DWORD;
  37. begin
  38.   RootName := ADrive + ':\'; // ADrive + ':\' kills the syntax highlighting
  39.  
  40.   case GetDriveType(PChar(RootName)) of
  41.     DRIVE_REMOVABLE:
  42.       AccessFlags := GENERIC_READ or GENERIC_WRITE;
  43.     DRIVE_FIXED:       //Añado el tipo FIXED Para q actue sobre discos USB
  44.       AccessFlags := GENERIC_READ or GENERIC_WRITE;
  45.     DRIVE_CDROM:
  46.       AccessFlags := GENERIC_READ;
  47.   else
  48.     Result := INVALID_HANDLE_VALUE;
  49.     exit;
  50.   end;
  51.  
  52.   VolumeName := Format('\\.\%s:', [ADrive]);
  53.   Result := CreateFile(PChar(VolumeName), AccessFlags, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
  54.  
  55.   if Result = INVALID_HANDLE_VALUE then RaiseLastOSError;
  56. end;
  57.   //---------------------------//
  58.  
  59. function LockVolume(AVolumeHandle: THandle): boolean;
  60. const
  61.   LOCK_TIMEOUT = 10 * 1000; // 10 Seconds
  62.   LOCK_RETRIES = 20;
  63.   LOCK_SLEEP = LOCK_TIMEOUT div LOCK_RETRIES;
  64.  
  65. // #define FSCTL_LOCK_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
  66.   FSCTL_LOCK_VOLUME = (9 shl 16) or (0 shl 14) or (6 shl 2) or 0;
  67. var
  68.   Retries: integer;
  69.   BytesReturned: Cardinal;
  70. begin
  71.   for Retries := 1 to LOCK_RETRIES do
  72.   begin
  73.     Result := DeviceIoControl(AVolumeHandle, FSCTL_LOCK_VOLUME, nil, 0, nil, 0, BytesReturned, nil);
  74.  
  75.     if Result then break;
  76.  
  77.     Sleep(LOCK_SLEEP);
  78.   end;
  79. end;
  80.   //---------------------------//
  81.  
  82. function DismountVolume(AVolumeHandle: THandle): boolean;
  83. const
  84. // #define FSCTL_DISMOUNT_VOLUME CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 8, METHOD_BUFFERED, FILE_ANY_ACCESS)
  85.   FSCTL_DISMOUNT_VOLUME = (9 shl 16) or (0 shl 14) or (8 shl 2) or 0;
  86. var
  87.   BytesReturned: Cardinal;
  88. begin
  89.   Result := DeviceIoControl(AVolumeHandle, FSCTL_DISMOUNT_VOLUME, nil, 0, nil, 0, BytesReturned, nil);
  90.  
  91.   if not Result then RaiseLastOSError;
  92. end;
  93.   //---------------------------//
  94.  
  95. function PreventRemovalOfVolume(AVolumeHandle: THandle; APreventRemoval: boolean): boolean;
  96. const
  97. // #define IOCTL_STORAGE_MEDIA_REMOVAL CTL_CODE(IOCTL_STORAGE_BASE, 0x0201, METHOD_BUFFERED, FILE_READ_ACCESS)
  98.   IOCTL_STORAGE_MEDIA_REMOVAL = ($2d shl 16) or (1 shl 14) or ($201 shl 2) or 0;
  99. type
  100.   TPreventMediaRemoval = record
  101.     PreventMediaRemoval: BOOL;
  102.   end;
  103. var
  104.   BytesReturned: Cardinal;
  105.   PMRBuffer: TPreventMediaRemoval;
  106. begin
  107.   PMRBuffer.PreventMediaRemoval := APreventRemoval;
  108.   Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_MEDIA_REMOVAL,
  109.     @PMRBuffer, SizeOf(TPreventMediaRemoval), nil, 0, BytesReturned, nil);
  110.   if not Result then
  111.     RaiseLastOSError;
  112. end;
  113.   //---------------------------//
  114.  
  115. function AutoEjectVolume(AVolumeHandle: THandle): boolean;
  116. const
  117. // #define IOCTL_STORAGE_EJECT_MEDIA CTL_CODE(IOCTL_STORAGE_BASE, 0x0202, METHOD_BUFFERED, FILE_READ_ACCESS)
  118. //  IOCTL_STORAGE_EJECT_MEDIA = ($2d shl 16) or (1 shl 14) or ($202 shl 2) or 0;
  119.   IOCTL_STORAGE_EJECT_MEDIA: Cardinal      = $002d4808;
  120.   IOCTL_STORAGE_EJECTION_CONTROL: Cardinal = $002d0940;
  121. var
  122.   BytesReturned: Cardinal;
  123. begin
  124.   Result := DeviceIoControl(AVolumeHandle, IOCTL_STORAGE_EJECT_MEDIA, nil, 0, nil, 0, BytesReturned, nil);
  125.  
  126.   if not Result then RaiseLastOSError;
  127. end;
  128.   //---------------------------//
  129.  
  130. function EjectVolume(ADrive: char): boolean;
  131. var
  132.   VolumeHandle: THandle;
  133. begin
  134.   Result := FALSE;
  135.   // Open the volume
  136.   VolumeHandle := OpenVolume(ADrive);
  137.   if VolumeHandle = INVALID_HANDLE_VALUE then  exit;
  138.  
  139.   try
  140.     // Lock and dismount the volume
  141.     if LockVolume(VolumeHandle) and DismountVolume(VolumeHandle) then
  142.     begin
  143.       // Set prevent removal to false and eject the volume
  144.       if PreventRemovalOfVolume(VolumeHandle, FALSE) then Result := AutoEjectVolume(VolumeHandle);
  145.     end;
  146.   finally
  147.     // Close the volume so other processes can use the drive
  148.     CloseHandle(VolumeHandle);
  149.   end;
  150. end;
  151.  
  152. procedure TForm1.Button1Click(Sender: TObject);
  153. var
  154.   s: string;
  155. begin
  156.   s:=Edit1.Text;
  157.   if EjectVolume(s[1]) then ShowMessage('ejected successful');
  158. end;
  159.  
  160. end.


rvk

  • Hero Member
  • *****
  • Posts: 6163
Re: Eject an usb drive in windows
« Reply #13 on: September 28, 2016, 11:20:54 am »
Pffffffffffffffffffffffff  %) I took the liberty to translate the CM_Request_Device_Eject method for you to FPC. I encapsulated it all in one function EjectUSB(DriveLetter). Here is a console-example which ejects the USB-drive to which F belongs to.

In Lazarus you can choose Project > New Project > Simple Program and completely copy/paste this and change the driveletter at the end. It worked for me for a WD USB-harddrive.

Code: Pascal  [Select][+][-]
  1. program Eject_USB;
  2.  
  3. {$APPTYPE CONSOLE}
  4.  
  5. uses
  6.   Windows,
  7.   SysUtils;
  8.  
  9. type
  10.   DEVICE_TYPE = DWORD;
  11.   _DEVINST = DWORD;
  12.   HDEVINFO = Pointer;
  13.   PPNP_VETO_TYPE = ^PNP_VETO_TYPE;
  14.   PNP_VETO_TYPE = DWORD;
  15.   RETURN_TYPE = DWORD;
  16.   CONFIGRET = RETURN_TYPE;
  17.  
  18. const
  19.   PNP_VetoTypeUnknown = 0;   // Name is unspecified
  20.  
  21. type
  22.   PSPDeviceInterfaceDetailDataA = ^TSPDeviceInterfaceDetailDataA;
  23.   SP_DEVICE_INTERFACE_DETAIL_DATA_A = packed record
  24.     cbSize: DWORD;
  25.     DevicePath: array [0..ANYSIZE_ARRAY - 1] of AnsiChar;
  26.   end;
  27.   TSPDeviceInterfaceDetailDataA = SP_DEVICE_INTERFACE_DETAIL_DATA_A;
  28.   TSPDeviceInterfaceDetailData = TSPDeviceInterfaceDetailDataA;
  29.   PSPDeviceInterfaceDetailData = PSPDeviceInterfaceDetailDataA;
  30.  
  31.   PSPDeviceInterfaceData = ^TSPDeviceInterfaceData;
  32.   SP_DEVICE_INTERFACE_DATA = packed record
  33.     cbSize: DWORD;
  34.     InterfaceClassGuid: TGUID;
  35.     Flags: DWORD;
  36.     Reserved: ULONG_PTR;
  37.   end;
  38.   TSPDeviceInterfaceData = SP_DEVICE_INTERFACE_DATA;
  39.  
  40.   PSPDevInfoData = ^TSPDevInfoData;
  41.   SP_DEVINFO_DATA = packed record
  42.     cbSize: DWORD;
  43.     ClassGuid: TGUID;
  44.     DevInst: DWORD; // DEVINST handle
  45.     Reserved: ULONG_PTR;
  46.   end;
  47.   TSPDevInfoData = SP_DEVINFO_DATA;
  48.  
  49.   _STORAGE_DEVICE_NUMBER = record
  50.     DeviceType: DEVICE_TYPE;
  51.     DeviceNumber: DWORD;
  52.     PartitionNumber: DWORD;
  53.   end;
  54.   STORAGE_DEVICE_NUMBER = _STORAGE_DEVICE_NUMBER;
  55.  
  56. const
  57.   CR_SUCCESS = $00000000;
  58.   DIGCF_PRESENT = $00000002;
  59.   DIGCF_DEVICEINTERFACE = $00000010;
  60.   FILE_DEVICE_MASS_STORAGE = $0000002d;
  61.   FILE_ANY_ACCESS = 0;
  62.   METHOD_BUFFERED = 0;
  63.   IOCTL_STORAGE_BASE = FILE_DEVICE_MASS_STORAGE;
  64.   IOCTL_STORAGE_GET_DEVICE_NUMBER =
  65.     (IOCTL_STORAGE_BASE shl 16) or (FILE_ANY_ACCESS shl 14) or
  66.     ($0420 shl 2) or (METHOD_BUFFERED);
  67.  
  68. const
  69.   GUID_DEVINTERFACE_DISK: TGUID = (
  70.     D1: $53f56307; D2: $b6bf; D3: $11d0; D4: ($94, $f2, $00, $a0, $c9, $1e, $fb, $8b));
  71.   GUID_DEVINTERFACE_CDROM: TGUID = (
  72.     D1: $53f56308; D2: $b6bf; D3: $11d0; D4: ($94, $f2, $00, $a0, $c9, $1e, $fb, $8b));
  73.   GUID_DEVINTERFACE_FLOPPY: TGUID = (
  74.     D1: $53f56311; D2: $b6bf; D3: $11d0; D4: ($94, $f2, $00, $a0, $c9, $1e, $fb, $8b));
  75.  
  76. type
  77.   TCM_Get_Parent = function(var dnDevInstParent: _DEVINST; dnDevInst: _DEVINST;
  78.     ulFlags: ULONG): CONFIGRET; stdcall;
  79.   TCM_Request_Device_Eject = function(dnDevInst: _DEVINST;
  80.     pVetoType: PPNP_VETO_TYPE;     // OPTIONAL
  81.     pszVetoName: PTSTR;            // OPTIONAL
  82.     ulNameLength: ULONG; ulFlags: ULONG): CONFIGRET; stdcall;
  83.   TSetupDiGetClassDevs = function(ClassGuid: PGUID; const aEnumerator: PTSTR;
  84.     hwndParent: HWND; Flags: DWORD): HDEVINFO; stdcall;
  85.   TSetupDiEnumDeviceInterfaces = function(DeviceInfoSet: HDEVINFO;
  86.     DeviceInfoData: PSPDevInfoData; const InterfaceClassGuid: TGUID;
  87.     MemberIndex: DWORD; var DeviceInterfaceData: TSPDeviceInterfaceData): BOOL; stdcall;
  88.   TSetupDiGetDeviceInterfaceDetail = function(DeviceInfoSet: HDEVINFO;
  89.     DeviceInterfaceData: PSPDeviceInterfaceData;
  90.     DeviceInterfaceDetailData: PSPDeviceInterfaceDetailData;
  91.     DeviceInterfaceDetailDataSize: DWORD; var RequiredSize: DWORD;
  92.     Device: PSPDevInfoData): BOOL; stdcall;
  93.   TSetupDiDestroyDeviceInfoList = function(DeviceInfoSet: HDEVINFO): BOOL; stdcall;
  94.  
  95.  
  96.  
  97.   function EjectUSB(const DriveLetter: char): boolean;
  98.   const
  99.     CfgMgrDllName = 'cfgmgr32.dll';
  100.     SetupApiModuleName = 'SetupApi.dll';
  101.   var
  102.     CM_Get_Parent: TCM_Get_Parent;
  103.     CM_Request_Device_Eject: TCM_Request_Device_Eject;
  104.     SetupDiGetClassDevs: TSetupDiGetClassDevs;
  105.     SetupDiEnumDeviceInterfaces: TSetupDiEnumDeviceInterfaces;
  106.     SetupDiGetDeviceInterfaceDetail: TSetupDiGetDeviceInterfaceDetail;
  107.     SetupDiDestroyDeviceInfoList: TSetupDiDestroyDeviceInfoList;
  108.   var
  109.     CfgMgrApiLib: HINST;
  110.     SetupApiLib: HINST;
  111.  
  112.     function GetDrivesDevInstByDeviceNumber(DeviceNumber: LONG; DriveType: UINT; szDosDeviceName: PChar): _DEVINST;
  113.     var
  114.       StorageGUID: TGUID;
  115.       IsFloppy: boolean;
  116.       hDevInfo: Pointer; //HDEVINFO;
  117.       dwIndex: DWORD;
  118.       res: BOOL;
  119.       pspdidd: PSPDeviceInterfaceDetailData;
  120.       spdid: SP_DEVICE_INTERFACE_DATA;
  121.       spdd: SP_DEVINFO_DATA;
  122.       dwSize: DWORD;
  123.       hDrive: THandle;
  124.       sdn: STORAGE_DEVICE_NUMBER;
  125.       dwBytesReturned: DWORD;
  126.     begin
  127.       Result := 0;
  128.  
  129.       IsFloppy := pos('\\Floppy', szDosDeviceName) > 0; // who knows a better way?
  130.       case DriveType of
  131.         DRIVE_REMOVABLE:
  132.           if (IsFloppy) then
  133.             StorageGUID := GUID_DEVINTERFACE_FLOPPY
  134.           else
  135.             StorageGUID := GUID_DEVINTERFACE_DISK;
  136.         DRIVE_FIXED: StorageGUID := GUID_DEVINTERFACE_DISK;
  137.         DRIVE_CDROM: StorageGUID := GUID_DEVINTERFACE_CDROM;
  138.         else
  139.           exit
  140.       end;
  141.  
  142.       // Get device interface info set handle for all devices attached to system
  143.       hDevInfo := SetupDiGetClassDevs(@StorageGUID, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
  144.       if (NativeUInt(hDevInfo) <> INVALID_HANDLE_VALUE) then
  145.         try
  146.           // Retrieve a context structure for a device interface of a device information set
  147.           dwIndex := 0;
  148.           //PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
  149.           spdid.cbSize := SizeOf(spdid);
  150.  
  151.           while True do
  152.           begin
  153.             res := SetupDiEnumDeviceInterfaces(hDevInfo, nil, StorageGUID, dwIndex, spdid);
  154.             if not res then break;
  155.  
  156.             dwSize := 0;
  157.             SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, nil, 0, dwSize, nil);
  158.             // check the buffer size
  159.  
  160.             if (dwSize <> 0) then
  161.             begin
  162.               pspdidd := AllocMem(dwSize);
  163.               try
  164.                 pspdidd^.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
  165.                 ZeroMemory(@spdd, sizeof(spdd));
  166.                 spdd.cbSize := SizeOf(spdd);
  167.                 res := SetupDiGetDeviceInterfaceDetail(hDevInfo, @spdid, pspdidd, dwSize, dwSize, @spdd);
  168.                 if res then
  169.                 begin
  170.                   // open the disk or cdrom or floppy
  171.                   hDrive := CreateFile(pspdidd^.DevicePath, 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
  172.                   if (hDrive <> INVALID_HANDLE_VALUE) then
  173.                     try
  174.                       // get its device number
  175.                       dwBytesReturned := 0;
  176.                       res := DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, sizeof(sdn), dwBytesReturned, nil);
  177.                       if res then
  178.                       begin
  179.                         if (DeviceNumber = sdn.DeviceNumber) then
  180.                         begin  // match the given device number with the one of the current device
  181.                           Result := spdd.DevInst;
  182.                           exit;
  183.                         end;
  184.                       end;
  185.                     finally
  186.                       CloseHandle(hDrive);
  187.                     end;
  188.                 end;
  189.               finally
  190.                 FreeMem(pspdidd);
  191.               end;
  192.             end;
  193.             Inc(dwIndex);
  194.           end;
  195.         finally
  196.           SetupDiDestroyDeviceInfoList(hDevInfo);
  197.         end;
  198.     end;
  199.  
  200.     function ReallyEjectUSB(const DriveLetter: char): boolean;
  201.     var
  202.       szRootPath, szDevicePath: string;
  203.       szVolumeAccessPath: string;
  204.       hVolume: THandle;
  205.       DeviceNumber: LONG;
  206.       sdn: STORAGE_DEVICE_NUMBER;
  207.       dwBytesReturned: DWORD;
  208.       res: BOOL;
  209.       resCM: cardinal;
  210.       DriveType: UINT;
  211.       szDosDeviceName: array [0..MAX_PATH - 1] of char;
  212.       DevInst: _DEVINST;
  213.       VetoType: PNP_VETO_TYPE;
  214.       VetoName: array [0..MAX_PATH - 1] of WCHAR;
  215.       DevInstParent: _DEVINST;
  216.       tries: integer;
  217.     begin
  218.       Result := False;
  219.  
  220.       szRootPath := DriveLetter + ':\';
  221.       szDevicePath := DriveLetter + ':';
  222.       szVolumeAccessPath := Format('\\.\%s:', [DriveLetter]);
  223.  
  224.       DeviceNumber := -1;
  225.       // open the storage volume
  226.       hVolume := CreateFile(PChar(szVolumeAccessPath), 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
  227.       if (hVolume <> INVALID_HANDLE_VALUE) then
  228.         try
  229.           //get the volume's device number
  230.           dwBytesReturned := 0;
  231.           res := DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, nil, 0, @sdn, SizeOf(sdn), dwBytesReturned, nil);
  232.           if res then DeviceNumber := sdn.DeviceNumber;
  233.         finally
  234.           CloseHandle(hVolume);
  235.         end;
  236.       if DeviceNumber = -1 then exit;
  237.  
  238.       // get the drive type which is required to match the device numbers correctely
  239.       DriveType := GetDriveType(PChar(szRootPath));
  240.  
  241.       // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
  242.       QueryDosDevice(PChar(szDevicePath), szDosDeviceName, MAX_PATH);
  243.  
  244.       // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
  245.       DevInst := GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName);
  246.  
  247.       if (DevInst = 0) then exit;
  248.  
  249.       VetoType := PNP_VetoTypeUnknown;
  250.  
  251.       // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
  252.       DevInstParent := 0;
  253.       resCM := CM_Get_Parent(DevInstParent, DevInst, 0);
  254.  
  255.       for tries := 0 to 3 do // sometimes we need some tries...
  256.       begin
  257.         FillChar(VetoName[0], SizeOf(VetoName), 0);
  258.  
  259.         // CM_Query_And_Remove_SubTree doesn't work for restricted users
  260.         //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!
  261.         //resCM = CM_Query_And_Remove_SubTree(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART);  // with messagebox (W2K, Vista) or balloon (XP)
  262.  
  263.         resCM := CM_Request_Device_Eject(DevInstParent, @VetoType, @VetoName[0], Length(VetoName), 0);
  264.         resCM := CM_Request_Device_Eject(DevInstParent, nil, nil, 0, 0);
  265.         // optional -> shows messagebox (W2K, Vista) or balloon (XP)
  266.  
  267.         Result := (resCM = CR_SUCCESS) and (VetoType = PNP_VetoTypeUnknown);
  268.         if Result then break;
  269.  
  270.         Sleep(500); // required to give the next tries a chance!
  271.       end;
  272.  
  273.     end;
  274.  
  275.   begin
  276.     Result := False;
  277.     CfgMgrApiLib := LoadLibrary(CfgMgrDllName);
  278.     SetupApiLib := LoadLibrary(SetupApiModuleName);
  279.     try
  280.       if (CfgMgrApiLib <> 0) and (SetupApiLib <> 0) then
  281.       begin
  282.         pointer(CM_Get_Parent) := GetProcAddress(CfgMgrApiLib, 'CM_Get_Parent');
  283.         pointer(CM_Request_Device_Eject) := GetProcAddress(SetupApiLib, 'CM_Request_Device_EjectA');
  284.         pointer(SetupDiGetClassDevs) := GetProcAddress(SetupApiLib, 'SetupDiGetClassDevsA');
  285.         pointer(SetupDiEnumDeviceInterfaces) := GetProcAddress(SetupApiLib, 'SetupDiEnumDeviceInterfaces');
  286.         pointer(SetupDiGetDeviceInterfaceDetail) := GetProcAddress(SetupApiLib, 'SetupDiGetDeviceInterfaceDetailA');
  287.         pointer(SetupDiDestroyDeviceInfoList) := GetProcAddress(SetupApiLib, 'SetupDiDestroyDeviceInfoList');
  288.         Result := ReallyEjectUSB(DriveLetter);
  289.       end;
  290.     finally
  291.       if CfgMgrApiLib <> 0 then FreeLibrary(CfgMgrApiLib);
  292.       if SetupApiLib <> 0 then FreeLibrary(SetupApiLib);
  293.     end;
  294.   end;
  295.  
  296.  
  297. begin
  298.   try
  299.     if EjectUSB('F') then
  300.       Writeln('Success')
  301.     else
  302.       Writeln('Failed');
  303.   except
  304.     on E: Exception do
  305.       Writeln(E.ClassName, ': ', E.Message);
  306.   end;
  307.   Readln;
  308. end.
« Last Edit: September 28, 2016, 11:29:49 am by rvk »

Ericktux

  • Sr. Member
  • ****
  • Posts: 345
Re: Eject an usb drive in windows
« Reply #14 on: September 28, 2016, 10:02:08 pm »
My friend rvk  :) your code work perfect in my usb stick and usb harddisk (samsung)  :D :D
thank you very much  :D :D

 

TinyPortal © 2005-2018