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

Reliably know if a volume is removable or not with WinApi

本文关键字:是否 可使用 WinApi 移动      更新时间:2023-10-16

>我正在使用

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确实区分了可移动的(它们在时钟附近有一个图标"安全删除硬质并弹出媒体"(。

问题是你问错了问题。当他们使用术语时,"可移动"意味着介质和介质驱动器是分开的(如软驱或 CD-ROM(。任何不允许单个驱动器在不同时间容纳不同媒体的东西都是"固定"驱动器。

根据你似乎想要的,我相信你想使用带有SPDRP_CAPABILITIES标志的SetupDiGetDeviceRegistryProperty。这将告诉您驱动器是否可以弹出其媒体(几乎等同于您已经找到的"可移动"(,以及设备本身是否可移动(CM_DEVCAP_REMOVABLE(。

不幸的是,Microsoft的SetupDi*函数使用起来有点混乱(说得和我知道的一样好(。他们有一些演示代码,使用正确的函数并检索相当相似的信息,但代码也有些丑陋,因此可能需要一些研究和实验来修改它以获得您想要的内容。

Jerry Coffin几乎是对的。 但是,您必须改为检查SPDRP_REMOVAL_POLICY属性。 到达那里很麻烦。下面是一个 C# 实现。它返回 PNPDeviceID 或设备路径的列表。然后,这可用于与WMI数据或PowerShellGet-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();
}
}
}

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

可移动介质

指示当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 %Sn", RemovableMedia, buf);
}
CloseHandle(hDisk);
}
buf += wcslen(buf) + 1;
}
}
}
}

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