UPDATE The latest version of the RegAddPathToVar function is here: https://sf.net/p/nsisplus (full test Example: https://sf.net/p/nsisplus/NsisSetupLib/HEAD/tree/trunk/tests/test_RegAddRemovePathGUI/main.nsi )
Here is an example of a complete and direct implementation of NSIS 3.0.
Consists of 3 files.
build.bat - create a script file.
@echo off set "MAKENSIS_EXE=makensis.exe" "%MAKENSIS_EXE%" /V4 "/Obuild.log" "/XOutFile '%~dp0test.exe'" "%~dp0main.nsi" echo.Return code: %ERRORLEVEL% pause
Edit the MAKENSIS_EXE variable so that the makensis executable is on your system.
stack.nsi - file of auxiliary functions.
!define PushStack11 "!insertmacro PushStack11" !macro PushStack11 var0 var1 var2 var3 var4 var5 var6 var7 var8 var9 var10 Push `${var0}` Push `${var1}` Push `${var2}` Push `${var3}` Push `${var4}` Push `${var5}` Push `${var6}` Push `${var7}` Push `${var8}` Push `${var9}` Push `${var10}` !macroend !define ExchStack4 "!insertmacro ExchStack4" !macro ExchStack4 var0 var1 var2 var3 Exch `${var3}` Exch 1 Exch `${var2}` Exch 1 Exch 2 Exch `${var1}` Exch 2 Exch 3 Exch `${var0}` Exch 3 !macroend !define PopStack15 "!insertmacro PopStack15" !macro PopStack15 var0 var1 var2 var3 var4 var5 var6 var7 var8 var9 var10 var11 var12 var13 var14 Pop `${var14}` Pop `${var13}` Pop `${var12}` Pop `${var11}` Pop `${var10}` Pop `${var9}` Pop `${var8}` Pop `${var7}` Pop `${var6}` Pop `${var5}` Pop `${var4}` Pop `${var3}` Pop `${var2}` Pop `${var1}` Pop `${var0}` !macroend
main.nsi is a complete NSIS implementation example.
!include "nsDialogs.nsh" !include "WinCore.nsh" !include "LogicLib.nsh" !include "stack.nsi" RequestExecutionLevel admin ; for all users Page Custom Show Leave Var /GLOBAL DialogID Var /GLOBAL EditID Var /GLOBAL Edit Var /GLOBAL ButtonAppendID !define GotoIf "!insertmacro GotoIf" !macro GotoIf label exp ${If} ${exp} Goto `${label}` ${EndIf} !macroend !define RegGetKeyMap "!insertmacro RegGetKeyMap" !macro RegGetKeyMap var value ${Switch} ${value} ${Case} "HKCR" StrCpy ${var} ${HKEY_CLASSES_ROOT} ${Break} ${Case} "HKCU" StrCpy ${var} ${HKEY_CURRENT_USER} ${Break} ${Case} "HKLM" StrCpy ${var} ${HKEY_LOCAL_MACHINE} ${Break} ${Case} "HKU" StrCpy ${var} ${HKEY_USERS} ${Break} ${Case} "HKPD" StrCpy ${var} ${HKEY_PERFORMANCE_DATA} ${Break} ${Case} "HKDD" StrCpy ${var} ${HKEY_DYN_DATA} ${Break} ${Case} "HKCC" StrCpy ${var} ${HKEY_CURRENT_CONFIG} ${Break} ${Case} "HKEY_CLASSES_ROOT" StrCpy ${var} ${HKEY_CLASSES_ROOT} ${Break} ${Case} "HKEY_CURRENT_USER" StrCpy ${var} ${HKEY_CURRENT_USER} ${Break} ${Case} "HKEY_LOCAL_MACHINE" StrCpy ${var} ${HKEY_LOCAL_MACHINE} ${Break} ${Case} "HKEY_USERS" StrCpy ${var} ${HKEY_USERS} ${Break} ${Case} "HKEY_PERFORMANCE_DATA" StrCpy ${var} ${HKEY_PERFORMANCE_DATA} ${Break} ${Case} "HKEY_DYN_DATA" StrCpy ${var} ${HKEY_DYN_DATA} ${Break} ${Case} "HKEY_CURRENT_CONFIG" StrCpy ${var} ${HKEY_CURRENT_CONFIG} ${Break} ${Default} StrCpy ${var} ${HKEY_CURRENT_USER} ${Break} ${EndSwitch} !macroend ; Usage: ; All users: ; ${Push} "<path>" ; ${Push} "HKLM" ; ${Push} "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" ; ${Push} "<env_var>" ; Call RegAddPathToVar ; Current user only: ; ${Push} "<path>" ; ${Push} "HKCU" ; ${Push} "Environment" ; ${Push} "<env_var>" ; Call RegAddPathToVar !macro Func_RegAddPathToVar un !ifndef ${un}RegAddPathToVar_INCLUDED !define ${un}RegAddPathToVar_INCLUDED Function ${un}RegAddPathToVar ${ExchStack4} $R0 $R1 $R2 $R3 ${PushStack11} $R4 $R5 $R6 $R7 $R8 $R9 $0 $1 $2 $8 $9 ; WARNING: ; NSIS ReadRegStr returns empty string on string overflow, so native calls are used here: ; 1. To check actual length of <env_var>. ; 2. To process the PATH variable of any length long. ; The IDEAL algorithm for any length long PATH variable, where each subpath is not longer than ${NSIS_MAX_STRLEN}-${NSIS_CHAR_SIZE} bytes: ; 1. Init current string list if does not have any before or take as current has created after the previous algorithm run. ; 2. Read string of ${NSIS_MAX_STRLEN}-${NSIS_CHAR_SIZE} bytes length into the array of ${NSIS_MAX_STRLEN} bytes length from the input address, add NSIS_CHAR_SIZE nulls at the end. ; 3. Go to 20 if empty or nothing else except the ; characters in the array. ; 4. Truncate all in the array after the last ; character, where the ; character has found not under " character quoted string (see description in the 6). ; 5. Split strings in the array by the ; character if it has found not under " character quoted string into the list. ; 6. Move the last string from the list into the next repeat cycle list if it begins by the " character but not ends by the same character (not completely fitted into the limited to ${NSIS_MAX_STRLEN} bytes window). ; 7. Unquote all strings in the list and create the second list with flags marked where the quotes has removed. ; 8. Search for "$R0" or "$R0\" in the list, if found then raise a flag and leave the algorithm. ; 9. Move ${NSIS_MAX_STRLEN} byte window by the array current string length long multiple to NSIS_CHAR_SIZE value along the input address. ; 10. Repeat the algorithm. ; 20. Append path to the list. ; 21. Restore quotes for those strings in the first list what been quoted before by the second list. ; 22. Join first list by the separator into one string. ; The REAL algorithm for any length long PATH variable, where each subpath is not longer than ${NSIS_MAX_STRLEN}-${NSIS_CHAR_SIZE} bytes: ; 1. Read string from registry into dynamic buffer enough to store more characters: length of being searched string + length of separator + length of string to search + length of null character. ; 2. Copy string from the buffer to the second dynamic buffer enough to store more characters: length of separator + length of being searched string + length of separator + length of null character. ; 3. Prepend and append separator character to second buffer. ; 4. Try to find multiple instances of the string to search in the second buffer through the shlwapi::StrStrI, where search instances are: ; `<Separator><StringToSearch><Separator>' ; `<Separator><StringToSearch>\<Separator>' ; 5. If found any instance then leave the algorithm. ; 6. Append separator character to the first buffer if it does not ending by it. ; 7. Append the string to search to the first buffer. ; handles and pointers init StrCpy $R7 0 StrCpy $R9 0 StrCpy $0 0 StrCpy $1 0 StrCpy $2 0 ; keys map ${RegGetKeyMap} $R8 $R1 System::Call "advapi32::RegOpenKey(i R8, t R2, *i.R6) i.R4" ${If} $R4 <> 0 DetailPrint "RegAddPathToVar: advapi32::RegOpenKey error: code=$R4 hive=$\"$R8$\" key=$\"$R2$\"" MessageBox MB_OK "RegAddPathToVar: advapi32::RegOpenKey error: code=$R4 hive=$\"$R8$\" key=$\"$R2$\"" /SD IDOK Goto done ${EndIf} System::Call "advapi32::RegQueryValueEx(i R6, t R3, i 0, *i .r9, p 0, *i 0 R7) i.R4" ${If} $R4 <> 0 DetailPrint "RegAddPathToVar: advapi32::RegQueryValueEx (1) is failed, unexpected error code: code=$R4 length=$\"$R7$\"" MessageBox MB_OK "RegAddPathToVar: advapi32::RegQueryValueEx (1) is failed, unexpected error code: code=$R4 length=$\"$R7$\"" /SD IDOK Goto done ${EndIf} ; remove trailing "\" character from the string to search StrCpy $R5 $R0 "" -1 ${If} $R5 == "\" StrCpy $R0 $R0 -1 ${EndIf} StrLen $R8 $R0 ; first buffer: length of being searched string + length of separator + length of string to search + length of null character IntOp $R5 $R8 + 1 ; ";" IntOp $R5 $R5 * ${NSIS_CHAR_SIZE} IntOp $R5 $R5 + $R7 ; already in bytes including null character ; allocate first dynamic buffer System::Alloc $R5 Pop $0 ${If} $0 = 0 DetailPrint "RegAddPathToVar: System::Alloc (1) is failed: size=$R5" MessageBox MB_OK "RegAddPathToVar: System::Alloc (1) is failed: size=$R5" /SD IDOK Goto done ${EndIf} System::Call "advapi32::RegQueryValueEx(i R6, t R3, i 0, i 0, p r0, *i R5 R7) i.R4" ${If} $R4 <> 0 DetailPrint "RegAddPathToVar: advapi32::RegQueryValueEx (2) is failed, unexpected error: code=$R4 length=$\"$R7$\"" MessageBox MB_OK "RegAddPathToVar: advapi32::RegQueryValueEx (2) is failed, unexpected error: code=$R4 length=$\"$R7$\"" /SD IDOK Goto done ${EndIf} ; strip separator characters from the first buffer end ${If} $R7 > ${NSIS_CHAR_SIZE} ; excluding null character IntOp $R5 $R7 - ${NSIS_CHAR_SIZE} IntOp $R5 $R5 - ${NSIS_CHAR_SIZE} IntOp $R9 $0 + $R5 strip_loop1: System::Call "*$R9(&t1 .r8)" ${If} $8 == ";" System::Call "*$R9(&t1 '')" ; null character IntOp $R7 $R7 - ${NSIS_CHAR_SIZE} ${If} $R9 >= ${NSIS_CHAR_SIZE} IntOp $R9 $R9 - ${NSIS_CHAR_SIZE} Goto strip_loop1 ${EndIf} ${EndIf} ${EndIf} ; second buffer: length of separator + length of being searched string + length of separator + length of null character IntOp $R5 2 * ${NSIS_CHAR_SIZE} ; 2 x ";" IntOp $R5 $R5 + $R7 ; already in bytes including null character ; allocate second dynamic buffer System::Alloc $R5 Pop $1 ${If} $1 = 0 DetailPrint "RegAddPathToVar: System::Alloc (2) is failed: size=$R5" MessageBox MB_OK "RegAddPathToVar: System::Alloc (2) is failed: size=$R5" /SD IDOK Goto done ${EndIf} System::Call "*$1(&t1 ';')" IntOp $R9 $1 + ${NSIS_CHAR_SIZE} System::Call "kernel32::lstrcpyn(p R9, p r0, i R7) p.R4" ${If} $R4 = 0 DetailPrint "RegAddPathToVar: kernel32::lstrcpyn (1) is failed" MessageBox MB_OK "RegAddPathToVar: kernel32::lstrcpyn (1) is failed" /SD IDOK Goto done ${EndIf} IntOp $R9 $R9 + $R7 IntOp $R9 $R9 - ${NSIS_CHAR_SIZE} ; exclude last null character System::Call "*$R9(&t1 ';')" IntOp $R9 $R9 + ${NSIS_CHAR_SIZE} System::Call "*$R9(&t1 '')" ; null character ; buffer for the string to search IntOp $R5 0 + 4 ; 2 x ";" + "\" + length of null character IntOp $R5 $R5 + $R8 ; excluding null character IntOp $R5 $R5 * ${NSIS_CHAR_SIZE} System::Alloc $R5 Pop $2 ${If} $2 = 0 DetailPrint "RegAddPathToVar: System::Alloc (3) is failed: size=$R5" MessageBox MB_OK "RegAddPathToVar: System::Alloc (3) is failed: size=$R5" /SD IDOK Goto done ${EndIf} ; convert R8 (length of R0) to bytes IntOp $R8 $R8 * ${NSIS_CHAR_SIZE} ; `<Separator><StringToSearch><Separator>' System::Call "*$2(&t1 ';')" IntOp $R9 $2 + ${NSIS_CHAR_SIZE} System::Call "kernel32::lstrcpy(p R9, t R0) p.R4" ${If} $R4 = 0 DetailPrint "RegAddPathToVar: kernel32::lstrcpy (2) is failed" MessageBox MB_OK "RegAddPathToVar: kernel32::lstrcpy (2) is failed" /SD IDOK Goto done ${EndIf} IntOp $R9 $R9 + $R8 ; length does not include the last null character System::Call "*$R9(&t1 ';')" IntOp $R9 $R9 + ${NSIS_CHAR_SIZE} System::Call "*$R9(&t1 '')" ; null character System::Call "shlwapi::StrStrI(p r1, p r2) p.R4" ${GotoIf} done "$R4 <> 0" ; `<Separator><StringToSearch>\<Separator>' System::Call "*$2(&t1 ';')" IntOp $R9 $2 + ${NSIS_CHAR_SIZE} IntOp $R9 $R9 + $R8 System::Call "*$R9(&t1 '\')" IntOp $R9 $R9 + ${NSIS_CHAR_SIZE} System::Call "*$R9(&t1 ';')" IntOp $R9 $R9 + ${NSIS_CHAR_SIZE} System::Call "*$R9(&t1 '')" ; null character System::Call "shlwapi::StrStrI(p r1, p r2) p.R4" ${GotoIf} done "$R4 <> 0" ; append to the first buffer IntOp $R9 0 + $0 ${If} $R7 > ${NSIS_CHAR_SIZE} IntOp $R9 $R9 + $R7 IntOp $R9 $R9 - ${NSIS_CHAR_SIZE} ; exclude last null character System::Call "*$R9(&t1 ';')" IntOp $R9 $R9 + ${NSIS_CHAR_SIZE} ${EndIf} System::Call "kernel32::lstrcpy(p R9, t R0) p.R4" ${If} $R4 = 0 DetailPrint "RegAddPathToVar: kernel32::lstrcpy (3) is failed" MessageBox MB_OK "RegAddPathToVar: kernel32::lstrcpy (3) is failed" /SD IDOK Goto done ${EndIf} IntOp $R9 $R9 + $R8 ; length does not include the last null character System::Call "*$R9(&t1 '')" ; null character IntOp $R9 $R9 + ${NSIS_CHAR_SIZE} IntOp $R5 $R9 - $0 System::Call "advapi32::RegSetValueEx(i R6, t R3, i 0, i r9, p r0, i R5) i.R4" ${If} $R4 <> 0 DetailPrint "RegAddPathToVar: advapi32::RegSetValueEx (1) is failed" MessageBox MB_OK "RegAddPathToVar: advapi32::RegSetValueEx (1) is failed" /SD IDOK Goto done ${EndIf} ; broadcast global event SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 done: System::Call "advapi32::RegCloseKey(i $R6)" ${If} $0 <> 0 System::Free $0 ${EndIf} ${If} $1 <> 0 System::Free $1 ${EndIf} ${If} $2 <> 0 System::Free $2 ${EndIf} ${PopStack15} $R0 $R1 $R2 $R3 $R4 $R5 $R6 $R7 $R8 $R9 $0 $1 $2 $8 $9 FunctionEnd !endif !macroend !define Call_RegAddPathToVar "!insertmacro Call_RegAddPathToVar" !macro Call_RegAddPathToVar prefix path hkey hkey_path env_var Push `${path}` Push `${hkey}` Push `${hkey_path}` Push `${env_var}` Call ${prefix}RegAddPathToVar !macroend !define RegAddPathToVar "!insertmacro RegAddPathToVar" !macro RegAddPathToVar !insertmacro Func_RegAddPathToVar "" !undef RegAddPathToVar !define RegAddPathToVar "${Call_RegAddPathToVar} ''" !macroend !define un.RegAddPathToVar "!insertmacro un.RegAddPathToVar" !macro un.RegAddPathToVar !insertmacro Func_RegAddPathToVar "un." !undef un.RegAddPathToVar !define un.RegAddPathToVar "${Call_RegAddPathToVar} 'un.'" !macroend ; include for install only ${RegAddPathToVar} Function Show nsDialogs::Create 1018 Pop $DialogID ${NSD_CreateText} 0 16u 80% 14u "C:\MyPath\bin" Pop $EditID ${NSD_OnChange} $EditID WndProc ${NSD_CreateButton} 80% 16u 20% 14u "Append" Pop $ButtonAppendID ${NSD_OnClick} $ButtonAppendID WndProc StrCpy $R0 -1 Call Update nsDialogs::Show FunctionEnd Function Leave FunctionEnd Function WndProc System::Store SR0 Call Update System::Store L FunctionEnd Function Update ; read values ${If} $R0 = $EditID ${OrIf} $R0 = -1 ${NSD_GetText} $EditID $Edit ${EndIf} ${If} $R0 = $ButtonAppendID ${If} $Edit != "" #${RegAddPathToVar} "$Edit" HKCU "Environment" PATH ; for current user ${RegAddPathToVar} "$Edit" HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" PATH ; for all users ${EndIf} ${EndIf} FunctionEnd Section -Hidden SectionEnd