Update %PATH% environment variable using NSIS

I prefer using windows command processor (cmd.exe) via the NSIS nsExec::Exec command, which allows you to append to the PATH easily like so:

; Check if the path entry already exists and write result to $0
nsExec::Exec 'echo %PATH% | find "c:\some\new\dir"'
Pop $0   ; gets result code

${If} $0 = 0
    nsExec::Exec 'setx PATH=%PATH%;c:\some\new\dir'
${EndIf}

Using this method, CMD.EXE expands the PATH variable internally, safe from any NSIS string length limits. Alternatively, change the order of %PATH% token pasting if you want your program to be picked up first, ahead of anything and everything else that might be installed on the system by the same name:

    nsExec::Exec 'setx PATH=c:\some\new\dir;%PATH%'

Note that it's important not to include double-quotes while building the new PATH. The command processor never expects double-quotes in the PATH string and may behave in unexpected ways if you add any. It delimits paths by semicolon (;) only.

Also note that this methods depends on the large strings build as explained by Seki in his answer.

nsExec::Exec differs from ExecWait in that it runs internally, without popping up additional visible cmd prompt windows.


You can use an alternative NSIS build from the special builds page like the large strings build that defines NSIS_MAX_STRLEN=8192 and should prevent you from breaking the host path.

In practice, on a desktop machine, 1024 byte seems enough, but on a development host with many tools installed (like mine), the path might be broken after manipulation, while the 8192 bytes strings build had never perturbed my machine.

To be very sure, you can add a check on the path length before manipulation and abort the installer with a message in the case where the path would be close to the NSIS_MAX_STRLEN constant before trying to manipulate it.


The real solution is to write a custom plugin or call the Windows API directly with the system plugin so you can avoid the NSIS buffer length limitation:

!include LogicLib.nsh
!include WinCore.nsh
!ifndef NSIS_CHAR_SIZE
!define NSIS_CHAR_SIZE 1
!endif

Function RegAppendString
System::Store S
Pop $R0 ; append
Pop $R1 ; separator
Pop $R2 ; reg value
Pop $R3 ; reg path
Pop $R4 ; reg hkey
System::Call 'ADVAPI32::RegCreateKey(i$R4,tR3,*i.r1)i.r0'
${If} $0 = 0
    System::Call 'ADVAPI32::RegQueryValueEx(ir1,tR2,i0,*i.r2,i0,*i0r3)i.r0'
    ${If} $0 <> 0
        StrCpy $2 ${REG_SZ}
        StrCpy $3 0
    ${EndIf}
    StrLen $4 $R0
    StrLen $5 $R1
    IntOp $4 $4 + $5
    IntOp $4 $4 + 1 ; For \0
    !if ${NSIS_CHAR_SIZE} > 1
        IntOp $4 $4 * ${NSIS_CHAR_SIZE}
    !endif
    IntOp $4 $4 + $3
    System::Alloc $4
    System::Call 'ADVAPI32::RegQueryValueEx(ir1,tR2,i0,i0,isr9,*ir4r4)i.r0'
    ${If} $0 = 0
    ${OrIf} $0 = ${ERROR_FILE_NOT_FOUND}
        System::Call 'KERNEL32::lstrlen(t)(ir9)i.r0'
        ${If} $0 <> 0
            System::Call 'KERNEL32::lstrcat(t)(ir9,tR1)'
        ${EndIf}
        System::Call 'KERNEL32::lstrcat(t)(ir9,tR0)'
        System::Call 'KERNEL32::lstrlen(t)(ir9)i.r0'
        IntOp $0 $0 + 1
        !if ${NSIS_CHAR_SIZE} > 1
            IntOp $0 $0 * ${NSIS_CHAR_SIZE}
        !endif
        System::Call 'ADVAPI32::RegSetValueEx(ir1,tR2,i0,ir2,ir9,ir0)i.r0'
    ${EndIf}
    System::Free $9
    System::Call 'ADVAPI32::RegCloseKey(ir1)'
${EndIf}
Push $0
System::Store L
FunctionEnd

Section

Push ${HKEY_CURRENT_USER}
Push "Environment"
Push "Path"
Push ";"
Push "c:\whatever"
Call RegAppendString
Pop $0
DetailPrint RegAppendString:Error=$0

SectionEnd