1

I am working on code to programmatically eject a USB device on user request using the Win32 API (which I don't normally work with). From searching the web up and down, the code below is what I have come up with.

As you can see, my call to CreateFile() used to pass GENERIC_READ|GENERIC_WRITE for the dwIoControlCode parameter, but when I debugged, I got a handle with value INVALID_HANDLE_VALUE. In my call to FormatMessage(), the GetLastError() call returned a code that translated to "Access is denied". Looking around, I saw someone who said pass 0 instead of GENERIC_READ|GENERIC_WRITE. I tried that and I got a handle, no longer a bad handle value. With the non-bad handle value, I continued working with the subsequent code.

The next call was to DeviceIoControl() (GetHandleInformation() and DeviceIoControl came in later for debugging purposes), passing the supposedly non-bad handle along with FSCTL_LOCK_VOLUME for the dwIoControlCode parameter, and the call returned false, with GetLastError() returning a code translating to "Incorrect function". I inserted the calls to GetHandleInformation() and DeviceIoControl(..., IOCTL_STORAGE_CHECK_VERIFY, ...) just before this in order to check my handle. GetHandleInformation() returned true, so that sounded good. However, DeviceIoControl(..., IOCTL_STORAGE_CHECK_VERIFY, ...) returned false, with GetLastError() returning a code translating to "Access is denied". I haven't yet been able to get passed this point.

This code, or variations on it, is suggested by many on the internet, but it's not working for me. I'm hoping someone can point out where I'm going wrong here. I'm compiling on Win7 64-bit using the MSVC2010 compiler.

Thanks for any help.

#include <windows.h>

void ejectDrive(char driveletter)
{
    DWORD dwRet = 0;
    DWORD retSize;
    LPTSTR pTemp=NULL;
    bool ok = false;
    char devicepath[7];
    char format[] = "\\\\.\\?:";
    wchar_t wtext[7];

    strcpy_s(devicepath, format);
    devicepath[4] = driveletter;
    mbstowcs(wtext, devicepath,strlen(devicepath)+1);

    HANDLE hVol = CreateFile(wtext, 0 /*GENERIC_READ | GENERIC_WRITE*/,
                             FILE_SHARE_WRITE | FILE_SHARE_READ, NULL,
                             OPEN_EXISTING, 0, NULL);
    if (hVol == INVALID_HANDLE_VALUE)
    {
        retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
                              FORMAT_MESSAGE_FROM_SYSTEM|
                              FORMAT_MESSAGE_ARGUMENT_ARRAY,
                              NULL,
                              GetLastError(),
                              LANG_NEUTRAL,
                              (LPTSTR)&pTemp,
                              0,
                              NULL );
        return;
    }

    LPDWORD lpdwFlags;
    ok = GetHandleInformation(hVol, lpdwFlags);
    retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
                          FORMAT_MESSAGE_FROM_SYSTEM|
                          FORMAT_MESSAGE_ARGUMENT_ARRAY,
                          NULL,
                          GetLastError(),
                          LANG_NEUTRAL,
                          (LPTSTR)&pTemp,
                          0,
                          NULL );

    ok = DeviceIoControl(hVol, IOCTL_STORAGE_CHECK_VERIFY, NULL,0,NULL, 0, lpdwFlags, 0);
    if(!ok)
    {
        retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
                              FORMAT_MESSAGE_FROM_SYSTEM|
                              FORMAT_MESSAGE_ARGUMENT_ARRAY,
                              NULL,
                              GetLastError(),
                              LANG_NEUTRAL,
                              (LPTSTR)&pTemp,
                              0,
                              NULL );
        return;
    }

    ok = DeviceIoControl(hVol, FSCTL_LOCK_VOLUME, 0, 0, 0, 0, &dwRet, 0);
    if(!ok)
    {
        retSize=FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
                              FORMAT_MESSAGE_FROM_SYSTEM|
                              FORMAT_MESSAGE_ARGUMENT_ARRAY,
                              NULL,
                              GetLastError(),
                              LANG_NEUTRAL,
                              (LPTSTR)&pTemp,
                              0,
                              NULL );
        return;
    }

    if(!DeviceIoControl(hVol, FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, &dwRet, 0))
    {
        return;
    }

    DeviceIoControl(hVol, IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, &dwRet, 0);

    CloseHandle(hVol);
}
bmahf
  • 965
  • 2
  • 11
  • 27
  • 1
    Try opening the handle with just `GENERIC_READ`. Also note that `DeviceIoControl` will only return true if the media is accessible (as per the [IOCTL_STORAGE_CHECK_VERIFY docs](http://msdn.microsoft.com/en-us/library/windows/desktop/aa363404%28v=vs.85%29.aspx)). – Jonathan Potter Jan 09 '15 at 20:24
  • According to http://stackoverflow.com/q/3918248/103167 you will need `GENERIC_READ | GENERIC_WRITE` to perform an eject operation. So resolve the `CreateFile` failure by running in the context of a user with sufficient permission to the device, instead of by opening with 0 access. – Ben Voigt Jan 09 '15 at 20:36
  • Also, you may need to open the PhysicalDrive device, not the volume. – Ben Voigt Jan 09 '15 at 20:37
  • I have some code that works in Windows PE, though Windows proper might behave differently. FWIW, though: I open the volume, I don't need to use the PhysicalDrive device; I request `MAXIMUM_ALLOWED` access; and I only call `IOCTL_STORAGE_EJECT_MEDIA`, there doesn't seem to be any need to lock or dismount the volume first. – Harry Johnston Jan 10 '15 at 00:44
  • If the code needs to work for non-administrators, you might need a completely different approach. (I'm not sure if that's even possible.) – Harry Johnston Jan 10 '15 at 00:45
  • That was it. I changed my CreateFile() back to passing GENERIC_READ|GENERIC_WRITE instead of 0 for the dwIoControlCode parameter, compiled, and then right clicked the exe and chose Run as Administrator. Ran fine, and the USB was ejected. Seems to me though that, since I can ask the system as non-admin to eject the USB, I should be able to do something in my application that would allow for ejecting without having my users have administrator rights. Anyone know anything about that? – bmahf Jan 12 '15 at 18:56

1 Answers1

1

I used a solution in my project that I write up in more detail in this SO answer.

However, I did modify the call to CreateFile — based on seeing your SO question earlier this year — to the following signature:

// C#
hVolume = CreateFile(@"\\.\A:", GENERIC_READ,
                                FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero,
                                OPEN_EXISTING, 0, IntPtr.Zero);

My application calling this runs without elevated privileges, and successfully ejects the drive.

Community
  • 1
  • 1
John Castleman
  • 1,552
  • 11
  • 12