├── DiskDeviceClass.cs ├── DeviceCapabilities.cs ├── Properties └── AssemblyInfo.cs ├── VolumeDeviceClass.cs ├── README.md ├── UsbEject.csproj ├── Native.cs ├── Volume.cs ├── Device.cs └── DeviceClass.cs /DiskDeviceClass.cs: -------------------------------------------------------------------------------- 1 | // UsbEject version 1.0 March 2006 2 | // written by Simon Mourier 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace UsbEject.Library 9 | { 10 | /// 11 | /// The device class for disk devices. 12 | /// 13 | public class DiskDeviceClass : DeviceClass 14 | { 15 | /// 16 | /// Initializes a new instance of the DiskDeviceClass class. 17 | /// 18 | public DiskDeviceClass() 19 | :base(new Guid(Native.GUID_DEVINTERFACE_DISK)) 20 | { 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /DeviceCapabilities.cs: -------------------------------------------------------------------------------- 1 | // UsbEject version 1.0 March 2006 2 | // written by Simon Mourier 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace UsbEject.Library 9 | { 10 | /// 11 | /// Contains constants for determining devices capabilities. 12 | /// This enumeration has a FlagsAttribute attribute that allows a bitwise combination of its member values. 13 | /// 14 | [Flags] 15 | public enum DeviceCapabilities 16 | { 17 | Unknown = 0x00000000, 18 | // matches cfmgr32.h CM_DEVCAP_* definitions 19 | 20 | LockSupported = 0x00000001, 21 | EjectSupported = 0x00000002, 22 | Removable = 0x00000004, 23 | DockDevice = 0x00000008, 24 | UniqueId = 0x00000010, 25 | SilentInstall =0x00000020, 26 | RawDeviceOk = 0x00000040, 27 | SurpriseRemovalOk = 0x00000080, 28 | HardwareDisabled = 0x00000100, 29 | NonDynamic = 0x00000200, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("UsbEject")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("UsbEject")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f759389b-ba3b-415f-92af-a55afbff68cf")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /VolumeDeviceClass.cs: -------------------------------------------------------------------------------- 1 | // UsbEject version 1.0 March 2006 2 | // written by Simon Mourier 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace UsbEject.Library 9 | { 10 | /// 11 | /// The device class for volume devices. 12 | /// 13 | public class VolumeDeviceClass : DeviceClass 14 | { 15 | internal SortedDictionary _logicalDrives = new SortedDictionary(); 16 | 17 | /// 18 | /// Initializes a new instance of the VolumeDeviceClass class. 19 | /// 20 | public VolumeDeviceClass() 21 | : base(new Guid(Native.GUID_DEVINTERFACE_VOLUME)) 22 | { 23 | foreach(string drive in Environment.GetLogicalDrives()) 24 | { 25 | StringBuilder sb = new StringBuilder(1024); 26 | if (Native.GetVolumeNameForVolumeMountPoint(drive, sb, sb.Capacity)) 27 | { 28 | _logicalDrives[sb.ToString()] = drive.Replace("\\", ""); 29 | Console.WriteLine(drive + " ==> " + sb.ToString()); 30 | } 31 | } 32 | } 33 | 34 | internal override Device CreateDevice(DeviceClass deviceClass, Native.SP_DEVINFO_DATA deviceInfoData, string path, int index, int disknum = -1) 35 | { 36 | return new Volume(deviceClass, deviceInfoData, path, index); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [ WARNING: I have received several reports of this not working for people. I'm leaving this up in case it's a helpful starting point even if it is not working as-is. I seem to recall needing a paid teir license for Visual Studio in order to have support for these system APIs, and I no longer have access to that. I also haven't written any C# in about 2.5 years (after having only written in it for a few weeks), so I'm probably not the formost expert on how to go about fixing this for your own particular Windows version or hardware architecture. 2 | 3 | Since a bunch of people seem to be landing here and trying this code despite the reported issues, I'm happy to accept pull requests if the changes seem legit and are legitimately documented (as I cannot test the changes myself), so that future "yous" might find something working (or closer to it). Of course, one of you could be a total star and write some tests and some git push hooks to run the tests as a presbumit, so that it's easier to tell when future patches are good :P I'm half kidding, but you'll get credit in the README if you do. ] 4 | 5 | # usbeject 6 | C# code to safely eject removable storage (Windows 8.1, compiled 32-bit) 7 | 8 | This is a class library of code stolen from a codeprojects page, and modified to fix a bug where it wasn't ejecting the drive it 9 | claimed to be ejecting. All the copyright headers in it are from the original author. I'm providing this code here since aparently 10 | the codeprojects page is dead (and the code posted there was broken anyways). 11 | 12 | I was running this code on Windows 8.1 Embedded Industrial (64-bit) compiled as a 32-bit dll. I received a pull request from somebody who looked like they knew what they were doing which apparently makes the code compile/work for AnyCPU. I have not tested this. 13 | 14 | I'm distributing this software with NO WARRANTY and NO GUARANTEE of suitability for any purpose, etc, etc, yada, yada, don't sue me. 15 | 16 | Usage example pulled from my code: 17 | 18 | VolumeDeviceClass volumes = new VolumeDeviceClass(); 19 | foreach (Volume vol in volumes.Devices) 20 | { 21 | if (vol.LogicalDrive.Equals(eject_drive)) 22 | { 23 | eventLog.WriteEntry("Attempting to eject drive: " + cur_write_drive); 24 | vol.Eject(false); 25 | eventLog.WriteEntry("Done ejecting drive."); 26 | break; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /UsbEject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BA1DCACB-99B3-49DB-9A06-A641205D25E6} 8 | Library 9 | Properties 10 | UsbEject 11 | UsbEject 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | AnyCPU 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 60 | -------------------------------------------------------------------------------- /Native.cs: -------------------------------------------------------------------------------- 1 | // UsbEject version 1.0 March 2006 2 | // written by Simon Mourier 3 | 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | namespace UsbEject.Library 9 | { 10 | internal sealed class Native 11 | { 12 | // from winuser.h 13 | internal const int WM_DEVICECHANGE = 0x0219; 14 | 15 | // from winbase.h 16 | internal const int INVALID_HANDLE_VALUE = -1; 17 | internal const int GENERIC_READ = unchecked((int)0x80000000); 18 | internal const int FILE_SHARE_READ = 0x00000001; 19 | internal const int FILE_SHARE_WRITE = 0x00000002; 20 | internal const int OPEN_EXISTING = 3; 21 | 22 | [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] 23 | internal static extern bool GetVolumeNameForVolumeMountPoint( 24 | string volumeName, 25 | StringBuilder uniqueVolumeName, 26 | int uniqueNameBufferCapacity); 27 | 28 | [DllImport("Kernel32.dll", SetLastError = true)] 29 | internal static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); 30 | 31 | [DllImport("Kernel32.dll", SetLastError = true)] 32 | internal static extern bool DeviceIoControl(IntPtr hDevice, int dwIoControlCode, IntPtr lpInBuffer, int nInBufferSize, IntPtr lpOutBuffer, int nOutBufferSize, out int lpBytesReturned, IntPtr lpOverlapped); 33 | 34 | [DllImport("Kernel32.dll", SetLastError = true)] 35 | internal static extern bool CloseHandle(IntPtr hObject); 36 | 37 | // from winerror.h 38 | internal const int ERROR_NO_MORE_ITEMS = 259; 39 | internal const int ERROR_INSUFFICIENT_BUFFER = 122; 40 | internal const int ERROR_INVALID_DATA = 13; 41 | 42 | // from winioctl.h 43 | internal const string GUID_DEVINTERFACE_VOLUME = "53f5630d-b6bf-11d0-94f2-00a0c91efb8b"; 44 | internal const string GUID_DEVINTERFACE_DISK = "53f56307-b6bf-11d0-94f2-00a0c91efb8b"; 45 | internal const int IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS = 0x00560000; 46 | internal const int IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x002d1080; 47 | 48 | [StructLayout(LayoutKind.Sequential)] 49 | internal struct DISK_EXTENT 50 | { 51 | internal int DiskNumber; 52 | internal long StartingOffset; 53 | internal long ExtentLength; 54 | } 55 | 56 | [StructLayout(LayoutKind.Sequential)] 57 | internal struct STORAGE_DEVICE_NUMBER 58 | { 59 | public int DeviceType; 60 | public int DeviceNumber; 61 | public int PartitionNumber; 62 | } 63 | 64 | // from cfg.h 65 | internal enum PNP_VETO_TYPE 66 | { 67 | Ok, 68 | 69 | TypeUnknown, 70 | LegacyDevice, 71 | PendingClose, 72 | WindowsApp, 73 | WindowsService, 74 | OutstandingOpen, 75 | Device, 76 | Driver, 77 | IllegalDeviceRequest, 78 | InsufficientPower, 79 | NonDisableable, 80 | LegacyDriver, 81 | } 82 | 83 | // from cfgmgr32.h 84 | [DllImport("setupapi.dll")] 85 | internal static extern int CM_Get_Parent( 86 | ref int pdnDevInst, 87 | uint dnDevInst, 88 | int ulFlags); 89 | 90 | [DllImport("setupapi.dll")] 91 | internal static extern int CM_Get_Device_ID( 92 | int dnDevInst, 93 | StringBuilder buffer, 94 | int bufferLen, 95 | int ulFlags); 96 | 97 | [DllImport("setupapi.dll")] 98 | internal static extern int CM_Request_Device_Eject( 99 | uint dnDevInst, 100 | out PNP_VETO_TYPE pVetoType, 101 | StringBuilder pszVetoName, 102 | int ulNameLength, 103 | int ulFlags 104 | ); 105 | 106 | [DllImport("setupapi.dll", EntryPoint = "CM_Request_Device_Eject")] 107 | internal static extern int CM_Request_Device_Eject_NoUi( 108 | uint dnDevInst, 109 | IntPtr pVetoType, 110 | StringBuilder pszVetoName, 111 | int ulNameLength, 112 | int ulFlags 113 | ); 114 | 115 | // from setupapi.h 116 | internal const int DIGCF_PRESENT = (0x00000002); 117 | internal const int DIGCF_DEVICEINTERFACE = (0x00000010); 118 | 119 | internal const int SPDRP_DEVICEDESC = 0x00000000; 120 | internal const int SPDRP_CAPABILITIES = 0x0000000F; 121 | internal const int SPDRP_CLASS = 0x00000007; 122 | internal const int SPDRP_CLASSGUID = 0x00000008; 123 | internal const int SPDRP_FRIENDLYNAME = 0x0000000C; 124 | 125 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 126 | public class SP_DEVINFO_DATA 127 | { 128 | public uint cbSize; 129 | public Guid classGuid; 130 | public uint devInst; 131 | public IntPtr reserved; 132 | } 133 | 134 | [StructLayout(LayoutKind.Sequential, Pack = 2)] 135 | internal class SP_DEVICE_INTERFACE_DETAIL_DATA 136 | { 137 | internal int cbSize; 138 | internal short devicePath; 139 | } 140 | 141 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] 142 | public class SP_DEVICE_INTERFACE_DATA 143 | { 144 | public uint cbSize; 145 | public Guid InterfaceClassGuid; 146 | public uint Flags; 147 | public IntPtr Reserved; 148 | } 149 | 150 | [DllImport("setupapi.dll")] 151 | internal static extern IntPtr SetupDiGetClassDevs( 152 | ref Guid classGuid, 153 | int enumerator, 154 | IntPtr hwndParent, 155 | int flags); 156 | 157 | [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 158 | internal static extern bool SetupDiEnumDeviceInterfaces( 159 | IntPtr deviceInfoSet, 160 | SP_DEVINFO_DATA deviceInfoData, 161 | ref Guid interfaceClassGuid, 162 | int memberIndex, 163 | SP_DEVICE_INTERFACE_DATA deviceInterfaceData); 164 | 165 | [DllImport("setupapi.dll")] 166 | internal static extern bool SetupDiOpenDeviceInfo( 167 | IntPtr deviceInfoSet, 168 | string deviceInstanceId, 169 | IntPtr hwndParent, 170 | int openFlags, 171 | SP_DEVINFO_DATA deviceInfoData 172 | ); 173 | 174 | [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)] 175 | internal static extern bool SetupDiGetDeviceInterfaceDetail( 176 | IntPtr deviceInfoSet, 177 | SP_DEVICE_INTERFACE_DATA deviceInterfaceData, 178 | IntPtr deviceInterfaceDetailData, 179 | int deviceInterfaceDetailDataSize, 180 | ref int requiredSize, 181 | SP_DEVINFO_DATA deviceInfoData); 182 | 183 | [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] 184 | internal static extern bool SetupDiGetDeviceRegistryProperty( 185 | IntPtr deviceInfoSet, 186 | SP_DEVINFO_DATA deviceInfoData, 187 | int property, 188 | out int propertyRegDataType, 189 | IntPtr propertyBuffer, 190 | int propertyBufferSize, 191 | out int requiredSize 192 | ); 193 | 194 | [DllImport("setupapi.dll")] 195 | internal static extern uint SetupDiDestroyDeviceInfoList( 196 | IntPtr deviceInfoSet); 197 | 198 | 199 | private Native() 200 | { 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Volume.cs: -------------------------------------------------------------------------------- 1 | // UsbEject version 1.0 March 2006 2 | // written by Simon Mourier 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | 11 | namespace UsbEject.Library 12 | { 13 | /// 14 | /// A volume device. 15 | /// 16 | public class Volume : Device, IComparable 17 | { 18 | private string _volumeName; 19 | private string _logicalDrive; 20 | private int[] _diskNumbers; 21 | private List _disks; 22 | private List _removableDevices; 23 | 24 | internal Volume(DeviceClass deviceClass, Native.SP_DEVINFO_DATA deviceInfoData, string path, int index) 25 | :base(deviceClass, deviceInfoData, path, index) 26 | { 27 | } 28 | 29 | /// 30 | /// Gets the volume's name. 31 | /// 32 | public string VolumeName 33 | { 34 | get 35 | { 36 | if (_volumeName == null) 37 | { 38 | StringBuilder sb = new StringBuilder(1024); 39 | if (!Native.GetVolumeNameForVolumeMountPoint(Path + "\\", sb, sb.Capacity)) 40 | { 41 | // throw new Win32Exception(Marshal.GetLastWin32Error()); 42 | 43 | } 44 | 45 | if (sb.Length > 0) 46 | { 47 | _volumeName = sb.ToString(); 48 | } 49 | } 50 | return _volumeName; 51 | } 52 | } 53 | 54 | /// 55 | /// Gets the volume's logical drive in the form [letter]:\ 56 | /// 57 | public string LogicalDrive 58 | { 59 | get 60 | { 61 | if ((_logicalDrive == null) && (VolumeName != null)) 62 | { 63 | ((VolumeDeviceClass)DeviceClass)._logicalDrives.TryGetValue(VolumeName, out _logicalDrive); 64 | } 65 | return _logicalDrive; 66 | } 67 | } 68 | 69 | /// 70 | /// Gets a value indicating whether this volume is a based on USB devices. 71 | /// 72 | public override bool IsUsb 73 | { 74 | get 75 | { 76 | if (Disks != null) 77 | { 78 | foreach (Device disk in Disks) 79 | { 80 | if (disk.IsUsb) 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | } 87 | 88 | /// 89 | /// Gets a list of underlying disks for this volume. 90 | /// 91 | public List Disks 92 | { 93 | get 94 | { 95 | if (_disks == null) 96 | { 97 | _disks = new List(); 98 | 99 | if (DiskNumbers != null) 100 | { 101 | DiskDeviceClass disks = new DiskDeviceClass(); 102 | foreach (int index in DiskNumbers) 103 | { 104 | foreach (Device disk in disks.Devices) 105 | { 106 | if (disk.DiskNumber == index) 107 | { 108 | _disks.Add(disk); 109 | } 110 | } 111 | } 112 | } 113 | } 114 | return _disks; 115 | } 116 | } 117 | 118 | public int[] DiskNumbers 119 | { 120 | get 121 | { 122 | if (_diskNumbers == null) 123 | { 124 | List numbers = new List(); 125 | if (LogicalDrive != null) 126 | { 127 | Console.WriteLine("Finding disk extents for volume: " + LogicalDrive); 128 | IntPtr hFile = Native.CreateFile(@"\\.\" + LogicalDrive, 0, Native.FILE_SHARE_READ | Native.FILE_SHARE_WRITE, IntPtr.Zero, Native.OPEN_EXISTING, 0, IntPtr.Zero); 129 | if (hFile.ToInt32() == Native.INVALID_HANDLE_VALUE) 130 | throw new Win32Exception(Marshal.GetLastWin32Error()); 131 | 132 | int size = 0x400; // some big size 133 | IntPtr buffer = Marshal.AllocHGlobal(size); 134 | int bytesReturned = 0; 135 | try 136 | { 137 | if (!Native.DeviceIoControl(hFile, Native.IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, IntPtr.Zero, 0, buffer, size, out bytesReturned, IntPtr.Zero)) 138 | { 139 | // do nothing here on purpose 140 | } 141 | } 142 | finally 143 | { 144 | Native.CloseHandle(hFile); 145 | } 146 | 147 | if (bytesReturned > 0) 148 | { 149 | int numberOfDiskExtents = (int)Marshal.PtrToStructure(buffer, typeof(int)); 150 | for (int i = 0; i < numberOfDiskExtents; i++) 151 | { 152 | IntPtr extentPtr = new IntPtr(buffer.ToInt32() + Marshal.SizeOf(typeof(long)) + i * Marshal.SizeOf(typeof(Native.DISK_EXTENT))); 153 | Native.DISK_EXTENT extent = (Native.DISK_EXTENT)Marshal.PtrToStructure(extentPtr, typeof(Native.DISK_EXTENT)); 154 | numbers.Add(extent.DiskNumber); 155 | } 156 | } 157 | Marshal.FreeHGlobal(buffer); 158 | } 159 | 160 | _diskNumbers = new int[numbers.Count]; 161 | numbers.CopyTo(_diskNumbers); 162 | } 163 | return _diskNumbers; 164 | } 165 | } 166 | 167 | /// 168 | /// Gets a list of removable devices for this volume. 169 | /// 170 | public override List RemovableDevices 171 | { 172 | get 173 | { 174 | if (_removableDevices == null) 175 | { 176 | _removableDevices = new List(); 177 | if (Disks == null) 178 | { 179 | _removableDevices = base.RemovableDevices; 180 | } 181 | else 182 | { 183 | foreach (Device disk in Disks) 184 | { 185 | foreach (Device device in disk.RemovableDevices) 186 | { 187 | _removableDevices.Add(device); 188 | } 189 | } 190 | } 191 | } 192 | return _removableDevices; 193 | } 194 | } 195 | 196 | /// 197 | /// Compares the current instance with another object of the same type. 198 | /// 199 | /// An object to compare with this instance. 200 | /// A 32-bit signed integer that indicates the relative order of the comparands. 201 | public override int CompareTo(object obj) 202 | { 203 | Volume device = obj as Volume; 204 | if (device == null) 205 | throw new ArgumentException(); 206 | 207 | if (LogicalDrive == null) 208 | return 1; 209 | 210 | if (device.LogicalDrive == null) 211 | return -1; 212 | 213 | return LogicalDrive.CompareTo(device.LogicalDrive); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Device.cs: -------------------------------------------------------------------------------- 1 | // UsbEject version 1.0 March 2006 2 | // written by Simon Mourier 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Diagnostics; 8 | using System.Text; 9 | 10 | namespace UsbEject.Library 11 | { 12 | /// 13 | /// A generic base class for physical devices. 14 | /// 15 | [TypeConverter(typeof(ExpandableObjectConverter))] 16 | public class Device : IComparable 17 | { 18 | private string _path; 19 | private DeviceClass _deviceClass; 20 | private string _description; 21 | private string _class; 22 | private string _classGuid; 23 | private int _disknum; 24 | private Device _parent; 25 | private int _index; 26 | private DeviceCapabilities _capabilities = DeviceCapabilities.Unknown; 27 | private List _removableDevices; 28 | private string _friendlyName; 29 | private Native.SP_DEVINFO_DATA _deviceInfoData; 30 | 31 | internal Device(DeviceClass deviceClass, Native.SP_DEVINFO_DATA deviceInfoData, string path, int index, int disknum = -1) 32 | { 33 | if (deviceClass == null) 34 | throw new ArgumentNullException("deviceClass"); 35 | 36 | if (deviceInfoData == null) 37 | throw new ArgumentNullException("deviceInfoData"); 38 | 39 | _deviceClass = deviceClass; 40 | _path = path; // may be null 41 | _deviceInfoData = deviceInfoData; 42 | _index = index; 43 | _disknum = disknum; 44 | } 45 | 46 | /// 47 | /// Gets the device's index. 48 | /// 49 | public int Index 50 | { 51 | get 52 | { 53 | return _index; 54 | } 55 | } 56 | 57 | /// 58 | /// Gets the device's class instance. 59 | /// 60 | [Browsable(false)] 61 | public DeviceClass DeviceClass 62 | { 63 | get 64 | { 65 | return _deviceClass; 66 | } 67 | } 68 | 69 | /// 70 | /// Gets the device's path. 71 | /// 72 | public string Path 73 | { 74 | get 75 | { 76 | if (_path == null) 77 | { 78 | } 79 | return _path; 80 | } 81 | } 82 | 83 | public int DiskNumber 84 | { 85 | get 86 | { 87 | return _disknum; 88 | } 89 | } 90 | 91 | /// 92 | /// Gets the device's instance handle. 93 | /// 94 | public uint InstanceHandle 95 | { 96 | get 97 | { 98 | return _deviceInfoData.devInst; 99 | } 100 | } 101 | 102 | /// 103 | /// Gets the device's class name. 104 | /// 105 | public string Class 106 | { 107 | get 108 | { 109 | if (_class == null) 110 | { 111 | _class = _deviceClass.GetProperty(_deviceInfoData, Native.SPDRP_CLASS, null); 112 | } 113 | return _class; 114 | } 115 | } 116 | 117 | /// 118 | /// Gets the device's class Guid as a string. 119 | /// 120 | public string ClassGuid 121 | { 122 | get 123 | { 124 | if (_classGuid == null) 125 | { 126 | _classGuid = _deviceClass.GetProperty(_deviceInfoData, Native.SPDRP_CLASSGUID, null); 127 | } 128 | return _classGuid; 129 | } 130 | } 131 | 132 | /// 133 | /// Gets the device's description. 134 | /// 135 | public string Description 136 | { 137 | get 138 | { 139 | if (_description == null) 140 | { 141 | _description = _deviceClass.GetProperty(_deviceInfoData, Native.SPDRP_DEVICEDESC, null); 142 | } 143 | return _description; 144 | } 145 | } 146 | 147 | /// 148 | /// Gets the device's friendly name. 149 | /// 150 | public string FriendlyName 151 | { 152 | get 153 | { 154 | if (_friendlyName == null) 155 | { 156 | _friendlyName = _deviceClass.GetProperty(_deviceInfoData, Native.SPDRP_FRIENDLYNAME, null); 157 | } 158 | return _friendlyName; 159 | } 160 | } 161 | 162 | /// 163 | /// Gets the device's capabilities. 164 | /// 165 | public DeviceCapabilities Capabilities 166 | { 167 | get 168 | { 169 | if (_capabilities == DeviceCapabilities.Unknown) 170 | { 171 | _capabilities = (DeviceCapabilities)_deviceClass.GetProperty(_deviceInfoData, Native.SPDRP_CAPABILITIES, 0); 172 | } 173 | return _capabilities; 174 | } 175 | } 176 | 177 | /// 178 | /// Gets a value indicating whether this device is a USB device. 179 | /// 180 | public virtual bool IsUsb 181 | { 182 | get 183 | { 184 | if (Class == "USB") 185 | return true; 186 | 187 | if (Parent == null) 188 | return false; 189 | 190 | return Parent.IsUsb; 191 | } 192 | } 193 | 194 | /// 195 | /// Gets the device's parent device or null if this device has not parent. 196 | /// 197 | public Device Parent 198 | { 199 | get 200 | { 201 | if (_parent == null) 202 | { 203 | int parentDevInst = 0; 204 | int hr = Native.CM_Get_Parent(ref parentDevInst, _deviceInfoData.devInst, 0); 205 | if (hr == 0) 206 | { 207 | _parent = new Device(_deviceClass, _deviceClass.GetInfo(parentDevInst), null, -1); 208 | } 209 | } 210 | return _parent; 211 | } 212 | } 213 | 214 | /// 215 | /// Gets this device's list of removable devices. 216 | /// Removable devices are parent devices that can be removed. 217 | /// 218 | public virtual List RemovableDevices 219 | { 220 | get 221 | { 222 | if (_removableDevices == null) 223 | { 224 | _removableDevices = new List(); 225 | 226 | if ((Capabilities & DeviceCapabilities.Removable) != 0) 227 | { 228 | _removableDevices.Add(this); 229 | } 230 | else 231 | { 232 | if (Parent != null) 233 | { 234 | foreach (Device device in Parent.RemovableDevices) 235 | { 236 | _removableDevices.Add(device); 237 | } 238 | } 239 | } 240 | } 241 | return _removableDevices; 242 | } 243 | } 244 | 245 | /// 246 | /// Ejects the device. 247 | /// 248 | /// Pass true to allow the Windows shell to display any related UI element, false otherwise. 249 | /// null if no error occured, otherwise a contextual text. 250 | public string Eject(bool allowUI) 251 | { 252 | foreach (Device device in RemovableDevices) 253 | { 254 | if (allowUI) 255 | { 256 | Native.CM_Request_Device_Eject_NoUi(device.InstanceHandle, IntPtr.Zero, null, 0, 0); 257 | // don't handle errors, there should be a UI for this 258 | } 259 | else 260 | { 261 | StringBuilder sb = new StringBuilder(1024); 262 | 263 | Native.PNP_VETO_TYPE veto; 264 | int hr = Native.CM_Request_Device_Eject(device.InstanceHandle, out veto, sb, sb.Capacity, 0); 265 | if (hr != 0) 266 | throw new Win32Exception(hr); 267 | 268 | if (veto != Native.PNP_VETO_TYPE.Ok) 269 | return veto.ToString(); 270 | } 271 | 272 | } 273 | return null; 274 | } 275 | 276 | /// 277 | /// Compares the current instance with another object of the same type. 278 | /// 279 | /// An object to compare with this instance. 280 | /// A 32-bit signed integer that indicates the relative order of the comparands. 281 | public virtual int CompareTo(object obj) 282 | { 283 | Device device = obj as Device; 284 | if (device == null) 285 | throw new ArgumentException(); 286 | 287 | return Index.CompareTo(device.Index); 288 | } 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /DeviceClass.cs: -------------------------------------------------------------------------------- 1 | // UsbEject version 1.0 March 2006 2 | // written by Simon Mourier 3 | 4 | using System; 5 | using System.ComponentModel; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Runtime.InteropServices; 9 | using System.Text; 10 | 11 | namespace UsbEject.Library 12 | { 13 | /// 14 | /// A generic base class for physical device classes. 15 | /// 16 | public abstract class DeviceClass : IDisposable 17 | { 18 | private IntPtr _deviceInfoSet; 19 | private Guid _classGuid; 20 | private List _devices; 21 | 22 | protected DeviceClass(Guid classGuid) 23 | :this(classGuid, IntPtr.Zero) 24 | { 25 | } 26 | 27 | internal virtual Device CreateDevice(DeviceClass deviceClass, Native.SP_DEVINFO_DATA deviceInfoData, string path, int index, int disknum = -1) 28 | { 29 | return new Device(deviceClass, deviceInfoData, path, index, disknum); 30 | } 31 | 32 | /// 33 | /// Initializes a new instance of the DeviceClass class. 34 | /// 35 | /// A device class Guid. 36 | /// The handle of the top-level window to be used for any user interface or IntPtr.Zero for no handle. 37 | protected DeviceClass(Guid classGuid, IntPtr hwndParent) 38 | { 39 | _classGuid = classGuid; 40 | 41 | _deviceInfoSet = Native.SetupDiGetClassDevs(ref _classGuid, 0, hwndParent, Native.DIGCF_DEVICEINTERFACE | Native.DIGCF_PRESENT); 42 | if (_deviceInfoSet == (IntPtr)Native.INVALID_HANDLE_VALUE) 43 | throw new Win32Exception(Marshal.GetLastWin32Error()); 44 | } 45 | 46 | /// 47 | /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 48 | /// 49 | public void Dispose() 50 | { 51 | if (_deviceInfoSet != IntPtr.Zero) 52 | { 53 | Native.SetupDiDestroyDeviceInfoList(_deviceInfoSet); 54 | _deviceInfoSet = IntPtr.Zero; 55 | } 56 | } 57 | 58 | /// 59 | /// Gets the device class's guid. 60 | /// 61 | public Guid ClassGuid 62 | { 63 | get 64 | { 65 | return _classGuid; 66 | } 67 | } 68 | 69 | /// 70 | /// Gets the list of devices of this device class. 71 | /// 72 | /// 73 | /// The devices. 74 | /// 75 | /// 76 | /// 77 | public List Devices 78 | { 79 | get 80 | { 81 | if (_devices == null) 82 | { 83 | _devices = new List(); 84 | int index = 0; 85 | while (true) 86 | { 87 | Native.SP_DEVICE_INTERFACE_DATA interfaceData = new Native.SP_DEVICE_INTERFACE_DATA(); 88 | interfaceData.cbSize = (uint)Marshal.SizeOf(interfaceData); 89 | if (!Native.SetupDiEnumDeviceInterfaces(_deviceInfoSet, null , ref _classGuid, index, interfaceData)) 90 | { 91 | int error = Marshal.GetLastWin32Error(); 92 | if (error != Native.ERROR_NO_MORE_ITEMS) 93 | throw new Win32Exception(error); 94 | break; 95 | } 96 | 97 | Native.SP_DEVINFO_DATA devData = new Native.SP_DEVINFO_DATA(); 98 | devData.cbSize = (uint)Marshal.SizeOf(devData); 99 | int size = 0; 100 | if (!Native.SetupDiGetDeviceInterfaceDetail(_deviceInfoSet, interfaceData, IntPtr.Zero, 0,ref size, devData)) 101 | { 102 | int error = Marshal.GetLastWin32Error(); 103 | if (error != Native.ERROR_INSUFFICIENT_BUFFER) 104 | throw new Win32Exception(error); 105 | } 106 | 107 | IntPtr buffer = Marshal.AllocHGlobal(size); 108 | Native.SP_DEVICE_INTERFACE_DETAIL_DATA detailData = new Native.SP_DEVICE_INTERFACE_DETAIL_DATA(); 109 | detailData.cbSize = Marshal.SizeOf(detailData); 110 | 111 | Marshal.WriteInt32(buffer, IntPtr.Size); 112 | //Marshal.StructureToPtr(detailData, buffer, false); 113 | 114 | if (!Native.SetupDiGetDeviceInterfaceDetail(_deviceInfoSet, interfaceData, buffer ,size, ref size, devData)) 115 | { 116 | Marshal.FreeHGlobal(buffer); 117 | throw new Win32Exception(Marshal.GetLastWin32Error()); 118 | } 119 | 120 | var strPtr = new IntPtr(buffer.ToInt64() + 4); 121 | string devicePath = Marshal.PtrToStringAuto(strPtr); 122 | //IntPtr pDevicePath = (IntPtr)((int)buffer + Marshal.SizeOf(typeof(int))); 123 | //string devicePath = Marshal.PtrToStringAuto(pDevicePath); 124 | Marshal.FreeHGlobal(buffer); 125 | 126 | if (_classGuid.Equals(new Guid(Native.GUID_DEVINTERFACE_DISK))) 127 | { 128 | // Find disks 129 | IntPtr hFile = Native.CreateFile(devicePath, 0, Native.FILE_SHARE_READ | Native.FILE_SHARE_WRITE, IntPtr.Zero, Native.OPEN_EXISTING, 0, IntPtr.Zero); 130 | if (hFile.ToInt32() == Native.INVALID_HANDLE_VALUE) 131 | throw new Win32Exception(Marshal.GetLastWin32Error()); 132 | 133 | int bytesReturned = 0; 134 | int numBufSize = 0x400; // some big size 135 | IntPtr numBuffer = Marshal.AllocHGlobal(numBufSize); 136 | Native.STORAGE_DEVICE_NUMBER disknum; 137 | 138 | try 139 | { 140 | if (!Native.DeviceIoControl(hFile, Native.IOCTL_STORAGE_GET_DEVICE_NUMBER, IntPtr.Zero, 0, numBuffer, numBufSize, out bytesReturned, IntPtr.Zero)) 141 | { 142 | Console.WriteLine("IOCTL failed."); 143 | } 144 | } 145 | catch (Exception ex) 146 | { 147 | Console.WriteLine("Exception calling ioctl: " + ex); 148 | } 149 | finally 150 | { 151 | Native.CloseHandle(hFile); 152 | } 153 | 154 | if (bytesReturned > 0) 155 | disknum = (Native.STORAGE_DEVICE_NUMBER)Marshal.PtrToStructure(numBuffer, typeof(Native.STORAGE_DEVICE_NUMBER)); 156 | else 157 | disknum = new Native.STORAGE_DEVICE_NUMBER() { DeviceNumber = -1, DeviceType = -1, PartitionNumber = -1 }; 158 | 159 | Device device = CreateDevice(this, devData, devicePath, index, disknum.DeviceNumber); 160 | _devices.Add(device); 161 | 162 | Marshal.FreeHGlobal(hFile); 163 | } 164 | else 165 | { 166 | Device device = CreateDevice(this, devData, devicePath, index); 167 | _devices.Add(device); 168 | } 169 | 170 | index++; 171 | } 172 | _devices.Sort(); 173 | } 174 | return _devices; 175 | } 176 | } 177 | 178 | internal Native.SP_DEVINFO_DATA GetInfo(int dnDevInst) 179 | { 180 | StringBuilder sb = new StringBuilder(1024); 181 | int hr = Native.CM_Get_Device_ID(dnDevInst, sb, sb.Capacity, 0); 182 | if (hr != 0) 183 | throw new Win32Exception(hr); 184 | 185 | Native.SP_DEVINFO_DATA devData = new Native.SP_DEVINFO_DATA(); 186 | devData.cbSize = (uint)Marshal.SizeOf(devData); 187 | if (!Native.SetupDiOpenDeviceInfo(_deviceInfoSet, sb.ToString(), IntPtr.Zero, 0, devData)) 188 | throw new Win32Exception(Marshal.GetLastWin32Error()); 189 | 190 | return devData; 191 | } 192 | 193 | internal string GetProperty(Native.SP_DEVINFO_DATA devData, int property, string defaultValue) 194 | { 195 | if (devData == null) 196 | throw new ArgumentNullException("devData"); 197 | 198 | int propertyRegDataType = 0; 199 | int requiredSize; 200 | int propertyBufferSize = 1024; 201 | 202 | IntPtr propertyBuffer = Marshal.AllocHGlobal(propertyBufferSize); 203 | if (!Native.SetupDiGetDeviceRegistryProperty(_deviceInfoSet, 204 | devData, 205 | property, 206 | out propertyRegDataType, 207 | propertyBuffer, 208 | propertyBufferSize, 209 | out requiredSize)) 210 | { 211 | Marshal.FreeHGlobal(propertyBuffer); 212 | int error = Marshal.GetLastWin32Error(); 213 | if (error != Native.ERROR_INVALID_DATA) 214 | throw new Win32Exception(error); 215 | return defaultValue; 216 | } 217 | 218 | string value = Marshal.PtrToStringAuto(propertyBuffer); 219 | Marshal.FreeHGlobal(propertyBuffer); 220 | return value; 221 | } 222 | 223 | internal int GetProperty(Native.SP_DEVINFO_DATA devData, int property, int defaultValue) 224 | { 225 | if (devData == null) 226 | throw new ArgumentNullException("devData"); 227 | 228 | int propertyRegDataType = 0; 229 | int requiredSize; 230 | int propertyBufferSize = Marshal.SizeOf(typeof(int)); 231 | 232 | IntPtr propertyBuffer = Marshal.AllocHGlobal(propertyBufferSize); 233 | if (!Native.SetupDiGetDeviceRegistryProperty(_deviceInfoSet, 234 | devData, 235 | property, 236 | out propertyRegDataType, 237 | propertyBuffer, 238 | propertyBufferSize, 239 | out requiredSize)) 240 | { 241 | Marshal.FreeHGlobal(propertyBuffer); 242 | int error = Marshal.GetLastWin32Error(); 243 | if (error != Native.ERROR_INVALID_DATA) 244 | throw new Win32Exception(error); 245 | return defaultValue; 246 | } 247 | 248 | int value = (int)Marshal.PtrToStructure(propertyBuffer, typeof(int)); 249 | Marshal.FreeHGlobal(propertyBuffer); 250 | return value; 251 | } 252 | 253 | internal Guid GetProperty(Native.SP_DEVINFO_DATA devData, int property, Guid defaultValue) 254 | { 255 | if (devData == null) 256 | throw new ArgumentNullException("devData"); 257 | 258 | int propertyRegDataType = 0; 259 | int requiredSize; 260 | int propertyBufferSize = Marshal.SizeOf(typeof(Guid)); 261 | 262 | IntPtr propertyBuffer = Marshal.AllocHGlobal(propertyBufferSize); 263 | if (!Native.SetupDiGetDeviceRegistryProperty(_deviceInfoSet, 264 | devData, 265 | property, 266 | out propertyRegDataType, 267 | propertyBuffer, 268 | propertyBufferSize, 269 | out requiredSize)) 270 | { 271 | Marshal.FreeHGlobal(propertyBuffer); 272 | int error = Marshal.GetLastWin32Error(); 273 | if (error != Native.ERROR_INVALID_DATA) 274 | throw new Win32Exception(error); 275 | return defaultValue; 276 | } 277 | 278 | Guid value = (Guid)Marshal.PtrToStructure(propertyBuffer, typeof(Guid)); 279 | Marshal.FreeHGlobal(propertyBuffer); 280 | return value; 281 | } 282 | 283 | } 284 | } 285 | --------------------------------------------------------------------------------