使用WinApi可靠地知道卷是否可移动

时间:2017-07-11 22:48:42

标签: c++ windows winapi usb ntfs

我正在使用

DeviceIoControl(dev, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &driveInfo, sizeof(driveInfo), &dwResult, NULL)

检查driveInfo.MediaTypeRemovableMedia还是FixedMedia,但似乎我的所有卷都被“看作”固定:

\\.\C:      NTFS Fixed, this is ok - internal hard drive
\\.\D:      NTFS Fixed, this is ok - internal hard drive
\\.\E:      NTFS Fixed, this is ok - internal hard drive
\\.\F:      NTFS Fixed, this is NOT ok, this is a USB external 2.5" hard drive

因此我的问题是:

是否有可靠的方法来了解音量是否可以移除?

应该有一种方法,因为Windows 区分可移动的(他们在时钟附近有一个“安全地移除硬盘并弹出媒体”的图标)。

3 个答案:

答案 0 :(得分:2)

问题在于你提出了错误的问题。当他们使用这个术语时,"可移动"表示介质和介质的驱动器是分开的(如软盘驱动器或CD-ROM)。任何不允许单个驱动器在不同时间保持不同媒体的东西都是“固定的”#34;驱动。

根据您的需求,我相信您希望将SetupDiGetDeviceRegistryPropertySPDRP_CAPABILITIES标志一起使用。这将告诉您驱动器是否可以弹出其媒体(几乎相当于"可移动"您已经找到),还有设备本身是否可移动( CM_DEVCAP_REMOVABLE)。

不幸的是,微软的SetupDi*函数使用起来很麻烦(就像我所知道的那样好)。他们有一些demo code使用正确的函数并检索相当类似的信息,但代码也有点难看,所以可能需要一些研究和实验来修改它以获得你想要的东西。

答案 1 :(得分:1)

最简单可靠的方法是将IOCTL_STORAGE_QUERY_PROPERTYStorageDeviceProperty一起使用。返回时我们得到了STORAGE_DEVICE_DESCRIPTOR - 并寻找

  

RemovableMedia

     

指示 TRUE 时,设备的媒体(如果有)是可移除的。   如果设备没有媒体,则应忽略此成员。什么时候    FALSE 设备的媒体无法删除。

