如何在NSIS中列出所有可移动与不可移动USB驱动器?
在NSIS中枚举所有USB连接的驱动器(含不可移动外置硬盘与无盘符设备)
要解决你的问题,不能依赖NSIS内置函数或仅识别可移动设备的插件,必须通过Windows内核API直接查询物理磁盘的总线类型,以此精准筛选所有USB连接的存储设备,兼容Windows XP及以上系统,无需PowerShell。
核心思路是:
- 枚举系统中的物理磁盘(
\\.\PhysicalDriveN) - 通过
DeviceIoControl发送IOCTL_STORAGE_QUERY_PROPERTY请求,获取磁盘的总线类型(BusType) - 若BusType为USB,则进一步枚举该磁盘的分区/卷,关联逻辑盘符(如果存在)
- 同时处理无盘符的USB存储设备
以下是完整的NSIS实现代码:
!include LogicLib.nsh !include WinCore.nsh ; 定义所需API常量与结构 !define IOCTL_STORAGE_QUERY_PROPERTY 0x2D1400 !define StorageDeviceProperty 0x00 !define PropertyStandardQuery 0x00 enum STORAGE_BUS_TYPE { BusTypeUnknown = 0, BusTypeScsi, BusTypeAtapi, BusTypeAta, BusType1394, BusTypeSsa, BusTypeFibre, BusTypeUsb, ; 目标总线类型 BusTypeRAID, BusTypeiScsi, BusTypeSas, BusTypeSata, BusTypeSd, BusTypeMmc, BusTypeVirtual, BusTypeFileBackedVirtual, BusTypeSpaces, BusTypeNvme, BusTypeSCM, BusTypeUfs, BusTypeMax } struct STORAGE_PROPERTY_QUERY DWORD PropertyId DWORD QueryType BYTE AdditionalParameters[1] ends struct STORAGE_DEVICE_DESCRIPTOR DWORD Version DWORD Size BYTE DeviceType BYTE DeviceTypeModifier BOOL RemovableMedia BOOL CommandQueueing DWORD VendorIdOffset DWORD ProductIdOffset DWORD ProductRevisionOffset DWORD SerialNumberOffset DWORD BusType DWORD RawPropertiesLength BYTE RawDeviceProperties[1] ends Function EnumUSBDrives Push $R0 Push $R1 Push $R2 Push $R3 Push $R4 Push $R5 Push $R6 Push $R7 ; 枚举物理磁盘,从PhysicalDrive0开始 StrCpy $R0 0 loop_disk: ; 构造物理磁盘路径 StrCpy $R1 "\\.\PhysicalDrive" StrCat $R1 $R0 ; 打开物理磁盘(需管理员权限) System::Call 'kernel32::CreateFile(t "$R1", i 0x80000000, i 3, i 0, i 3, i 0x80, i 0) p.r2' IntCmp $2 -1 end_loop_disk ; 打开失败,终止枚举 ; 初始化查询结构 System::Call '(i ${StorageDeviceProperty}, i ${PropertyStandardQuery}, i 0) .r3' ; 先获取所需缓冲区大小 System::Call 'kernel32::DeviceIoControl(p $2, i ${IOCTL_STORAGE_QUERY_PROPERTY}, p $3, i ${STORAGE_PROPERTY_QUERY_SIZE}, p 0, i 0, *i.r4, i 0) i.r5' IntCmp $5 0 skip_disk ; 调用失败,跳过当前磁盘 ; 分配缓冲区 System::Call 'kernel32::LocalAlloc(i 0, i $4) p.r6' System::Call 'kernel32::ZeroMemory(p $6, i $4)' ; 获取设备描述符 System::Call 'kernel32::DeviceIoControl(p $2, i ${IOCTL_STORAGE_QUERY_PROPERTY}, p $3, i ${STORAGE_PROPERTY_QUERY_SIZE}, p $6, i $4, *i.r4, i 0) i.r5' IntCmp $5 0 free_buffer skip_disk ; 读取总线类型 System::Call '*$6(i, i, i, i, i, i, i, i, i, i.r7, ...)' IntCmp $7 ${BusTypeUsb} is_usb skip_disk is_usb: DetailPrint "USB物理磁盘: PhysicalDrive$R0" ; 如需关联逻辑盘符,可扩展调用FindFirstVolumeW、GetVolumeDiskExtents等API继续处理 skip_disk: free_buffer: System::Call 'kernel32::LocalFree(p $6)' System::Call 'kernel32::CloseHandle(p $2)' IntOp $R0 $R0 + 1 Goto loop_disk end_loop_disk: Pop $R7 Pop $R6 Pop $R5 Pop $R4 Pop $R3 Pop $R2 Pop $R1 Pop $R0 FunctionEnd ; 安装流程中调用枚举函数 RequestExecutionLevel admin Section "枚举USB驱动器" Call EnumUSBDrives SectionEnd
关键说明
- 权限要求:访问物理磁盘必须拥有管理员权限,脚本开头需添加
RequestExecutionLevel admin声明 - 兼容性:所有用到的API均支持Windows XP及以上系统,无需依赖PowerShell或高版本系统特性
- 无盘符设备:代码已能识别无盘符的USB物理磁盘,如需获取卷详细信息,可扩展相关API调用
- 不可移动USB硬盘:通过BusType=USB筛选,不受设备是否标记为"可移动存储"的限制,能精准识别外置USB硬盘
内容的提问来源于stack exchange,提问作者Jiri
相关产品推荐
相关产品推荐

