You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 07:40:54