I want to set environment variable using NSIS installer. I will run script on Windows 7, if it is important. Thanks!
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 full and direct implementation example for the NSIS 3.0.
Consist of 3 files.
build.bat - build 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 to there the makensis executable is on your system.
stack.nsi - helper functions file.
!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 - full 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} ""
; ${Push} "HKLM"
; ${Push} "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
; ${Push} ""
; Call RegAddPathToVar
; Current user only:
; ${Push} ""
; ${Push} "HKCU"
; ${Push} "Environment"
; ${Push} ""
; 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 .
; 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:
; `'
; `\'
; 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}
; `'
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"
; `\'
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