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.
RemoveDriveTools.RemoveDrive( "H:" );
Here the full code of the class (I don't know why the syntax formatter srews up)
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;
namespace RemoveDriveByLetter
{
public class RemoveDriveTools
{
[StructLayout( LayoutKind.Sequential )]
struct STORAGE_DEVICE_NUMBER
{
public int DeviceType;
public int DeviceNumber;
public int PartitionNumber;
};
enum DriveType : uint
{
DRIVE_UNKNOWN = 0,
DRIVE_NO_ROOT_DIR = 1,
DRIVE_REMOVABLE = 2,
DRIVE_FIXED = 3,
DRIVE_REMOTE = 4,
DRIVE_CDROM = 5,
DRIVE_RAMDISK = 6
}
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 );
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;
public int devInst = 0;
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;
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
}
public static bool RemoveDrive( string driveCharWithColon )
{
IntPtr hVolume = CreateFile( @"\\.\" + driveCharWithColon, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero );
if ( hVolume.ToInt32( ) == -1 ) return false;
long DeviceNumber = GetDeviceNumber( hVolume );
if ( DeviceNumber == -1 ) return false;
string rootPath = driveCharWithColon + "\\";
DriveType driveType = GetDriveType( rootPath );
StringBuilder pathInformation = new StringBuilder( 250 );
uint res = QueryDosDevice( driveCharWithColon, pathInformation, 250 );
if ( res == 0 ) return false;
long DevInst = GetDrivesDevInstByDeviceNumber( DeviceNumber, driveType, pathInformation.ToString( ) );
if ( DevInst == 0 ) return false;
int DevInstParent = 0;
CM_Get_Parent( ref DevInstParent, ( int ) DevInst, 0 );
for ( int tries=1; tries <= 3; 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 )
{
long DeviceNumber = -1;
int size = 0x400;
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;
}
static long GetDrivesDevInstByDeviceNumber( long DeviceNumber, DriveType DriveType, string dosDeviceName )
{
bool IsFloppy = dosDeviceName.Contains( "\\Floppy" );
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;
}
IntPtr hDevInfo = SetupDiGetClassDevs( ref guid, 0, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE );
if ( hDevInfo.ToInt32( ) == INVALID_HANDLE_VALUE ) throw new Win32Exception( Marshal.GetLastWin32Error( ) );
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 );
IntPtr hDrive = CreateFile( devicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero );
if ( hDrive.ToInt32( ) != INVALID_HANDLE_VALUE )
{
long driveDeviceNumber = GetDeviceNumber( hDrive );
if ( DeviceNumber == driveDeviceNumber )
{
SetupDiDestroyDeviceInfoList( hDevInfo );
return devData.devInst;
}
}
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!