|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionRemoving a USB drive using the Windows tray icon is easy, especially if you single left-click it, but sometimes it's useful to do it from your program. BackgroundThere are some samples around, but the ones I saw were searching for the volume and then calling USB drives without removable media are handled like basic disks, so they can have multiple partitions and the volume's parent device is not the disk! Under Vista, this is the case for removable drives too, but multiple partitions are still not allowed. By the way, there are more differences resulting from the type of the USB disk. Here is some information. The magic link between storage volumes and their disk is the device number. You can get it via By the way: Legacy floppies are not part of the The SampleThis sample is a simplified version of my command-line tool RemoveDrive. It expects the drive letter to prepare for safe removal as a parameter. It opens the volume and gets its device number: // "X:\" -> for GetDriveType
char szRootPath[] = "X:\\";
szRootPath[0] = DriveLetter;
// "X:" -> for QueryDosDevice
char szDevicePath[] = "X:";
szDevicePath[0] = DriveLetter;
// "\\.\X:" -> to open the volume
char szVolumeAccessPath[] = "\\\\.\\X:";
szVolumeAccessPath[4] = DriveLetter;
long DeviceNumber = -1;
HANDLE hVolume = CreateFile(szVolumeAccessPath, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (hVolume == INVALID_HANDLE_VALUE) {
return 1;
}
STORAGE_DEVICE_NUMBER sdn;
DWORD dwBytesReturned = 0;
long res = DeviceIoControl(hVolume,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &sdn, sizeof(sdn),
&dwBytesReturned, NULL);
if ( res ) {
DeviceNumber = sdn.DeviceNumber;
}
CloseHandle(hVolume);
if ( DeviceNumber == -1 ) {
return 1;
}
UINT DriveType = GetDriveType(szRootPath);
// get the dos device name (like \device\floppy0)
// to decide if it's a floppy or not
char szDosDeviceName[MAX_PATH];
res =QueryDosDevice(szDevicePath,szDosDeviceName,MAX_PATH);
if ( !res ) {
return 1;
}
DEVINST DevInst=GetDrivesDevInstByDeviceNumber(DeviceNumber,
DriveType, szDosDeviceName);
if ( DeviceNumber == -1 ) {
return 1;
}
Then it enumerates all disks, floppies or CD-ROMs — depending on the drive type and the DOS device name — using the setup API. The disk's device numbers are matched with the device number mentioned above in order to get the device instance: //---------------------------------------------------------
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber,
UINT DriveType, char* szDosDeviceName)
{
bool IsFloppy = (strstr(szDosDeviceName,
"\\Floppy") != NULL); // is there a better way?
GUID* guid;
switch (DriveType) {
case DRIVE_REMOVABLE:
if ( IsFloppy ) {
guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY;
} else {
guid = (GUID*)&GUID_DEVINTERFACE_DISK;
}
break;
case DRIVE_FIXED:
guid = (GUID*)&GUID_DEVINTERFACE_DISK;
break;
case DRIVE_CDROM:
guid = (GUID*)&GUID_DEVINTERFACE_CDROM;
break;
default:
return 0;
}
// Get device interface info set handle
// for all devices attached to system
HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDevInfo == INVALID_HANDLE_VALUE) {
return 0;
}
// Retrieve a context structure for a device interface
// of a device information set.
DWORD dwIndex = 0;
BOOL bRet = FALSE;
BYTE Buf[1024];
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd =
(PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
SP_DEVICE_INTERFACE_DATA spdid;
SP_DEVINFO_DATA spdd;
DWORD dwSize;
spdid.cbSize = sizeof(spdid);
while ( true ) {
bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL,
guid, dwIndex, &spdid);
if (!bRet) {
break;
}
dwSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo,
&spdid, NULL, 0, &dwSize, NULL);
if ( dwSize!=0 && dwSize<=sizeof(Buf) ) {
pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!
ZeroMemory((PVOID)&spdd, sizeof(spdd));
spdd.cbSize = sizeof(spdd);
long res =
SetupDiGetDeviceInterfaceDetail(hDevInfo, &
spdid, pspdidd,
dwSize, &dwSize,
&spdd);
if ( res ) {
HANDLE hDrive = CreateFile(pspdidd->DevicePath,0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, NULL, NULL);
if ( hDrive != INVALID_HANDLE_VALUE ) {
STORAGE_DEVICE_NUMBER sdn;
DWORD dwBytesReturned = 0;
res = DeviceIoControl(hDrive,
IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &sdn, sizeof(sdn),
&dwBytesReturned, NULL);
if ( res ) {
if ( DeviceNumber == (long)sdn.DeviceNumber ) {
CloseHandle(hDrive);
SetupDiDestroyDeviceInfoList(hDevInfo);
return spdd.DevInst;
}
}
CloseHandle(hDrive);
}
}
}
dwIndex++;
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return 0;
}
//---------------------------------------------------------
The parent device of the disk, floppy or CD-ROM is the USB device to eject. However, DiscussionHere are some interesting links:
If you use it for PATA drives, then both master and slave drives are removed! However, both can be brought back with a DEVCON RESCAN. If the functions are called with NULL/0 for the veto parameters, then XP shows the "it's safe now" balloon tip, W2K shows a messagebox and Vista shows nothing. As McCoy once said: "I know engineers. They love to change things." I remember that I've seen Under Windows 2000, the ANSI versions of both functions are not implemented. They return Both functions may take several seconds until they return, so it's a good idea to put them into their own thread. However, I didn't do that in this simple example of removal: ULONG Status = 0;
ULONG ProblemNumber = 0;
PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown;
WCHAR VetoNameW[MAX_PATH];
bool bSuccess = false;
// get drives's parent, e.g. the USB bridge,
// the SATA port, an IDE channel with two drives!
DEVINST DevInstParent = 0;
res = CM_Get_Parent(&DevInstParent, DevInst, 0);
for ( long tries=1; tries>=3; tries++ ) {
// sometimes we need some tries...
VetoNameW[0] = 0;
res = CM_Request_Device_EjectW(DevInstParent,
&VetoType, VetoNameW, MAX_PATH, 0);
bSuccess = (res==CR_SUCCESS &&
VetoType==PNP_VetoTypeUnknown);
if ( bSuccess ) {
break;
}
Sleep(500); // required to give the next tries a chance!
}
It's often seen that the removal fails on the first attempt but works on the second attempt. Therefore, I just try it three times. What Makes the Removal FailThe prepartion for safe removal fails as long as there is one open handle to the disk or to the storage volume. And of course you cannot run this EXE from the drive to remove it. To do that you would need a temporary copy on another drive. ProcessExplorer is great for discovering which process holds an open handle to a drive. Press Ctrl+F and enter the drive letter, like The Demo ProjectThe demo project is made with VS6. It requires LIBs and headers from the Microsoft Windows Platform SDK. The latest version that integrates and works perfectly with VS6 is from February 2003. It's called Platform SDK for Windows Server 2003 Build 3780.0 and is still available for download: Unfortunately, the CFG.h file is still missing in pre Vista SDKs. You can take it from the Vista Platform SDK or from any DDK/WDK since build 2600. If you don't want to download a whole DDK for a single file then you can use the CFG.h from the ReactOS project, which is at least compatible for this demo. Change the History
|
||||||||||||||||||||||