EnvVarUpdate.nsh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. /**
  2. * EnvVarUpdate.nsh
  3. * : Environmental Variables: append, prepend, and remove entries
  4. *
  5. * WARNING: If you use StrFunc.nsh header then include it before this file
  6. * with all required definitions. This is to avoid conflicts
  7. *
  8. * Usage:
  9. * ${EnvVarUpdate} "ResultVar" "EnvVarName" "Action" "RegLoc" "PathString"
  10. *
  11. * Credits:
  12. * Version 1.0
  13. * * Cal Turney (turnec2)
  14. * * Amir Szekely (KiCHiK) and e-circ for developing the forerunners of this
  15. * function: AddToPath, un.RemoveFromPath, AddToEnvVar, un.RemoveFromEnvVar,
  16. * WriteEnvStr, and un.DeleteEnvStr
  17. * * Diego Pedroso (deguix) for StrTok
  18. * * Kevin English (kenglish_hi) for StrContains
  19. * * Hendri Adriaens (Smile2Me), Diego Pedroso (deguix), and Dan Fuhry
  20. * (dandaman32) for StrReplace
  21. *
  22. * Version 1.1 (compatibility with StrFunc.nsh)
  23. * * techtonik
  24. *
  25. * http://nsis.sourceforge.net/Environmental_Variables:_append%2C_prepend%2C_and_remove_entries
  26. *
  27. */
  28. !ifndef ENVVARUPDATE_FUNCTION
  29. !define ENVVARUPDATE_FUNCTION
  30. !verbose push
  31. !verbose 3
  32. !include "LogicLib.nsh"
  33. !include "WinMessages.NSH"
  34. !include "StrFunc.nsh"
  35. ; ---- Fix for conflict if StrFunc.nsh is already includes in main file -----------------------
  36. !macro _IncludeStrFunction StrFuncName
  37. !ifndef ${StrFuncName}_INCLUDED
  38. ${${StrFuncName}}
  39. !endif
  40. !ifndef Un${StrFuncName}_INCLUDED
  41. ${Un${StrFuncName}}
  42. !endif
  43. !define un.${StrFuncName} "${Un${StrFuncName}}"
  44. !macroend
  45. !insertmacro _IncludeStrFunction StrTok
  46. !insertmacro _IncludeStrFunction StrStr
  47. !insertmacro _IncludeStrFunction StrRep
  48. ; ---------------------------------- Macro Definitions ----------------------------------------
  49. !macro _EnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString
  50. Push "${EnvVarName}"
  51. Push "${Action}"
  52. Push "${RegLoc}"
  53. Push "${PathString}"
  54. Call EnvVarUpdate
  55. Pop "${ResultVar}"
  56. !macroend
  57. !define EnvVarUpdate '!insertmacro "_EnvVarUpdateConstructor"'
  58. !macro _unEnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString
  59. Push "${EnvVarName}"
  60. Push "${Action}"
  61. Push "${RegLoc}"
  62. Push "${PathString}"
  63. Call un.EnvVarUpdate
  64. Pop "${ResultVar}"
  65. !macroend
  66. !define un.EnvVarUpdate '!insertmacro "_unEnvVarUpdateConstructor"'
  67. ; ---------------------------------- Macro Definitions end-------------------------------------
  68. ;----------------------------------- EnvVarUpdate start----------------------------------------
  69. !define hklm_all_users 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
  70. !define hkcu_current_user 'HKCU "Environment"'
  71. !macro EnvVarUpdate UN
  72. Function ${UN}EnvVarUpdate
  73. Push $0
  74. Exch 4
  75. Exch $1
  76. Exch 3
  77. Exch $2
  78. Exch 2
  79. Exch $3
  80. Exch
  81. Exch $4
  82. Push $5
  83. Push $6
  84. Push $7
  85. Push $8
  86. Push $9
  87. Push $R0
  88. /* After this point:
  89. -------------------------
  90. $0 = ResultVar (returned)
  91. $1 = EnvVarName (input)
  92. $2 = Action (input)
  93. $3 = RegLoc (input)
  94. $4 = PathString (input)
  95. $5 = Orig EnvVar (read from registry)
  96. $6 = Len of $0 (temp)
  97. $7 = tempstr1 (temp)
  98. $8 = Entry counter (temp)
  99. $9 = tempstr2 (temp)
  100. $R0 = tempChar (temp) */
  101. ; Step 1: Read contents of EnvVarName from RegLoc
  102. ;
  103. ; Check for empty EnvVarName
  104. ${If} $1 == ""
  105. SetErrors
  106. DetailPrint "ERROR: EnvVarName is blank"
  107. Goto EnvVarUpdate_Restore_Vars
  108. ${EndIf}
  109. ; Check for valid Action
  110. ${If} $2 != "A"
  111. ${AndIf} $2 != "P"
  112. ${AndIf} $2 != "R"
  113. SetErrors
  114. DetailPrint "ERROR: Invalid Action - must be A, P, or R"
  115. Goto EnvVarUpdate_Restore_Vars
  116. ${EndIf}
  117. ${If} $3 == HKLM
  118. ReadRegStr $5 ${hklm_all_users} $1 ; Get EnvVarName from all users into $5
  119. ${ElseIf} $3 == HKCU
  120. ReadRegStr $5 ${hkcu_current_user} $1 ; Read EnvVarName from current user into $5
  121. ${Else}
  122. SetErrors
  123. DetailPrint 'ERROR: Action is [$3] but must be "HKLM" or HKCU"'
  124. Goto EnvVarUpdate_Restore_Vars
  125. ${EndIf}
  126. ; Check for empty PathString
  127. ${If} $4 == ""
  128. SetErrors
  129. DetailPrint "ERROR: PathString is blank"
  130. Goto EnvVarUpdate_Restore_Vars
  131. ${EndIf}
  132. Push $6
  133. Push $7
  134. Push $8
  135. StrLen $7 $4
  136. StrLen $6 $5
  137. IntOp $8 $6 + $7
  138. ${If} $5 == ""
  139. ${OrIf} $8 >= ${NSIS_MAX_STRLEN}
  140. SetErrors
  141. DetailPrint "Current $1 length ($6) too long to modify in NSIS; set manually if needed"
  142. Pop $8
  143. Pop $7
  144. Pop $6
  145. Goto EnvVarUpdate_Restore_Vars
  146. ${EndIf}
  147. Pop $8
  148. Pop $7
  149. Pop $6
  150. ; Make sure we've got some work to do
  151. ${If} $5 == ""
  152. ${AndIf} $2 == "R"
  153. SetErrors
  154. DetailPrint "$1 is empty - Nothing to remove"
  155. Goto EnvVarUpdate_Restore_Vars
  156. ${EndIf}
  157. ; Step 2: Scrub EnvVar
  158. ;
  159. StrCpy $0 $5 ; Copy the contents to $0
  160. ; Remove spaces around semicolons (NOTE: spaces before the 1st entry or
  161. ; after the last one are not removed here but instead in Step 3)
  162. ${If} $0 != "" ; If EnvVar is not empty ...
  163. ${Do}
  164. ${${UN}StrStr} $7 $0 " ;"
  165. ${If} $7 == ""
  166. ${ExitDo}
  167. ${EndIf}
  168. ${${UN}StrRep} $0 $0 " ;" ";" ; Remove '<space>;'
  169. ${Loop}
  170. ${Do}
  171. ${${UN}StrStr} $7 $0 "; "
  172. ${If} $7 == ""
  173. ${ExitDo}
  174. ${EndIf}
  175. ${${UN}StrRep} $0 $0 "; " ";" ; Remove ';<space>'
  176. ${Loop}
  177. ${Do}
  178. ${${UN}StrStr} $7 $0 ";;"
  179. ${If} $7 == ""
  180. ${ExitDo}
  181. ${EndIf}
  182. ${${UN}StrRep} $0 $0 ";;" ";"
  183. ${Loop}
  184. ; Remove a leading or trailing semicolon from EnvVar
  185. StrCpy $7 $0 1 0
  186. ${If} $7 == ";"
  187. StrCpy $0 $0 "" 1 ; Change ';<EnvVar>' to '<EnvVar>'
  188. ${EndIf}
  189. StrLen $6 $0
  190. IntOp $6 $6 - 1
  191. StrCpy $7 $0 1 $6
  192. ${If} $7 == ";"
  193. StrCpy $0 $0 $6 ; Change ';<EnvVar>' to '<EnvVar>'
  194. ${EndIf}
  195. ; DetailPrint "Scrubbed $1: [$0]" ; Uncomment to debug
  196. ${EndIf}
  197. /* Step 3. Remove all instances of the target path/string (even if "A" or "P")
  198. $6 = bool flag (1 = found and removed PathString)
  199. $7 = a string (e.g. path) delimited by semicolon(s)
  200. $8 = entry counter starting at 0
  201. $9 = copy of $0
  202. $R0 = tempChar */
  203. ${If} $5 != "" ; If EnvVar is not empty ...
  204. StrCpy $9 $0
  205. StrCpy $0 ""
  206. StrCpy $8 0
  207. StrCpy $6 0
  208. ${Do}
  209. ${${UN}StrTok} $7 $9 ";" $8 "0" ; $7 = next entry, $8 = entry counter
  210. ${If} $7 == "" ; If we've run out of entries,
  211. ${ExitDo} ; were done
  212. ${EndIf} ;
  213. ; Remove leading and trailing spaces from this entry (critical step for Action=Remove)
  214. ${Do}
  215. StrCpy $R0 $7 1
  216. ${If} $R0 != " "
  217. ${ExitDo}
  218. ${EndIf}
  219. StrCpy $7 $7 "" 1 ; Remove leading space
  220. ${Loop}
  221. ${Do}
  222. StrCpy $R0 $7 1 -1
  223. ${If} $R0 != " "
  224. ${ExitDo}
  225. ${EndIf}
  226. StrCpy $7 $7 -1 ; Remove trailing space
  227. ${Loop}
  228. ${If} $7 == $4 ; If string matches, remove it by not appending it
  229. StrCpy $6 1 ; Set 'found' flag
  230. ${ElseIf} $7 != $4 ; If string does NOT match
  231. ${AndIf} $0 == "" ; and the 1st string being added to $0,
  232. StrCpy $0 $7 ; copy it to $0 without a prepended semicolon
  233. ${ElseIf} $7 != $4 ; If string does NOT match
  234. ${AndIf} $0 != "" ; and this is NOT the 1st string to be added to $0,
  235. StrCpy $0 $0;$7 ; append path to $0 with a prepended semicolon
  236. ${EndIf} ;
  237. IntOp $8 $8 + 1 ; Bump counter
  238. ${Loop} ; Check for duplicates until we run out of paths
  239. ${EndIf}
  240. ; Step 4: Perform the requested Action
  241. ;
  242. ${If} $2 != "R" ; If Append or Prepend
  243. ${If} $6 == 1 ; And if we found the target
  244. DetailPrint "Target is already present in $1. It will be removed and"
  245. ${EndIf}
  246. ${If} $0 == "" ; If EnvVar is (now) empty
  247. StrCpy $0 $4 ; just copy PathString to EnvVar
  248. ${If} $6 == 0 ; If found flag is either 0
  249. ${OrIf} $6 == "" ; or blank (if EnvVarName is empty)
  250. DetailPrint "$1 was empty and has been updated with the target"
  251. ${EndIf}
  252. ${ElseIf} $2 == "A" ; If Append (and EnvVar is not empty),
  253. StrCpy $0 $0;$4 ; append PathString
  254. ${If} $6 == 1
  255. DetailPrint "appended to $1"
  256. ${Else}
  257. DetailPrint "Target was appended to $1"
  258. ${EndIf}
  259. ${Else} ; If Prepend (and EnvVar is not empty),
  260. StrCpy $0 $4;$0 ; prepend PathString
  261. ${If} $6 == 1
  262. DetailPrint "prepended to $1"
  263. ${Else}
  264. DetailPrint "Target was prepended to $1"
  265. ${EndIf}
  266. ${EndIf}
  267. ${Else} ; If Action = Remove
  268. ${If} $6 == 1 ; and we found the target
  269. DetailPrint "Target was found and removed from $1"
  270. ${Else}
  271. DetailPrint "Target was NOT found in $1 (nothing to remove)"
  272. ${EndIf}
  273. ${If} $0 == ""
  274. DetailPrint "$1 is now empty"
  275. ${EndIf}
  276. ${EndIf}
  277. ; Step 5: Update the registry at RegLoc with the updated EnvVar and announce the change
  278. ;
  279. ClearErrors
  280. ${If} $3 == HKLM
  281. WriteRegExpandStr ${hklm_all_users} $1 $0 ; Write it in all users section
  282. ${ElseIf} $3 == HKCU
  283. WriteRegExpandStr ${hkcu_current_user} $1 $0 ; Write it to current user section
  284. ${EndIf}
  285. IfErrors 0 +4
  286. MessageBox MB_OK|MB_ICONEXCLAMATION "Could not write updated $1 to $3"
  287. DetailPrint "Could not write updated $1 to $3"
  288. Goto EnvVarUpdate_Restore_Vars
  289. ; "Export" our change
  290. SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
  291. EnvVarUpdate_Restore_Vars:
  292. ;
  293. ; Restore the user's variables and return ResultVar
  294. Pop $R0
  295. Pop $9
  296. Pop $8
  297. Pop $7
  298. Pop $6
  299. Pop $5
  300. Pop $4
  301. Pop $3
  302. Pop $2
  303. Pop $1
  304. Push $0 ; Push my $0 (ResultVar)
  305. Exch
  306. Pop $0 ; Restore his $0
  307. FunctionEnd
  308. !macroend ; EnvVarUpdate UN
  309. !insertmacro EnvVarUpdate ""
  310. !insertmacro EnvVarUpdate "un."
  311. ;----------------------------------- EnvVarUpdate end----------------------------------------
  312. !verbose pop
  313. !endif