13

I've read that "Strings longer than ${NSIS_MAX_STRLEN} (1024) will get truncated/corrupted."

How can I safely update %PATH% environment variable?

Cyber Tailor
  • 443
  • 7
  • 18

5 Answers5

7

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.

arrowd
  • 33,231
  • 8
  • 79
  • 110
Seki
  • 11,135
  • 7
  • 46
  • 70
7

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.

jstine
  • 3,234
  • 20
  • 21
5

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 
Anders
  • 97,548
  • 12
  • 110
  • 164
  • I only did minor testing on the RegAppendString function, ideally you would force zero termination, check if it already has the separator at the end and perform all of this in a loop to avoid issues with the string changing between the calls to RegQueryValueEx. – Anders Jul 10 '15 at 13:49
2

I was frustrated looking at all the complicated plugins and char limitations with the NSIS stuff so I wrote a tiny little application called PathEd to take care of all.

PathEd.exe

It is designed to be deployed within your installer and can be called from its deploy location like this, for example:

PathEd.exe add "C:\Program Files\RepoZ" or PathEd.exe remove "C:\Program Files\RepoZ"

PathEd takes care of the semicolon handling, duplicate avoidance, case-insensitive checks, user account control prompts, argument quote handling, safe and defensive value removal and so on.

Feel free to use it. But then, don't forget to give it a star on GitHub.

See https://github.com/awaescher/PathEd

Waescher
  • 5,361
  • 3
  • 34
  • 51
  • 1
    Used this in my own project just now. This application works great! It can be implemented in three lines of NSI code. Definitely the best and easiest option. @Waescher , you've received my star. – tayler6000 Jun 22 '22 at 01:07
0

I have been wrote the NSIS 3.0 example to handle cases longer than the limit and without a need to install anything. Answered the question here: Set environment variables with NSIS in Window 7

Community
  • 1
  • 1
Andry
  • 2,273
  • 29
  • 28