Search code examples
nsis

NSIS SelectFolderDialog with specific drives


In NSIS SelectFolderDialog can I somehow display tree only with HDDs? I need to restrict user from selecting any directory that is not located on the HDD.


Solution

  • To have full control over which items are present you would have to write a custom NSIS plug-in that calls SHBrowseForFolder and provides a implementation of IFolderFilterSite.

    Another approach is to disable the OK button when the user has selected something that is unacceptable to you:

    !include LogicLib.nsh
    
    !ifndef DRIVE_FIXED
    !define DRIVE_FIXED 3
    !endif
    ; This function decides which items are acceptable
    Function MyFolderValidator ; Input: $1=PIDL, $2=Path Output:$0=0 if invalid
    System::Call 'KERNEL32::GetVolumePathName(tr2, t.r4, i ${NSIS_MAX_STRLEN})i.r5'
    ${If} $5 <> 0
        StrCpy $2 $4
    ${ElseIf} $5 == "error" ; GetVolumePathName is Win2000+
        StrCpy $2 $2 3 ; GetDriveType only accepts root paths (This will not work for UNC)
    ${EndIf}
    System::Call 'KERNEL32::GetDriveType(tr2)i.r3'
    ${If} $3 = ${DRIVE_FIXED}
        StrCpy $0 1 ; Allow this path
    ${Else}
        StrCpy $0 0 ; Don't allow this path
    ${EndIf}
    FunctionEnd
    
    !include WinMessages.nsh
    !define /math BFFM_ENABLEOK ${WM_USER} + 101
    !define BFFM_SELCHANGED 2
    !define BFFM_VALIDATEFAILEDA 3
    !define BFFM_VALIDATEFAILEDW 4
    !if "${NSIS_CHAR_SIZE}" > 1
    !define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDW}
    !else
    !define BFFM_VALIDATEFAILED ${BFFM_VALIDATEFAILEDA}
    !endif
    !if "${NSIS_PTR_SIZE}" <= 4
    Function BrowseForValidatedFolder ; NSIS 2.51+
    System::Store S
    Pop $2 ; HeadingText
    Pop $1 ; ValidatorFunc
    System::Get "(p.R1, i.R2, p.R3, p)i R8R8" ; BFFCALLBACK
    Pop $R9 ; The system plug-in callback function
    StrCpy $4 "kR9" ; SHBrowseForFolder callback parameter
    System::Call '*(&t261 "")p.r7' ; pszDisplayName buffer
    System::Call '*(p $hwndparent, p0, pr7, t r2, i 0x51, $4, p0, i)p.r8' ; BROWSEINFO struct 
    !if "${NSIS_CHAR_SIZE}" > 1
    System::Call 'SHELL32::SHBrowseForFolderW(pr8)p.r9'
    !else
    System::Call 'SHELL32::SHBrowseForFolderA(pr8)p.r9'
    !endif
    BFFCALLBACK_loop:
        StrCpy $R8 $R8 8 ; HACKHACK: Working around 2.x bug where the callback IDs are never released
        StrCmp $R8 "callback" 0 BFFCALLBACK_done
        ${If} $R2 = ${BFFM_SELCHANGED}
        ${AndIf} $R3 P<> 0
            System::Store S
            StrCpy $0 $1
            StrCpy $1 $R3
            System::Call 'SHELL32::SHGetPathFromIDList(p $R3, t.r2)'
            Call $0
            SendMessage $R1 ${BFFM_ENABLEOK} 0 $0
            System::Store L
        ${EndIf}
        StrCpy $R8 0 ; Yep, the return value is in the same place as the callback id
        ${IfThen} $R2 = ${BFFM_VALIDATEFAILED} ${|} StrCpy $R8 1 ${|} ; Keep the dialog open
        System::Call $R9
        goto BFFCALLBACK_loop
    BFFCALLBACK_done:
    System::Free $R9 ; BFFCALLBACK
    System::Free $7 ; pszDisplayName 
    System::Free $8 ; BROWSEINFO
    ${If} $9 Z<> 0
        System::Call 'SHELL32::SHGetPathFromIDList(p r9, t.s)i'
        System::Call 'OLE32::CoTaskMemFree(p r9)'
    ${Else}
        Push "" ; Error/cancel, return empty string
    ${EndIf}
    System::Store L
    FunctionEnd
    !endif
    !macro BrowseForValidatedFolder HeadingText ValidatorFuncName VarResult
    GetFunctionAddress ${VarResult} ${ValidatorFuncName}
    Push ${VarResult}
    Push "${HeadingText}"
    Call BrowseForValidatedFolder
    Pop ${VarResult}
    !macroend
    
    Section
    !insertmacro BrowseForValidatedFolder "Choose a location for blah blah" MyFolderValidator $0
    ${If} $0 != ""
        MessageBox mb_ok "Result: $0"
    ${Else}
        MessageBox mb_iconstop "User cancelled"
    ${EndIf}
    SectionEnd