nsis

NSIS list all USB drives both removable and non-removable


Is there any way to list all removable and non-removable USB drives in NSIS (NullSoft Install System)? The closest thing I can do at the moment is to use built-in "GetDrives" function and specify HDD, FDD (which lists all USB removable drives, but not USB non-removable), CDROM or NET. I have also found "GetFirstRemovable" plugin, but that too only lists removable drives. If I plug my external 1TB USB drive which is not configured as a "removable" device, it won't show it as FDD, it shows it as HDD so I can't filter by drive type. This also limits any drive that isn't formatted and/or doesn't have a letter assigned.

I need to list all USB drives so I could format it once selected. The way I do it in my PowerShell install script using Get-Disk is like this:

# Only get a list of removable devices (ignore internal drives, cdroms etc.)
$drives = Get-Disk | Where-Object -FilterScript {$_.Bustype -Eq "USB"} | Select-Object -Property DiskNumber, FriendlyName, @{Label = "Size" ; Expression ={ [Math]::Round($_.Size/1GB,0)}} | Sort-Object -Property DiskNumber

But I would like to move from PowerShell (as Get-Disk is only supported on Windows 8+), not all customised systems have PowerShell available and I need it to be cross compatible as much as possible.

I have a feeling I would have to use "Kernel32::" approach, but I have no idea which function from the Kernel32 to use in order to get the list of physical drives.

At this point, I'm nearly ready to abandon the NSIS, which is otherwise a perfect fit for the isntaller I want to make.

I did search around extensively, but all I get is Volume data, but I need Physical disk data which I could filter by bus type.

Any help would be greatly appreaciated.


Solution

  • NSIS has no native support for this so yes, you need to call kernel32 directly:

    !include LogicLib.nsh
    Section
    
    StrCpy $1 "D:" ; Drive letter
    
    !define DRIVE_REMOVABLE 2
    !define DRIVE_FIXED 3
    System::Call 'KERNEL32::GetDriveType(t "$1\")i.r0'
    ${If} $0 = ${DRIVE_REMOVABLE}
        DetailPrint DRIVE_REMOVABLE
    ${ElseIf} $0 = ${DRIVE_FIXED}
        DetailPrint DRIVE_FIXED
    ${Else}
        DetailPrint DRIVE_?
    ${EndIf}
    
    !define  OPEN_EXISTING 3
    !define FILE_FLAG_NO_BUFFERING 0x20000000
    System::Call 'KERNEL32::CreateFile(t "\\.\$1", i0, i7, p0, i${OPEN_EXISTING}, i{FILE_FLAG_NO_BUFFERING}, p0)i.r2'
    ${If} $2 P= -1
        DetailPrint "Unable to open volume $1"
        Quit
    ${EndIf}
    
    !define IOCTL_STORAGE_GET_HOTPLUG_INFO 0x002d0c14
    System::Call 'KERNEL32::DeviceIoControl(pr2, i${IOCTL_STORAGE_GET_HOTPLUG_INFO}, p0, i0, @r3, i8, *i, p0)i.r0'
    ${If} $0 <> 0
        System::Call '*$3(i, &i1.r4, &i1.r5, &i1.r6)'
        DetailPrint "MediaRemovable=$4 MediaHotplug=$5 DeviceHotplug=$6"
    ${EndIf}
    
    !define IOCTL_STORAGE_QUERY_PROPERTY 0x002d1400
    System::Call 'GDI32::X(@r3)' ; Just "allocate" $3
    System::Call '*$3(i0, i0)'
    System::Call 'KERNEL32::DeviceIoControl(pr2, i${IOCTL_STORAGE_QUERY_PROPERTY}, pr3, i12, @r4, i100, *i, p0)i.r0'
    ${If} $0 <> 0
        System::Call '*$4(i,i,i,i,i,i,i,&i1.r5)'
        DetailPrint "BusType=$5 (BusTypeUsb is 7)"
    ${EndIf}
    
    FileClose $2
    
    SectionEnd
    

    Enumerating valid drive letters:

    Section GetLogicalDrives
    System::Call 'KERNEL32::GetLogicalDriveStringsW(i999, @r9)'
    loop:
        System::Call '*$9(&w4.r8)'
        ${If} $8 != ""
            IntPtrOp $9 $9 + 8
            StrCpy $8 $8 2
            DetailPrint "Drive $8"
            Goto loop
        ${EndIf}
    SectionEnd