因此我们需要具有任何访问权限的磁盘句柄(因为IOCTL_STORAGE_QUERY_PROPERTY定义为CTL_CODE(IOCTL_STORAGE_BASE, 0x0500, METHOD_BUFFERED, FILE_ANY_ACCESS)(在每个IOCtl编码访问中(读,写,两者或任何),在本例中为FILE_ANY_ACCESS。使用此句柄查询可以看起来像下一个

ULONG IsRemovable(HANDLE hDisk, BOOLEAN& RemovableMedia)
{
    STORAGE_PROPERTY_QUERY spq = { StorageDeviceProperty, PropertyStandardQuery }; 

    STORAGE_DEVICE_DESCRIPTOR sdd;

    ULONG rcb;
    if (DeviceIoControl(hDisk, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(spq), &sdd, sizeof(sdd), &rcb, 0))
    {
        RemovableMedia = sdd.RemovableMedia;
        return NOERROR;
    }

    return GetLastError();
}

用于枚举我们可以使用的所有磁盘驱动器,例如下一个代码:

void EnumDisks()
{
    ULONG len;

    if (!CM_Get_Device_Interface_List_SizeW(&len, const_cast<GUID*>(&GUID_DEVINTERFACE_DISK), 0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
    {
        PWSTR buf = (PWSTR)alloca(len << 1);
        if (!CM_Get_Device_Interface_ListW(const_cast<GUID*>(&GUID_DEVINTERFACE_DISK), 0, buf, len, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
        {
            while (*buf)
            {
                HANDLE hDisk = CreateFile(buf, 0, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

                if (hDisk != INVALID_HANDLE_VALUE)
                {
                    BOOLEAN RemovableMedia;
                    if (!IsRemovable(hDisk, RemovableMedia))
                    {
                        DbgPrint("%u %S\n", RemovableMedia, buf);
                    }
                    CloseHandle(hDisk);
                }
                buf += wcslen(buf) + 1;
            }
        }
    }
}

但是对于测试,您可以打开磁盘作为L"\\\\?\\X:",例如

答案 2 :(得分:0)

杰里·科芬(Jerry Coffin)几乎是正确的。 但是,您必须改为检查SPDRP_REMOVAL_POLICY属性。 到达那里很麻烦。这是一个C#实现。它返回PNPDeviceID列表或设备路径。然后可以将其与WMI数据或PowerShell Get-Disk的结果相关。

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

namespace checkIfDriveIsRemovable
{

    class Program
    {
        // some relevant sources:
        // https://www.pinvoke.net/default.aspx/setupapi.setupdigetclassdevs
        // https://www.pinvoke.net/default.aspx/setupapi.setupdigetdeviceregistryproperty
        // https://stackoverflow.com/questions/15000196/reading-device-managers-property-fields-in-windows-7-8
        // https://stackoverflow.com/questions/14621211/determine-if-drive-is-removable-flash-or-hdd-knowing-only-the-drive-letter
        private static string GUID_DEVINTERFACE_DISK = "53F56307-B6BF-11D0-94F2-00A0C91EFB8B";

        private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr( -1 );

        const int BUFFER_SIZE = 1024;

        enum RemovalPolicy : uint
        {
            CM_REMOVAL_POLICY_EXPECT_NO_REMOVAL            = 1,
            CM_REMOVAL_POLICY_EXPECT_ORDERLY_REMOVAL       = 2,
            CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL      = 3
        }

        enum SetupDiGetDeviceRegistryPropertyEnum : uint
        {
            SPDRP_DEVICEDESC = 0x00000000, // DeviceDesc (R/W)
            SPDRP_HARDWAREID = 0x00000001, // HardwareID (R/W)
            SPDRP_COMPATIBLEIDS = 0x00000002, // CompatibleIDs (R/W)
            SPDRP_UNUSED0 = 0x00000003, // unused
            SPDRP_SERVICE = 0x00000004, // Service (R/W)
            SPDRP_UNUSED1 = 0x00000005, // unused
            SPDRP_UNUSED2 = 0x00000006, // unused
            SPDRP_CLASS = 0x00000007, // Class (R--tied to ClassGUID)
            SPDRP_CLASSGUID = 0x00000008, // ClassGUID (R/W)
            SPDRP_DRIVER = 0x00000009, // Driver (R/W)
            SPDRP_CONFIGFLAGS = 0x0000000A, // ConfigFlags (R/W)
            SPDRP_MFG = 0x0000000B, // Mfg (R/W)
            SPDRP_FRIENDLYNAME = 0x0000000C, // FriendlyName (R/W)
            SPDRP_LOCATION_INFORMATION = 0x0000000D, // LocationInformation (R/W)
            SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E, // PhysicalDeviceObjectName (R)
            SPDRP_CAPABILITIES = 0x0000000F, // Capabilities (R)
            SPDRP_UI_NUMBER = 0x00000010, // UiNumber (R)
            SPDRP_UPPERFILTERS = 0x00000011, // UpperFilters (R/W)
            SPDRP_LOWERFILTERS = 0x00000012, // LowerFilters (R/W)
            SPDRP_BUSTYPEGUID = 0x00000013, // BusTypeGUID (R)
            SPDRP_LEGACYBUSTYPE = 0x00000014, // LegacyBusType (R)
            SPDRP_BUSNUMBER = 0x00000015, // BusNumber (R)
            SPDRP_ENUMERATOR_NAME = 0x00000016, // Enumerator Name (R)
            SPDRP_SECURITY = 0x00000017, // Security (R/W, binary form)
            SPDRP_SECURITY_SDS = 0x00000018, // Security (W, SDS form)
            SPDRP_DEVTYPE = 0x00000019, // Device Type (R/W)
            SPDRP_EXCLUSIVE = 0x0000001A, // Device is exclusive-access (R/W)
            SPDRP_CHARACTERISTICS = 0x0000001B, // Device Characteristics (R/W)
            SPDRP_ADDRESS = 0x0000001C, // Device Address (R)
            SPDRP_UI_NUMBER_DESC_FORMAT = 0X0000001D, // UiNumberDescFormat (R/W)
            SPDRP_DEVICE_POWER_DATA = 0x0000001E, // Device Power Data (R)
            SPDRP_REMOVAL_POLICY = 0x0000001F, // Removal Policy (R)
            SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x00000020, // Hardware Removal Policy (R)
            SPDRP_REMOVAL_POLICY_OVERRIDE = 0x00000021, // Removal Policy Override (RW)
            SPDRP_INSTALL_STATE = 0x00000022, // Device Install State (R)
            SPDRP_LOCATION_PATHS = 0x00000023, // Device Location Paths (R)
            SPDRP_BASE_CONTAINERID = 0x00000024  // Base ContainerID (R)
        }

        [Flags]
        public enum DiGetClassFlags : uint
        {
            DIGCF_DEFAULT = 0x00000001,  // only valid with DIGCF_DEVICEINTERFACE
            DIGCF_PRESENT = 0x00000002,
            DIGCF_ALLCLASSES = 0x00000004,
            DIGCF_PROFILE = 0x00000008,
            DIGCF_DEVICEINTERFACE = 0x00000010,
        }

        public enum RegType : uint
        {
            REG_BINARY = 3,
            REG_DWORD = 4,
            REG_EXPAND_SZ = 2,
            REG_MULTI_SZ = 7,
            REG_SZ = 1
        }

        [StructLayout( LayoutKind.Sequential )]
        struct SP_DEVICE_INTERFACE_DATA
        {
            public Int32 cbSize;
            public Guid interfaceClassGuid;
            public Int32 flags;
            private UIntPtr reserved;
        }

        [StructLayout( LayoutKind.Sequential )]
        struct SP_DEVINFO_DATA
        {
            public UInt32 cbSize;
            public Guid ClassGuid;
            public UInt32 DevInst;
            public IntPtr Reserved;
        }

        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Auto )]
        struct SP_DEVICE_INTERFACE_DETAIL_DATA
        {
            public int cbSize;
            [MarshalAs( UnmanagedType.ByValArray, SizeConst = BUFFER_SIZE )]
            public byte[] DevicePath;
        }

        [DllImport( "setupapi.dll", CharSet = CharSet.Auto )]
        static extern IntPtr SetupDiGetClassDevs(
                                                  ref Guid ClassGuid,
                                                  IntPtr Enumerator,
                                                  IntPtr hwndParent,
                                                  uint Flags
                                                 );

        [DllImport( @"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
        static extern Boolean SetupDiEnumDeviceInterfaces( IntPtr hDevInfo, IntPtr devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData );

        [DllImport( @"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
        static extern Boolean SetupDiGetDeviceInterfaceDetail( IntPtr hDevInfo, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, UInt32 deviceInterfaceDetailDataSize, ref UInt32 requiredSize, ref SP_DEVINFO_DATA deviceInfoData );

        [DllImport( "setupapi.dll", CharSet = CharSet.Auto, SetLastError = true )]
        static extern bool SetupDiGetDeviceRegistryProperty( IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint property, out UInt32 propertyRegDataType, byte[] propertyBuffer, uint propertyBufferSize, out UInt32 requiredSize );

        [DllImport( "setupapi.dll" )]
        static extern bool SetupDiGetDeviceInstanceIdA(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, byte[] DeviceInstanceId, Int32 DeviceInstanceIdSize, out UInt32 RequiredSize);

        [DllImport( "setupapi.dll" )]
        static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

        static string getStringProp(IntPtr h, SP_DEVINFO_DATA da, SetupDiGetDeviceRegistryPropertyEnum prop)
        {
            UInt32 requiredSize;
            UInt32 regType;
            byte[] ptrBuf = new byte[BUFFER_SIZE];
            if( !SetupDiGetDeviceRegistryProperty( h, ref da, ( uint ) prop, out regType, ptrBuf, BUFFER_SIZE, out requiredSize ) )
                throw new InvalidOperationException( "Error getting string property" );

            if( regType != (uint) RegType.REG_SZ || ( requiredSize & 1 ) != 0 )
                throw new InvalidOperationException( "Property is not a REG_SZ" );

            if( requiredSize == 0 )
                return "";

            return Encoding.Unicode.GetString( ptrBuf, 0, (int) requiredSize - 2 );
        }

        static uint getDWORDProp(IntPtr h, SP_DEVINFO_DATA da, SetupDiGetDeviceRegistryPropertyEnum prop)
        {
            UInt32 requiredSize;
            UInt32 regType;
            byte[] ptrBuf = new byte[4];
            if( !SetupDiGetDeviceRegistryProperty( h, ref da, ( uint ) prop, out regType, ptrBuf, 4, out requiredSize ) )
                throw new InvalidOperationException( "Error getting DWORD property" );

            if( regType != ( uint ) RegType.REG_DWORD || requiredSize != 4 )
                throw new InvalidOperationException( "Property is not a REG_DWORD" );

            return BitConverter.ToUInt32( ptrBuf, 0 );
        }

        public static string[] getRemovableDisks( bool getPNPDeviceID = false )
        {
            List<String> result = new List<string>();
            Guid DiskGUID = new Guid( GUID_DEVINTERFACE_DISK );
            IntPtr h = SetupDiGetClassDevs( ref DiskGUID, IntPtr.Zero, IntPtr.Zero, ( uint ) ( DiGetClassFlags.DIGCF_PRESENT | DiGetClassFlags.DIGCF_DEVICEINTERFACE ) );
            if( h == INVALID_HANDLE_VALUE )
                return null;

            IntPtr x = new IntPtr(); ;
            int y = Marshal.SizeOf( x );


            try {
                for( uint i = 0; ; i++ ) {
                    SP_DEVICE_INTERFACE_DATA dia = new SP_DEVICE_INTERFACE_DATA();
                    dia.cbSize = Marshal.SizeOf( dia );
                    if( !SetupDiEnumDeviceInterfaces( h, IntPtr.Zero, ref DiskGUID, i, ref dia ) )
                        break;

                    SP_DEVINFO_DATA da = new SP_DEVINFO_DATA();
                    da.cbSize = ( uint ) Marshal.SizeOf( da );

                    // build a Device Interface Detail Data structure
                    SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
                    // I honestly don't know, why this works. The if-part can be found on the net a few times, the else part is from me
                    if( IntPtr.Size == 4 )
                        didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // trust me :)
                    else
                        didd.cbSize = 8;

                    // now we can get some more detailed information
                    uint nRequiredSize = 0;
                    uint nBytes = BUFFER_SIZE;
                    if( SetupDiGetDeviceInterfaceDetail( h, ref dia, ref didd, nBytes, ref nRequiredSize, ref da ) ) {

                        string devicePath = Encoding.Unicode.GetString( didd.DevicePath, 0, ( int ) nRequiredSize - 6 );  // remove 6 bytes: 2 bytes zero termination and another 4 bytes, because nRequiredSize also counts SP_DEVICE_INTERFACE_DETAIL_DATA.cbSize (as it seems...)

                        UInt32 RequiredSize;
                        byte[] ptrBuf = new byte[BUFFER_SIZE];
                        string PNPDeviceID = "";
                        if( SetupDiGetDeviceInstanceIdA( h, ref da, ptrBuf, BUFFER_SIZE, out RequiredSize ) ) {
                            if( RequiredSize >= 1 )
                                PNPDeviceID = Encoding.ASCII.GetString( ptrBuf, 0, ( int ) RequiredSize - 1 );
                        }

                        // you can get the properties, which are shown in "device manager -> properties of the drive -> details"
                        /*
                        string desc = getStringProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_PHYSICAL_DEVICE_OBJECT_NAME ); // SPDRP_DEVICEDESC );
                        string driver = getStringProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_DRIVER );
                        string friendlyname = getStringProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_FRIENDLYNAME );
                        // no, the removable flag in the capabalities is of no use! Use removalPolicy!
                        uint capabilities = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_CAPABILITIES );
                        uint removalPolicy = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY );
                        uint removalPolicyHW = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY_HW_DEFAULT );
                        //uint removalPolicyOVR = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY_OVERRIDE );

                        Console.WriteLine( "{0,-40} {1,-60} {2,-20} {3,-20} {4,5} {5} {6}", friendlyname, PNPDeviceID, desc, driver, capabilities, removalPolicy, removalPolicyHW );
                        */
                        try {
                            uint removalPolicy = getDWORDProp( h, da, SetupDiGetDeviceRegistryPropertyEnum.SPDRP_REMOVAL_POLICY );
                            if( removalPolicy == ( uint ) RemovalPolicy.CM_REMOVAL_POLICY_EXPECT_ORDERLY_REMOVAL || removalPolicy == ( uint ) RemovalPolicy.CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL )
                                result.Add( getPNPDeviceID ? PNPDeviceID : devicePath );
                        } catch( InvalidOperationException ) {
                            continue;
                        }
                    }
                }
            } finally {
                SetupDiDestroyDeviceInfoList( h );
            }

            return result.ToArray();
        }

        static void Main(string[] args)
        {
            string[] removableDisks = getRemovableDisks();
        }
    }
}