0

I am struggling trying to create an application capable to eject any USB mass storage device. After many tries, I am finally using some code found in codeproject https://www.codeproject.com/Articles/375916/How-to-Prepare-a-USB-Drive-for-Safe-Removal BUT with some USB devices I get an error "This device is currently in use. Close any programs or windows that might be using the device, and then try again.". I dont really understand why it happens for some devices and not for others...

The error window is shown and basically removalDrive() method returns false, USB not ejected.

        public static bool RemoveDrive( string driveCharWithColon )
    {
        // open the storage volume
        IntPtr hVolume = CreateFile( @"\\.\" + driveCharWithColon, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero );
        if ( hVolume.ToInt32( ) == -1 ) return false;

        // get the volume's device number
        long DeviceNumber = GetDeviceNumber( hVolume );
        if ( DeviceNumber == -1 ) return false;

        // get the drive type which is required to match the device numbers correctely
        string rootPath = driveCharWithColon + "\\";        
        DriveType driveType = GetDriveType( rootPath );

        // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
        StringBuilder pathInformation = new StringBuilder( 250 );
        uint res = QueryDosDevice( driveCharWithColon, pathInformation, 250 );
        if ( res == 0 ) return false;

        // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
        long DevInst = GetDrivesDevInstByDeviceNumber( DeviceNumber, driveType, pathInformation.ToString( ) );
        if ( DevInst == 0 ) return false;

        // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
        int DevInstParent = 0;
        CM_Get_Parent( ref DevInstParent, ( int ) DevInst, 0 );

        for ( int tries=1; tries <= 3; tries++ )  // sometimes we need some tries...
        {
            int r = CM_Request_Device_Eject_NoUi( DevInstParent, IntPtr.Zero, null, 0, 0 );
            if ( r == 0 ) return true;
            Thread.Sleep( 500 );
        }
        return false;
    }

Reading more answers I found this one Eject Memory card from Card Reader C#

Where it is mentioned that before invoking the method CM_Request_Device_Eject_NoUi, I have to call "LockVolume, DismountVolume, and PrepareRemovalOfVolume" using the hVolume returned from CreateFile.

Unfortunately these functions are only provided by Microsoft in C++. how-to-ejecting-removable-media-in-windows

I wanted to try this solution but I dont really know how to implement those methods using c#.

Any could give me any idea about what might be happening? I tried with simply closing the file handler

CloseHandle(hVolume);

before calling the method CM_Request_Device_Eject_NoUi but it created other SEHException exception (External component has thrown an exception).

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
YaKs
  • 143
  • 12

1 Answers1

1

I managed to implemented in C# the 3 methods but the methods LockVolume and DismountVolume always return a false value.

    const int LOCK_TIMEOUT = 10000;       // 10 Seconds
    const int LOCK_RETRIES = 20; 
    public static bool LockVolume(IntPtr hVolume)
    {
        int dwBytesReturned;
        int dwSleepAmount;
        int nTryCount;
        dwSleepAmount = LOCK_TIMEOUT / LOCK_RETRIES;
        // Do this in a loop until a timeout period has expired
        for (nTryCount = 0; nTryCount < LOCK_RETRIES; nTryCount++)
        {
            if (DeviceIoControl(hVolume,
                                FSCTL_LOCK_VOLUME,
                                IntPtr.Zero, 0,
                                IntPtr.Zero, 0,
                                out dwBytesReturned,
                                IntPtr.Zero))
                return true;
            Thread.Sleep(dwSleepAmount);
        }
        return false;
    }

    public static bool PreventRemovalOfVolume(IntPtr hVolume, bool fPreventRemoval)
    {
        int retVal;
        IntPtr buffer = new IntPtr((fPreventRemoval) ? 1 : 0);
        return DeviceIoControl(hVolume, IOCTL_STORAGE_MEDIA_REMOVAL, buffer, 1, IntPtr.Zero, 0, out retVal, IntPtr.Zero);
    }
    public static bool DismountVolume(IntPtr hVolume)
    {
        int dwBytesReturned;

        return DeviceIoControl(hVolume,
                                FSCTL_DISMOUNT_VOLUME,
                                IntPtr.Zero, 0,
                                IntPtr.Zero, 0,
                                out dwBytesReturned,
                                IntPtr.Zero);
    }

    public static bool  AutoEjectVolume(IntPtr hVolume)
    {
        int  dwBytesReturned;

        return DeviceIoControl(hVolume,
                                IOCTL_STORAGE_EJECT_MEDIA,
                                IntPtr.Zero, 0,
                                IntPtr.Zero, 0,
                                out dwBytesReturned,
                                IntPtr.Zero);
    }

    public static bool CloseVolume(IntPtr hVolume)
    {
        return CloseHandle(hVolume);
    }

UPDATE: I captured the error produced in the DeviceIoControl call using the code

int error = Marshal.GetLastWin32Error();

and I got error 6, meaning ERROR_INVALID_HANDLE. :O

Now I am even more confused as that handler works fine in the rest of the RemoveDrive method, including the CloseVolume(handler) call.

        public static bool RemoveDrive(string driveCharWithColon)
    {
        // open the storage volume
        IntPtr hVolume = CreateFile(@"\\.\" + driveCharWithColon, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
        if (hVolume.ToInt32() == -1) return false;

        // get the volume's device number
        long DeviceNumber = GetDeviceNumber(hVolume);
        if (DeviceNumber == -1) return false;

        // get the drive type which is required to match the device numbers correctely
        string rootPath = driveCharWithColon + "\\";
        DriveType driveType = GetDriveType(rootPath);

        // get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
        StringBuilder pathInformation = new StringBuilder(250);
        uint res = QueryDosDevice(driveCharWithColon, pathInformation, 250);
        if (res == 0) return false;

        // get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
        long DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, driveType, pathInformation.ToString());
        if (DevInst == 0) return false;

        // get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
        int DevInstParent = 0;
        CM_Get_Parent(ref DevInstParent, (int)DevInst, 0);

        // Lock and dismount the volume.
        bool e = LockVolume(hVolume);
        int error = Marshal.GetLastWin32Error();
        bool f = DismountVolume(hVolume);
        int error2 = Marshal.GetLastWin32Error();

        // Close the volume so other processes can use the drive.
        if (!CloseVolume(hVolume))
            return false;

        for (int tries = 1; tries <= 3; tries++)  // sometimes we need some tries...
        {
            int r = CM_Request_Device_Eject_NoUi(DevInstParent, IntPtr.Zero, null, 0, 0);
            if (r == 0) return true;
            Thread.Sleep(500);
        }

        return false;
    }
YaKs
  • 143
  • 12