/**
*  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