C#实现SCSI磁盘安全弹出失败的技术解决方案问询
SCSI大容量驱动器安全弹出问题解决方案
问题背景
用C#通过System.Runtime.InteropServices调用C++接口实现安全弹出功能,USB存储设备正常,但SCSI大容量驱动器失效:
- 仅能通过系统内置的“安全删除硬件并弹出媒体”工具弹出,无对应公开API或参数可用
- 调用
DeviceIOControl传入IOCTL_STORAGE_EJECT_MEDIA、IOCTL_STORAGE_MEDIA_REMOVAL参数失败 - SCSI设备默认策略为“更好的性能”,需改为“快速删除”才能在Win10/11专用应用外弹出,但无法通过设备设置修改
解决方案
1. 编程修改设备策略为“快速删除”
通过WMI直接修改设备的DevicePolicy属性:
- 引用
System.Management命名空间 - 从盘符映射到对应的物理磁盘、分区、卷
- 调用
SetDevicePolicy方法将策略设为2(对应快速删除模式)
2. 调整SCSI设备的弹出逻辑
原代码针对卷操作,SCSI设备需要直接操作物理磁盘句柄:
- 使用
\\.\PHYSICALDRIVEx格式的设备路径,而非\\.\X:的卷路径 - 打开设备时仅需
GENERIC_READ权限,避免权限不足问题 - 先禁用介质移除锁定,再执行弹出操作
3. 增加错误处理
调用DeviceIoControl后通过Marshal.GetLastWin32Error()获取具体错误码,可针对性排查权限、设备占用等问题
修改后的完整代码
using System; using System.Runtime.InteropServices; using System.Management; namespace USBRemoval { internal class USBMagic { [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern IntPtr CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile ); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] private static extern bool DeviceIoControl( IntPtr hDevice, uint dwIoControlCode, IntPtr lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped ); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSet = CharSet.Auto)] private static extern bool DeviceIoControl( IntPtr hDevice, uint dwIoControlCode, byte[] lpInBuffer, uint nInBufferSize, IntPtr lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped ); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr hObject); const uint GENERIC_READ = 0x80000000; const uint FILE_SHARE_READ = 0x1; const uint FILE_SHARE_WRITE = 0x2; const uint OPEN_EXISTING = 0x3; const uint FSCTL_LOCK_VOLUME = 0x00090018; const uint FSCTL_DISMOUNT_VOLUME = 0x00090020; const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808; const uint IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804; // 获取盘符对应的物理磁盘路径 public static string GetPhysicalDrivePath(string driveLetter) { string drive = driveLetter.TrimEnd('\\', '/'); using (var partitionSearcher = new ManagementObjectSearcher( $"ASSOCIATORS OF {{Win32_LogicalDisk.DeviceID='{drive}'}} WHERE AssocClass=Win32_LogicalDiskToPartition")) { foreach (var partition in partitionSearcher.Get()) { using (var diskSearcher = new ManagementObjectSearcher( $"ASSOCIATORS OF {{Win32_DiskPartition.DeviceID='{partition["DeviceID"]}'}} WHERE AssocClass=Win32_DiskDriveToDiskPartition")) { foreach (var disk in diskSearcher.Get()) { return $"\\\\.\\{disk["DeviceID"].ToString().Split('#')[0]}"; } } } } return null; } // 设置设备为快速删除策略 public static bool SetQuickRemovalPolicy(string driveLetter) { string physicalDrive = GetPhysicalDrivePath(driveLetter); if (string.IsNullOrEmpty(physicalDrive)) return false; using (var disk = new ManagementObject($"Win32_DiskDrive.DeviceID='{physicalDrive.Replace("\\\\", "\\")}'")) { foreach (var partition in disk.GetRelated("Win32_DiskPartition")) { foreach (var logicalDisk in partition.GetRelated("Win32_LogicalDisk")) { using (var volume = new ManagementObject($"Win32_Volume.DeviceID='{logicalDisk["DeviceID"]}'")) { volume.InvokeMethod("SetDevicePolicy", new object[] { 2 }); // 2代表快速删除模式 return true; } } } } return false; } // 弹出SCSI物理磁盘设备 public static bool EjectScsiDevice(string driveLetter) { string physicalDrivePath = GetPhysicalDrivePath(driveLetter); if (string.IsNullOrEmpty(physicalDrivePath)) return false; IntPtr handle = CreateFile( physicalDrivePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); if (handle == IntPtr.Zero || handle == (IntPtr)(-1)) { return false; } bool result = false; uint bytesReturned; // 禁用介质移除锁定 byte[] disableLockBuffer = new byte[1]; disableLockBuffer[0] = 0; if (DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, disableLockBuffer, 1, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero)) { // 执行弹出命令 if (DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero)) { result = true; } } CloseHandle(handle); return result; } // 原USB卷弹出方法(保留兼容普通USB设备) public static IntPtr USBEject(string driveLetter) { string filename = @"\\.\" + driveLetter[0] + ":"; return CreateFile(filename, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); } public static bool EjectVolume(IntPtr handle) { bool result = false; uint bytesReturned; if (DeviceIoControl(handle, FSCTL_LOCK_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero) && DeviceIoControl(handle, FSCTL_DISMOUNT_VOLUME, IntPtr.Zero, 0, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero)) { byte[] disableLockBuffer = new byte[1]; disableLockBuffer[0] = 0; DeviceIoControl(handle, IOCTL_STORAGE_MEDIA_REMOVAL, disableLockBuffer, 1, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); result = DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0, IntPtr.Zero, 0, out bytesReturned, IntPtr.Zero); } CloseHandle(handle); return result; } } }
使用示例
// 先设置快速删除策略 bool policySet = USBMagic.SetQuickRemovalPolicy("F:"); if (policySet) { // 弹出SCSI设备 bool ejectSuccess = USBMagic.EjectScsiDevice("F:"); // 普通USB设备使用原方法 // IntPtr volumeHandle = USBMagic.USBEject("F:"); // bool ejectSuccess = USBMagic.EjectVolume(volumeHandle); }
内容的提问来源于stack exchange,提问作者M.Mitelman
相关产品推荐
相关产品推荐

