Click here to Skip to main content
15,881,424 members
Articles / Desktop Programming / Win32

How to Prepare a USB Drive for Safe Removal

Rate me:
Please Sign up or sign in to vote.
4.75/5 (10 votes)
13 Mar 2013CPOL 60.6K   2.2K   41   16
This is an alternative for "How to Prepare a USB Drive for Safe Removal"

Download RemoveDriveByLetter.zip

Introduction 

This is a port to C# of the code form the article: 

http://www.codeproject.com/Articles/13839/How-to-Prepare-a-USB-Drive-for-Safe-Removal
by Uwe Sieber 

and with code parts from http://www.codeproject.com/Articles/13530/Eject-USB-disks-using-C 
by Simon Mourier 

All credits belongs to them, I just ported the code to C# with the help of their code bases. 

Using the code

There is no demo program, just a static class named RemoveDriveTools with everything inside to pinvoke the system libraries. 

C#
// call it inside your application like:
RemoveDriveTools.RemoveDrive( "H:" );
     

Here the full code of the class (I don't know why the syntax formatter srews up)  

C#
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;

namespace RemoveDriveByLetter
{
    // Converted from: http://www.codeproject.com/Articles/13839/How-to-Prepare-a-USB-Drive-for-Safe-Removal

    public class RemoveDriveTools
    {
        [StructLayout( LayoutKind.Sequential )]
        struct STORAGE_DEVICE_NUMBER
        {
            public int DeviceType;
            public int DeviceNumber;
            public int PartitionNumber;
        };

        enum DriveType : uint
        {
            /// <summary>The drive type cannot be determined.</summary>
            DRIVE_UNKNOWN = 0,      //DRIVE_UNKNOWN
            /// <summary>The root path is invalid, for example, no volume is mounted at the path.</summary>
            DRIVE_NO_ROOT_DIR = 1,  //DRIVE_NO_ROOT_DIR
            /// <summary>The drive is a type that has removable media, for example, a floppy drive or removable hard disk.</summary>
            DRIVE_REMOVABLE = 2,    //DRIVE_REMOVABLE
            /// <summary>The drive is a type that cannot be removed, for example, a fixed hard drive.</summary>
            DRIVE_FIXED = 3,        //DRIVE_FIXED
            /// <summary>The drive is a remote (network) drive.</summary>
            DRIVE_REMOTE = 4,       //DRIVE_REMOTE
            /// <summary>The drive is a CD-ROM drive.</summary>
            DRIVE_CDROM = 5,        //DRIVE_CDROM
            /// <summary>The drive is a RAM disk.</summary>
            DRIVE_RAMDISK = 6       //DRIVE_RAMDISK
        }

        const string GUID_DEVINTERFACE_VOLUME = "53f5630d-b6bf-11d0-94f2-00a0c91efb8b";
        const string GUID_DEVINTERFACE_DISK = "53f56307-b6bf-11d0-94f2-00a0c91efb8b";
        const string GUID_DEVINTERFACE_FLOPPY = "53f56311-b6bf-11d0-94f2-00a0c91efb8b";
        const string GUID_DEVINTERFACE_CDROM = "53f56308-b6bf-11d0-94f2-00a0c91efb8b";

        const int INVALID_HANDLE_VALUE = -1;
        const int GENERIC_READ = unchecked( ( int ) 0x80000000 );
        const int GENERIC_WRITE = unchecked( ( int ) 0x40000000 );
        const int FILE_SHARE_READ = unchecked( ( int ) 0x00000001 );
        const int FILE_SHARE_WRITE = unchecked( ( int ) 0x00000002 );
        const int OPEN_EXISTING = unchecked( ( int ) 3 );
        const int FSCTL_LOCK_VOLUME = unchecked( ( int ) 0x00090018 );
        const int FSCTL_DISMOUNT_VOLUME = unchecked( ( int ) 0x00090020 );
        const int IOCTL_STORAGE_EJECT_MEDIA = unchecked( ( int ) 0x002D4808 );
        const int IOCTL_STORAGE_MEDIA_REMOVAL = unchecked( ( int ) 0x002D4804 );
        const int IOCTL_STORAGE_GET_DEVICE_NUMBER = unchecked( ( int ) 0x002D1080 );

        const int ERROR_NO_MORE_ITEMS = 259;
        const int ERROR_INSUFFICIENT_BUFFER = 122;
        const int ERROR_INVALID_DATA = 13;

        [DllImport( "kernel32.dll" )]
        static extern DriveType GetDriveType( [MarshalAs( UnmanagedType.LPStr )] string lpRootPathName );

        [DllImport( "kernel32.dll" )]
        static extern uint QueryDosDevice( string lpDeviceName, StringBuilder lpTargetPath, int ucchMax );

        [DllImport( "kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true )]
        static extern IntPtr CreateFile(
            string lpFileName,
            int dwDesiredAccess,
            int dwShareMode,
            IntPtr lpSecurityAttributes,
            int dwCreationDisposition,
            int dwFlagsAndAttributes,
            IntPtr hTemplateFile );

        [DllImport( "kernel32.dll", ExactSpelling = true, SetLastError = true )]
        static extern bool CloseHandle( IntPtr handle );

        [DllImport( "kernel32.dll", ExactSpelling = true, SetLastError = true )]
        static extern bool DeviceIoControl(
            IntPtr hDevice,
            int dwIoControlCode,
            IntPtr lpInBuffer,
            int nInBufferSize,
            IntPtr lpOutBuffer,
            int nOutBufferSize,
            out int lpBytesReturned,
            IntPtr lpOverlapped );

        // from setupapi.h
        const int DIGCF_PRESENT = ( 0x00000002 );
        const int DIGCF_DEVICEINTERFACE = ( 0x00000010 );

        [StructLayout( LayoutKind.Sequential )]
        class SP_DEVINFO_DATA
        {
            public int cbSize = Marshal.SizeOf( typeof( SP_DEVINFO_DATA ) );
            public Guid classGuid = Guid.Empty; // temp
            public int devInst = 0; // dumy
            public int reserved = 0;
        }

        [StructLayout( LayoutKind.Sequential, Pack = 2 )]
        struct SP_DEVICE_INTERFACE_DETAIL_DATA
        {
            public int cbSize;
            public short devicePath;
        }

        [StructLayout( LayoutKind.Sequential )]
        class SP_DEVICE_INTERFACE_DATA
        {
            public int cbSize = Marshal.SizeOf( typeof( SP_DEVICE_INTERFACE_DATA ) );
            public Guid interfaceClassGuid = Guid.Empty; // temp
            public int flags = 0;
            public int reserved = 0;
        }

        [DllImport( "setupapi.dll" )]
        static extern IntPtr SetupDiGetClassDevs(
            ref Guid classGuid,
            int enumerator,
            IntPtr hwndParent,
            int flags );

        [DllImport( "setupapi.dll", SetLastError = true, CharSet = CharSet.Auto )]
        static extern bool SetupDiEnumDeviceInterfaces(
            IntPtr deviceInfoSet,
            SP_DEVINFO_DATA deviceInfoData,
            ref Guid interfaceClassGuid,
            int memberIndex,
            SP_DEVICE_INTERFACE_DATA deviceInterfaceData );

        [DllImport( "setupapi.dll", SetLastError = true, CharSet = CharSet.Auto )]
        static extern bool SetupDiGetDeviceInterfaceDetail(
            IntPtr deviceInfoSet,
            SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
            IntPtr deviceInterfaceDetailData,
            int deviceInterfaceDetailDataSize,
            ref int requiredSize,
            SP_DEVINFO_DATA deviceInfoData );

        [DllImport( "setupapi.dll" )]
        static extern uint SetupDiDestroyDeviceInfoList(
            IntPtr deviceInfoSet );

        [DllImport( "setupapi.dll" )]
        static extern int CM_Get_Parent(
            ref int pdnDevInst,
            int dnDevInst,
            int ulFlags );

        [DllImport( "setupapi.dll" )]
        static extern int CM_Request_Device_Eject(
            int dnDevInst,
            out PNP_VETO_TYPE pVetoType,
            StringBuilder pszVetoName,
            int ulNameLength,
            int ulFlags );

        [DllImport( "setupapi.dll", EntryPoint = "CM_Request_Device_Eject" )]
        static extern int CM_Request_Device_Eject_NoUi(
            int dnDevInst,
            IntPtr pVetoType,
            StringBuilder pszVetoName,
            int ulNameLength,
            int ulFlags );

        enum PNP_VETO_TYPE
        {
            Ok,
            TypeUnknown,
            LegacyDevice,
            PendingClose,
            WindowsApp,
            WindowsService,
            OutstandingOpen,
            Device,
            Driver,
            IllegalDeviceRequest,
            InsufficientPower,
            NonDisableable,
            LegacyDriver,
            InsufficientRights
        }

        /// <summary>
        /// Call with "X:" or similar
        /// </summary>
        /// <param name="driveCharWithColon"></param>
        /// <returns></returns>
        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;
        }

        static long GetDeviceNumber( IntPtr handle )
        {
            // get the volume's device number
            long DeviceNumber = -1;
            int size = 0x400; // some big size
            IntPtr buffer = Marshal.AllocHGlobal( size );
            int bytesReturned = 0;

            try
            {
                DeviceIoControl( handle, IOCTL_STORAGE_GET_DEVICE_NUMBER, IntPtr.Zero, 0, buffer, size, out bytesReturned, IntPtr.Zero );
            }
            finally
            {
                CloseHandle( handle );
            }

            if ( bytesReturned > 0 )
            {
                STORAGE_DEVICE_NUMBER sdn = ( STORAGE_DEVICE_NUMBER ) Marshal.PtrToStructure( buffer, typeof( STORAGE_DEVICE_NUMBER ) );
                DeviceNumber = sdn.DeviceNumber;
            }
            Marshal.FreeHGlobal( buffer );

            return DeviceNumber;
        }


        //----------------------------------------------------------------------
        // returns the device instance handle of a storage volume or 0 on error
        //----------------------------------------------------------------------
        static long GetDrivesDevInstByDeviceNumber( long DeviceNumber, DriveType DriveType, string dosDeviceName )
        {
            bool IsFloppy = dosDeviceName.Contains( "\\Floppy" ); // who knows a better way?
            Guid guid;

            switch ( DriveType )
            {
                case DriveType.DRIVE_REMOVABLE:
                    if ( IsFloppy ) guid = new Guid( GUID_DEVINTERFACE_FLOPPY );
                    else guid = new Guid( GUID_DEVINTERFACE_DISK );
                    break;
                case DriveType.DRIVE_FIXED:
                    guid = new Guid( GUID_DEVINTERFACE_DISK );
                    break;
                case DriveType.DRIVE_CDROM:
                    guid = new Guid( GUID_DEVINTERFACE_CDROM );
                    break;
                default:
                    return 0;
            }

            // Get device interface info set handle for all devices attached to system
            IntPtr hDevInfo = SetupDiGetClassDevs( ref guid, 0, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE );

            if ( hDevInfo.ToInt32( ) == INVALID_HANDLE_VALUE ) throw new Win32Exception( Marshal.GetLastWin32Error( ) );

            // Retrieve a context structure for a device interface of a device information set
            int dwIndex = 0;

            while ( true )
            {
                SP_DEVICE_INTERFACE_DATA interfaceData = new SP_DEVICE_INTERFACE_DATA( );
                if ( !SetupDiEnumDeviceInterfaces( hDevInfo, null, ref guid, dwIndex, interfaceData ) )
                {
                    int error = Marshal.GetLastWin32Error( );
                    if ( error != ERROR_NO_MORE_ITEMS ) throw new Win32Exception( error );
                    break;
                }

                SP_DEVINFO_DATA devData = new SP_DEVINFO_DATA( );
                int size = 0;
                if ( !SetupDiGetDeviceInterfaceDetail( hDevInfo, interfaceData, IntPtr.Zero, 0, ref size, devData ) )
                {
                    int error = Marshal.GetLastWin32Error( );
                    if ( error != ERROR_INSUFFICIENT_BUFFER ) throw new Win32Exception( error );
                }

                IntPtr buffer = Marshal.AllocHGlobal( size );
                SP_DEVICE_INTERFACE_DETAIL_DATA detailData = new SP_DEVICE_INTERFACE_DETAIL_DATA( );
                detailData.cbSize = Marshal.SizeOf( typeof( SP_DEVICE_INTERFACE_DETAIL_DATA ) );
                Marshal.StructureToPtr( detailData, buffer, false );

                if ( !SetupDiGetDeviceInterfaceDetail( hDevInfo, interfaceData, buffer, size, ref size, devData ) )
                {
                    Marshal.FreeHGlobal( buffer );
                    throw new Win32Exception( Marshal.GetLastWin32Error( ) );
                }

                IntPtr pDevicePath = ( IntPtr ) ( ( int ) buffer + Marshal.SizeOf( typeof( int ) ) );
                string devicePath = Marshal.PtrToStringAuto( pDevicePath );
                Marshal.FreeHGlobal( buffer );

                // open the disk or cdrom or floppy
                IntPtr hDrive = CreateFile( devicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero );
                if ( hDrive.ToInt32( ) != INVALID_HANDLE_VALUE )
                {
                    // get its device number
                    long driveDeviceNumber = GetDeviceNumber( hDrive );
                    if ( DeviceNumber == driveDeviceNumber )   // match the given device number with the one of the current device
                    {
                        // CloseHandle( hDrive );  // handle hDrive was closed inside GetDeviceNumber(..)
                        SetupDiDestroyDeviceInfoList( hDevInfo );
                        return devData.devInst;
                    }
                    // CloseHandle( hDrive );  <span style="font-size: 9pt;">// handle hDrive was closed inside GetDeviceNumber(..)</span>

                }
                dwIndex++;
            }

            SetupDiDestroyDeviceInfoList( hDevInfo );
            return 0;
        }

        static void Test( )
        {
            bool ok = RemoveDrive( "H:" );
        }
    }
}

     

Points of Interest

Compared to the original I used the CM_Request_Device_Eject_NoUi instead of CM_Request_Device_Eject. This shows a popup ballon like "Hardware is now save to be removed". 

History

First Draft  

Updated 13.03.2013:
I fixed a minor bug (see comments)
(.NET 4 with attached debugger threw an exception because of closing a handle twice)
CAUTION: The code in the zip is unfixed! 

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
gine9424-Mar-21 2:22
gine9424-Mar-21 2:22 
BugRe: My vote of 5 Pin
Member 1515485016-Apr-21 0:00
Member 1515485016-Apr-21 0:00 
GeneralExcellent!!!! Pin
Israel Marsilli4-Aug-15 8:35
Israel Marsilli4-Aug-15 8:35 
AnswerWin7 X64 Pin
Cruiser7710-Dec-14 7:43
Cruiser7710-Dec-14 7:43 
GeneralX64 problems Pin
OliverCanucks17-Jan-19 5:19
OliverCanucks17-Jan-19 5:19 
Question"Windows8" In does not display in a pop-up. Pin
herewego00213-Nov-14 22:44
herewego00213-Nov-14 22:44 
AnswerRe: "Windows8" In does not display in a pop-up. Pin
Uwe_Sieber11-Apr-24 5:35
Uwe_Sieber11-Apr-24 5:35 
QuestionThanks and question Pin
Member 1048829026-Dec-13 2:51
Member 1048829026-Dec-13 2:51 
QuestionPNP_VETO_TYPE Pin
rsam703-Dec-13 2:07
rsam703-Dec-13 2:07 
AnswerRe: PNP_VETO_TYPE Pin
Member 454336516-Jan-18 1:33
Member 454336516-Jan-18 1:33 
GeneralThank you~ Pin
Cosmic_Spy24-Nov-13 0:24
Cosmic_Spy24-Nov-13 0:24 
BugCloseHandle exception in Net.4 Pin
sergex29512-Mar-13 21:56
sergex29512-Mar-13 21:56 
AnswerRe: CloseHandle exception in Net.4 Pin
Armanisoft12-Mar-13 22:49
Armanisoft12-Mar-13 22:49 
QuestionWhy are you.... Pin
xComaWhitex30-Apr-12 12:02
xComaWhitex30-Apr-12 12:02 
AnswerRe: Why are you.... Pin
Armanisoft30-Apr-12 19:28
Armanisoft30-Apr-12 19:28 
GeneralCould'nt be done any better ! Pin
Mazen el Senih30-Apr-12 7:27
professionalMazen el Senih30-Apr-12 7:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.