Environmental Variables: append, prepend, and remove entries
Author: turnec2 (talk, contrib) |
Description
- Function - EnvVarUpdate
- Version 1.1
Append, prepend or remove a pathname or string from an environmental variable such as PATH, LIB, CLASSPATH, PATHEXT, or a variable of your own. The variable can be updated in either the "all users" section of the registry (HKLM) or the "current user" section (HKCU).
Duplicates entries, leading and trailing spaces around each entry, and duplicate semicolons are permanently removed from the contents of the variable before performing the requested operation. This step prevents the corruption of the variable's contents (e.g., when removing a target pathname that is a subset of another pathname).
The function can be called from an installer or uninstaller. The "remove" subfunction is normally not required for installers; however, it might be useful for such things as removing pathnames associated with earlier revisions in the event that the user has failed to run the uninstaller.
The updated contents of the variable returned in ResultVar might be useful for displaying to the user, writing to a log file, or verification.
- Design
- Read the contents of EnvVarName from RegLoc
- Remove duplicate semicolons and spaces around semicolons
- Remove all instances of the target path/string regardless of the operation because this not only eliminates duplicate entries, it also allows for the search order of the target entry to be changed from first to last and vice versa.
- If Action is "R" (remove), skip this step, or else prepend or append the target per the requested Action.
- Write the updated variable to RegLoc even if the Action is "R" or the target is not found because this allows for the elimination of duplicate semicolons, spaces around semicolons, spaces preceding the first entry, and spaces following the last entry.
Usage
Warning
If you are using StrFunc.nsh header file then include it with all required definitions before this header to avoid conflicts.
Warning this code will replace paths rather than append if the existing path exceeds the maximum string length in the NSIS build you are using. Some setup crash can also occurs.
Default maximum string length is 1024, see Special_Builds for a 8192 max length.
The following patch is suggested to foolproof this problem. It has a remaining problem in that if the path is initially empty it will not allow adding to it. This could be fixed by adding a test to only use this patch when $1 is PATH, but then long non-path environments could be trashed. 129.130.40.208 23:37, 29 June 2011 (UTC)
> diff -c EnvVarUpdate.nsh /usr/share/nsis/Include/EnvVarUpdate-safe.nsh *** EnvVarUpdate.nsh 2011-06-28 16:45:20.000000000 -0500 --- /usr/share/nsis/Include/EnvVarUpdate-safe.nsh 2011-06-29 15:28:41.000000000 -0500 *************** *** 147,152 **** --- 147,172 ---- ;;khc - here check if length is going to be greater then max string length ;; and abort if so - also abort if original path empty - may mean ;; it was too long as well- write message to say set it by hand + + Push $6 + Push $7 + Push $8 + StrLen $7 $4 + StrLen $6 $5 + IntOp $8 $6 + $7 + ${If} $5 == "" + ${OrIf} $8 >= ${NSIS_MAX_STRLEN} + SetErrors + DetailPrint "Current $1 length ($6) too long to modify in NSIS; set manually if needed" + Pop $8 + Pop $7 + Pop $6 + Goto EnvVarUpdate_Restore_Vars + ${EndIf} + Pop $8 + Pop $7 + Pop $6 + ;;khc ; Make sure we've got some work to do ${If} $5 == "" ${AndIf} $2 == "R"
Syntax
${EnvVarUpdate} "ResultVar" "EnvVarName" "Action" "RegLoc" "PathString"
or
Push "EnvVarName" Push "Action" Push "RegLoc" Push "PathString" Call EnvVarUpdate Pop "ResultVar"
Parameters
- ResultVar
- Updated environmental variable returned by the function
- EnvVarName
- Environmental variable name such as "PATH", "LIB", or "MYVAR"
- Action
- "A" = Append
- "P" = Prepend
- "R" = Remove
- RegLoc
- "HKLM" = the "all users" section of the registry
- "HKCU" = the "current user" section
- PathString
- A pathname or string to add to or remove from the contents of EnvVarName (e.g., "C:\MyApp")
Examples
Installer Examples
${EnvVarUpdate} $0 "PATH" "A" "HKLM" "C:\Program Files\Windows Resource Kits\Tools" ; Append ${EnvVarUpdate} $0 "PATH" "P" "HKCU" "%WinDir%\System32" ; Prepend ${EnvVarUpdate} $0 "LIB" "R" "HKLM" "C:\MyLib" ; Remove ${EnvVarUpdate} $0 "PATH" "R" "HKLM" "C:\Program Files\MyApp-v1.0" ; Remove path of old rev ${EnvVarUpdate} $0 "PATH" "A" "HKLM" "C:\Program Files\MyApp-v2.0" ; Append the new one
Uninstaller Examples
${un.EnvVarUpdate} $0 "LIB" "R" "HKLM" "C:\MyLib" ${un.EnvVarUpdate} $0 "PATH" "R" "HKLM" "C:\MyApp-v2.0" ; Remove path of latest rev ${un.EnvVarUpdate} $0 "PATH" "A" "HKLM" "C:\MyApp-v1.0" ; Restore path of previous rev
Function Code
Download:
EnvVarUpdate.7z (3 KB)
/** * EnvVarUpdate.nsh * : Environmental Variables: append, prepend, and remove entries * * WARNING: If you use StrFunc.nsh header then include it before this file * with all required definitions. This is to avoid conflicts * * Usage: * ${EnvVarUpdate} "ResultVar" "EnvVarName" "Action" "RegLoc" "PathString" * * Credits: * Version 1.0 * * Cal Turney (turnec2) * * Amir Szekely (KiCHiK) and e-circ for developing the forerunners of this * function: AddToPath, un.RemoveFromPath, AddToEnvVar, un.RemoveFromEnvVar, * WriteEnvStr, and un.DeleteEnvStr * * Diego Pedroso (deguix) for StrTok * * Kevin English (kenglish_hi) for StrContains * * Hendri Adriaens (Smile2Me), Diego Pedroso (deguix), and Dan Fuhry * (dandaman32) for StrReplace * * Version 1.1 (compatibility with StrFunc.nsh) * * techtonik * * http://nsis.sourceforge.net/Environmental_Variables:_append%2C_prepend%2C_and_remove_entries * */ !ifndef ENVVARUPDATE_FUNCTION !define ENVVARUPDATE_FUNCTION !verbose push !verbose 3 !include "LogicLib.nsh" !include "WinMessages.NSH" !include "StrFunc.nsh" ; ---- Fix for conflict if StrFunc.nsh is already includes in main file ----------------------- !macro _IncludeStrFunction StrFuncName !ifndef ${StrFuncName}_INCLUDED ${${StrFuncName}} !endif !ifndef Un${StrFuncName}_INCLUDED ${Un${StrFuncName}} !endif !define un.${StrFuncName} "${Un${StrFuncName}}" !macroend !insertmacro _IncludeStrFunction StrTok !insertmacro _IncludeStrFunction StrStr !insertmacro _IncludeStrFunction StrRep ; ---------------------------------- Macro Definitions ---------------------------------------- !macro _EnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString Push "${EnvVarName}" Push "${Action}" Push "${RegLoc}" Push "${PathString}" Call EnvVarUpdate Pop "${ResultVar}" !macroend !define EnvVarUpdate '!insertmacro "_EnvVarUpdateConstructor"' !macro _unEnvVarUpdateConstructor ResultVar EnvVarName Action Regloc PathString Push "${EnvVarName}" Push "${Action}" Push "${RegLoc}" Push "${PathString}" Call un.EnvVarUpdate Pop "${ResultVar}" !macroend !define un.EnvVarUpdate '!insertmacro "_unEnvVarUpdateConstructor"' ; ---------------------------------- Macro Definitions end------------------------------------- ;----------------------------------- EnvVarUpdate start---------------------------------------- !define hklm_all_users 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' !define hkcu_current_user 'HKCU "Environment"' !macro EnvVarUpdate UN Function ${UN}EnvVarUpdate Push $0 Exch 4 Exch $1 Exch 3 Exch $2 Exch 2 Exch $3 Exch Exch $4 Push $5 Push $6 Push $7 Push $8 Push $9 Push $R0 /* After this point: ------------------------- $0 = ResultVar (returned) $1 = EnvVarName (input) $2 = Action (input) $3 = RegLoc (input) $4 = PathString (input) $5 = Orig EnvVar (read from registry) $6 = Len of $0 (temp) $7 = tempstr1 (temp) $8 = Entry counter (temp) $9 = tempstr2 (temp) $R0 = tempChar (temp) */ ; Step 1: Read contents of EnvVarName from RegLoc ; ; Check for empty EnvVarName ${If} $1 == "" SetErrors DetailPrint "ERROR: EnvVarName is blank" Goto EnvVarUpdate_Restore_Vars ${EndIf} ; Check for valid Action ${If} $2 != "A" ${AndIf} $2 != "P" ${AndIf} $2 != "R" SetErrors DetailPrint "ERROR: Invalid Action - must be A, P, or R" Goto EnvVarUpdate_Restore_Vars ${EndIf} ${If} $3 == HKLM ReadRegStr $5 ${hklm_all_users} $1 ; Get EnvVarName from all users into $5 ${ElseIf} $3 == HKCU ReadRegStr $5 ${hkcu_current_user} $1 ; Read EnvVarName from current user into $5 ${Else} SetErrors DetailPrint 'ERROR: Action is [$3] but must be "HKLM" or HKCU"' Goto EnvVarUpdate_Restore_Vars ${EndIf} ; Check for empty PathString ${If} $4 == "" SetErrors DetailPrint "ERROR: PathString is blank" Goto EnvVarUpdate_Restore_Vars ${EndIf} ; Make sure we've got some work to do ${If} $5 == "" ${AndIf} $2 == "R" SetErrors DetailPrint "$1 is empty - Nothing to remove" Goto EnvVarUpdate_Restore_Vars ${EndIf} ; Step 2: Scrub EnvVar ; StrCpy $0 $5 ; Copy the contents to $0 ; Remove spaces around semicolons (NOTE: spaces before the 1st entry or ; after the last one are not removed here but instead in Step 3) ${If} $0 != "" ; If EnvVar is not empty ... ${Do} ${${UN}StrStr} $7 $0 " ;" ${If} $7 == "" ${ExitDo} ${EndIf} ${${UN}StrRep} $0 $0 " ;" ";" ; Remove '<space>;' ${Loop} ${Do} ${${UN}StrStr} $7 $0 "; " ${If} $7 == "" ${ExitDo} ${EndIf} ${${UN}StrRep} $0 $0 "; " ";" ; Remove ';<space>' ${Loop} ${Do} ${${UN}StrStr} $7 $0 ";;" ${If} $7 == "" ${ExitDo} ${EndIf} ${${UN}StrRep} $0 $0 ";;" ";" ${Loop} ; Remove a leading or trailing semicolon from EnvVar StrCpy $7 $0 1 0 ${If} $7 == ";" StrCpy $0 $0 "" 1 ; Change ';<EnvVar>' to '<EnvVar>' ${EndIf} StrLen $6 $0 IntOp $6 $6 - 1 StrCpy $7 $0 1 $6 ${If} $7 == ";" StrCpy $0 $0 $6 ; Change ';<EnvVar>' to '<EnvVar>' ${EndIf} ; DetailPrint "Scrubbed $1: [$0]" ; Uncomment to debug ${EndIf} /* Step 3. Remove all instances of the target path/string (even if "A" or "P") $6 = bool flag (1 = found and removed PathString) $7 = a string (e.g. path) delimited by semicolon(s) $8 = entry counter starting at 0 $9 = copy of $0 $R0 = tempChar */ ${If} $5 != "" ; If EnvVar is not empty ... StrCpy $9 $0 StrCpy $0 "" StrCpy $8 0 StrCpy $6 0 ${Do} ${${UN}StrTok} $7 $9 ";" $8 "0" ; $7 = next entry, $8 = entry counter ${If} $7 == "" ; If we've run out of entries, ${ExitDo} ; were done ${EndIf} ; ; Remove leading and trailing spaces from this entry (critical step for Action=Remove) ${Do} StrCpy $R0 $7 1 ${If} $R0 != " " ${ExitDo} ${EndIf} StrCpy $7 $7 "" 1 ; Remove leading space ${Loop} ${Do} StrCpy $R0 $7 1 -1 ${If} $R0 != " " ${ExitDo} ${EndIf} StrCpy $7 $7 -1 ; Remove trailing space ${Loop} ${If} $7 == $4 ; If string matches, remove it by not appending it StrCpy $6 1 ; Set 'found' flag ${ElseIf} $7 != $4 ; If string does NOT match ${AndIf} $0 == "" ; and the 1st string being added to $0, StrCpy $0 $7 ; copy it to $0 without a prepended semicolon ${ElseIf} $7 != $4 ; If string does NOT match ${AndIf} $0 != "" ; and this is NOT the 1st string to be added to $0, StrCpy $0 $0;$7 ; append path to $0 with a prepended semicolon ${EndIf} ; IntOp $8 $8 + 1 ; Bump counter ${Loop} ; Check for duplicates until we run out of paths ${EndIf} ; Step 4: Perform the requested Action ; ${If} $2 != "R" ; If Append or Prepend ${If} $6 == 1 ; And if we found the target DetailPrint "Target is already present in $1. It will be removed and" ${EndIf} ${If} $0 == "" ; If EnvVar is (now) empty StrCpy $0 $4 ; just copy PathString to EnvVar ${If} $6 == 0 ; If found flag is either 0 ${OrIf} $6 == "" ; or blank (if EnvVarName is empty) DetailPrint "$1 was empty and has been updated with the target" ${EndIf} ${ElseIf} $2 == "A" ; If Append (and EnvVar is not empty), StrCpy $0 $0;$4 ; append PathString ${If} $6 == 1 DetailPrint "appended to $1" ${Else} DetailPrint "Target was appended to $1" ${EndIf} ${Else} ; If Prepend (and EnvVar is not empty), StrCpy $0 $4;$0 ; prepend PathString ${If} $6 == 1 DetailPrint "prepended to $1" ${Else} DetailPrint "Target was prepended to $1" ${EndIf} ${EndIf} ${Else} ; If Action = Remove ${If} $6 == 1 ; and we found the target DetailPrint "Target was found and removed from $1" ${Else} DetailPrint "Target was NOT found in $1 (nothing to remove)" ${EndIf} ${If} $0 == "" DetailPrint "$1 is now empty" ${EndIf} ${EndIf} ; Step 5: Update the registry at RegLoc with the updated EnvVar and announce the change ; ClearErrors ${If} $3 == HKLM WriteRegExpandStr ${hklm_all_users} $1 $0 ; Write it in all users section ${ElseIf} $3 == HKCU WriteRegExpandStr ${hkcu_current_user} $1 $0 ; Write it to current user section ${EndIf} IfErrors 0 +4 MessageBox MB_OK|MB_ICONEXCLAMATION "Could not write updated $1 to $3" DetailPrint "Could not write updated $1 to $3" Goto EnvVarUpdate_Restore_Vars ; "Export" our change SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 EnvVarUpdate_Restore_Vars: ; ; Restore the user's variables and return ResultVar Pop $R0 Pop $9 Pop $8 Pop $7 Pop $6 Pop $5 Pop $4 Pop $3 Pop $2 Pop $1 Push $0 ; Push my $0 (ResultVar) Exch Pop $0 ; Restore his $0 FunctionEnd !macroend ; EnvVarUpdate UN !insertmacro EnvVarUpdate "" !insertmacro EnvVarUpdate "un." ;----------------------------------- EnvVarUpdate end---------------------------------------- !verbose pop !endif