Ключом для извлечения USB-накопителя является функция CM_Request_Device_Eject ,
Проверьте это пример приложения Delphi, основанного на этой статье How to Prepare a USB Drive for Safe Removal
и использующего 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.