Environment.ahk - Change User and System Environment Variables Permanently

Post your working scripts, libraries and tools for AHK v1.1 and older
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Environment.ahk - Change User and System Environment Variables Permanently

24 Apr 2017, 16:42

Environment.ahk
Provides two sets of functions, one starting with "Env_UserXXX" and another starting with "Env_SystemXXX"

Features

  • Automatic REG_SZ and REG_EXPAND_SZ detection
  • Backup before you make any changes with EnvUserBackup() and EnvSystemBackup()
  • Sort your messy Windows PATH in alphabetical order.
  • Edit both system and user path with separate commands.
  • Broadcast changes to PATH in the current AutoHotKey script and System-wide.
Backup First

Code: Select all

Env_UserBackup()    ; Creates a backup of the user's environment variables. 
Env_SystemBackup()  ; Creates a backup of the system environment variables.
Stuff you can do

Code: Select all

; Add your binaries to the user PATH
Env_UserAdd("PATH", "C:\bin")
; Remove a directory from user PATH
Env_UserSub("PATH", "C:\bin")
; Create a new Environment Variable
Env_UserNew("ANSWER", "42")
; Read an existing Environment Variable
key := Env_UserRead("ANSWER") ; returns 42
; Delete an Environment Variable
Env_UserDel("ANSWER")
Example System Commands - RUN AS ADMINISTRATOR (except EnvSystemRead() and EnvSystemBackup())

Code: Select all

; Use EnvSystem to edit the System Environment Variables
Env_SystemAdd("PATH", "X:\Backup\bin")
; Sort System PATH in alphabetical order
Env_SystemSort("PATH")
; Remove pesky duplicate entries in your system PATH
Env_SystemRemoveDuplicates("PATH")
Full Script

Code: Select all

; Script     Environment.ahk
; License:   MIT License
; Author:    Edison Hua (iseahound)
; Github:    https://github.com/iseahound/Environment.ahk
; Date       2021-02-05
; Version    1.0.0
;
; ExpandEnvironmentStrings(), RefreshEnvironment()   by NoobSawce + DavidBiesack (modified by BatRamboZPM)
;   https://autohotkey.com/board/topic/63312-reload-systemuser-environment-variables/
;
; Global Error Values
;   0 - Success.
;  -1 - Error when writing value to registry.
;  -2 - Value already added or value already deleted.
;  -3 - Need to Run As Administrator.
;
; Notes
;   SendMessage 0x1A, 0, "Environment",, ahk_id 0xFFFF ; 0x1A is WM_SETTINGCHANGE
;      - The above code will broadcast a message stating there has been a change of environment variables.
;      - Some programs have not implemented this message.
;      - v1.00 replaces this with a powershell command using asyncronous execution providing 10x speedup.
;   RefreshEnvironment()
;      - This function will update the environment variables within AutoHotkey.
;      - Command prompts launched by AutoHotkey inherit AutoHotkey's environment.
;   Any command prompts currently open will not have their environment variables changed.
;      - Please use the RefreshEnv.cmd batch script found at:
;        https://github.com/chocolatey-archive/chocolatey/blob/master/src/redirects/RefreshEnv.cmd

#Requires AutoHotkey v1.1.33+

Env_UserAdd(name, value, type := "", location := ""){
   value    := (value ~= "^\.\.\\") ? GetFullPathName(value) : value
   location := (location == "")     ? "HKCU\Environment"     : location

   RegRead registry, % location, % name
   if (ErrorLevel)
      return -1
   Loop Parse, registry, % ";"
      if (A_LoopField == value)
         return -2
   registry .= (registry ~= "(^$|;$)") ? "" : ";"
   value := registry . value
   type := (type) ? type : (value ~= "%") ? "REG_EXPAND_SZ" : "REG_SZ"
   RegWrite % type, % location, % name, % value
   SettingChange()
   RefreshEnvironment()
   return (ErrorLevel) ? -1 : 0
}

Env_SystemAdd(name, value, type := ""){
   return (A_IsAdmin) ? Env_UserAdd(name, value, type, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment") : -3
}

Env_UserSub(name, value, type := "", location := ""){
   value    := (value ~= "^\.\.\\") ? GetFullPathName(value) : value
   location := (location == "")     ? "HKCU\Environment"     : location

   RegRead registry, % location, % name
   if ErrorLevel
      return -2

   Loop Parse, registry, % ";"
      if (A_LoopField != value) {
         output .= (A_Index > 1 && output != "") ? ";" : ""
         output .= A_LoopField
      }

   if (output == registry)
      return -2

   if (output != "") {
      type := (type) ? type : (output ~= "%") ? "REG_EXPAND_SZ" : "REG_SZ"
      RegWrite % type, % location, % name, % output
   }
   else
      RegDelete % location, % name
   SettingChange()
   RefreshEnvironment()
   return (ErrorLevel) ? -1 : 0
}

Env_SystemSub(name, value, type := ""){
   return (A_IsAdmin) ? Env_UserSub(name, value, type, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment") : -3
}

Env_UserNew(name, value := "", type := "", location := ""){
   type := (type) ? type : (value ~= "%") ? "REG_EXPAND_SZ" : "REG_SZ"
   RegWrite % type, % (location == "") ? "HKCU\Environment" : location, % name, % value
   SettingChange()
   RefreshEnvironment()
   return (ErrorLevel) ? -1 : 0
}

Env_SystemNew(name, value := "", type := ""){
   return (A_IsAdmin) ? Env_UserNew(name, value, type, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment") : -3
}

; Value does nothing except let me easily change between functions.
Env_UserDel(name, value := "", location := ""){
   RegDelete % (location == "") ? "HKCU\Environment" : location, % name
   SettingChange()
   RefreshEnvironment()
   return 0
}

Env_SystemDel(name, value := ""){
   return (A_IsAdmin) ? Env_UserDel(name, value, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment") : -3
}

Env_UserRead(name, value := "", location := ""){
   RegRead registry, % (location == "") ? "HKCU\Environment" : location, % name
   if (value != "") {
      Loop Parse, registry, % ";"
         if (A_LoopField = value)
            return A_LoopField
      return ; Value not found
   }
   return registry
}

Env_SystemRead(name, value := ""){
   return Env_UserRead(name, value, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment")
}

; Value does nothing except let me easily change between functions.
Env_UserSort(name, value := "", location := ""){
   RegRead registry, % (location == "") ? "HKCU\Environment" : location, % name
   Sort registry, % "D;"
   type := (type) ? type : (registry ~= "%") ? "REG_EXPAND_SZ" : "REG_SZ"
   RegWrite % type, % (location == "") ? "HKCU\Environment" : location, % name, % registry
   return (ErrorLevel) ? -1 : 0
}

Env_SystemSort(name, value := ""){
   return (A_IsAdmin) ? Env_UserSort(name, value, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment") : -3
}

; Value does nothing except let me easily change between functions.
Env_UserRemoveDuplicates(name, value := "", location := ""){
   RegRead registry, % (location == "") ? "HKCU\Environment" : location, % name
   Sort registry, % "U D;"
   type := (type) ? type : (registry ~= "%") ? "REG_EXPAND_SZ" : "REG_SZ"
   RegWrite % type, % (location == "") ? "HKCU\Environment" : location, % name, % registry
   return (ErrorLevel) ? -1 : 0
}

Env_SystemRemoveDuplicates(name, value := ""){
   return (A_IsAdmin) ? Env_UserRemoveDuplicates(name, value, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment") : -3
}

Env_UserBackup(fileName := "UserEnvironment.reg", location := ""){
   _cmd .= (A_Is64bitOS != A_PtrSize >> 3)    ? A_WinDir "\SysNative\cmd.exe"   : A_ComSpec
   _cmd .= " /K " Chr(0x22) "reg export " Chr(0x22)
   _cmd .= (location == "")                   ? "HKCU\Environment" : location
   _cmd .= Chr(0x22) " " Chr(0x22)
   _cmd .= fileName
   _cmd .= Chr(0x22) . Chr(0x22) . " && pause && exit"
   try RunWait % _cmd
   catch
      return "FAIL"
   return "SUCCESS"
}

Env_SystemBackup(fileName := "SystemEnvironment.reg"){
   return Env_UserBackup(fileName, "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment")
}

Env_UserRestore(fileName := "UserEnvironment.reg"){
   try RunWait % fileName
   catch
      return "FAIL"
   return "SUCCESS"
}

Env_SystemRestore(fileName := "SystemEnvironment.reg"){
   try RunWait % fileName
   catch
      return "FAIL"
   return "SUCCESS"
}


RefreshEnvironment()
{
   Path := ""
   PathExt := ""
   RegKeys := "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment,HKCU\Environment"
   Loop Parse, RegKeys, CSV
   {
      Loop Reg, % A_LoopField, V
      {
         RegRead Value
         If (A_LoopRegType == "REG_EXPAND_SZ" && !ExpandEnvironmentStrings(Value))
            Continue
         If (A_LoopRegName = "PATH")
            Path .= Value . ";"
         Else If (A_LoopRegName = "PATHEXT")
            PathExt .= Value . ";"
         Else
            EnvSet % A_LoopRegName, % Value
      }
   }
   EnvSet PATH, % Path
   EnvSet PATHEXT, % PathExt
}

ExpandEnvironmentStrings(ByRef vInputString)
{
   ; get the required size for the expanded string
   vSizeNeeded := DllCall("ExpandEnvironmentStrings", "Str", vInputString, "Int", 0, "Int", 0)
   If (vSizeNeeded == "" || vSizeNeeded <= 0)
      return False ; unable to get the size for the expanded string for some reason

   vByteSize := vSizeNeeded + 1
   VarSetCapacity(vTempValue, vByteSize*(A_IsUnicode?2:1))

   ; attempt to expand the environment string
   If (!DllCall("ExpandEnvironmentStrings", "Str", vInputString, "Str", vTempValue, "Int", vSizeNeeded))
      return False ; unable to expand the environment string
   vInputString := vTempValue

   ; return success
   Return True
}

GetFullPathName(path) {
    cc := DllCall("GetFullPathName", "str", path, "uint", 0, "ptr", 0, "ptr", 0, "uint")
    VarSetCapacity(buf, cc*(A_IsUnicode?2:1))
    DllCall("GetFullPathName", "str", path, "uint", cc, "str", buf, "ptr", 0, "uint")
    return buf
}


; Source: https://gist.github.com/alphp/78fffb6d69e5bb863c76bbfc767effda
/*
$Script = @'
Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"
  [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
  public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);
"@

function Send-SettingChange {
  $HWND_BROADCAST = [IntPtr] 0xffff;
  $WM_SETTINGCHANGE = 0x1a;
  $result = [UIntPtr]::Zero

  [void] ([Win32.Nativemethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, "Environment", 2, 5000, [ref] $result))
}

Send-SettingChange;
'@

$ByteScript  = [System.Text.Encoding]::Unicode.GetBytes($Script)
[System.Convert]::ToBase64String($ByteScript)
*/

; To verify the encoded command, start a powershell terminal and paste the script above.
; 10x faster than SendMessage 0x1A, 0, "Environment",, ahk_id 0xFFFF ; 0x1A is WM_SETTINGCHANGE
SettingChange() {

   static _cmd := "
   ( LTrim
   QQBkAGQALQBUAHkAcABlACAALQBOAGEAbQBlAHMAcABhAGMAZQAgAFcAaQBuADMA
   MgAgAC0ATgBhAG0AZQAgAE4AYQB0AGkAdgBlAE0AZQB0AGgAbwBkAHMAIAAtAE0A
   ZQBtAGIAZQByAEQAZQBmAGkAbgBpAHQAaQBvAG4AIABAACIACgAgACAAWwBEAGwA
   bABJAG0AcABvAHIAdAAoACIAdQBzAGUAcgAzADIALgBkAGwAbAAiACwAIABTAGUA
   dABMAGEAcwB0AEUAcgByAG8AcgAgAD0AIAB0AHIAdQBlACwAIABDAGgAYQByAFMA
   ZQB0ACAAPQAgAEMAaABhAHIAUwBlAHQALgBBAHUAdABvACkAXQAKACAAIABwAHUA
   YgBsAGkAYwAgAHMAdABhAHQAaQBjACAAZQB4AHQAZQByAG4AIABJAG4AdABQAHQA
   cgAgAFMAZQBuAGQATQBlAHMAcwBhAGcAZQBUAGkAbQBlAG8AdQB0ACgASQBuAHQA
   UAB0AHIAIABoAFcAbgBkACwAIAB1AGkAbgB0ACAATQBzAGcALAAgAFUASQBuAHQA
   UAB0AHIAIAB3AFAAYQByAGEAbQAsACAAcwB0AHIAaQBuAGcAIABsAFAAYQByAGEA
   bQAsACAAdQBpAG4AdAAgAGYAdQBGAGwAYQBnAHMALAAgAHUAaQBuAHQAIAB1AFQA
   aQBtAGUAbwB1AHQALAAgAG8AdQB0ACAAVQBJAG4AdABQAHQAcgAgAGwAcABkAHcA
   UgBlAHMAdQBsAHQAKQA7AAoAIgBAAAoACgBmAHUAbgBjAHQAaQBvAG4AIABTAGUA
   bgBkAC0AUwBlAHQAdABpAG4AZwBDAGgAYQBuAGcAZQAgAHsACgAgACAAJABIAFcA
   TgBEAF8AQgBSAE8AQQBEAEMAQQBTAFQAIAA9ACAAWwBJAG4AdABQAHQAcgBdACAA
   MAB4AGYAZgBmAGYAOwAKACAAIAAkAFcATQBfAFMARQBUAFQASQBOAEcAQwBIAEEA
   TgBHAEUAIAA9ACAAMAB4ADEAYQA7AAoAIAAgACQAcgBlAHMAdQBsAHQAIAA9ACAA
   WwBVAEkAbgB0AFAAdAByAF0AOgA6AFoAZQByAG8ACgAKACAAIABbAHYAbwBpAGQA
   XQAgACgAWwBXAGkAbgAzADIALgBOAGEAdABpAHYAZQBtAGUAdABoAG8AZABzAF0A
   OgA6AFMAZQBuAGQATQBlAHMAcwBhAGcAZQBUAGkAbQBlAG8AdQB0ACgAJABIAFcA
   TgBEAF8AQgBSAE8AQQBEAEMAQQBTAFQALAAgACQAVwBNAF8AUwBFAFQAVABJAE4A
   RwBDAEgAQQBOAEcARQAsACAAWwBVAEkAbgB0AFAAdAByAF0AOgA6AFoAZQByAG8A
   LAAgACIARQBuAHYAaQByAG8AbgBtAGUAbgB0ACIALAAgADIALAAgADUAMAAwADAA
   LAAgAFsAcgBlAGYAXQAgACQAcgBlAHMAdQBsAHQAKQApAAoAfQAKAAoAUwBlAG4A
   ZAAtAFMAZQB0AHQAaQBuAGcAQwBoAGEAbgBnAGUAOwA=
   )"
   Run % "powershell -NoProfile -EncodedCommand " _cmd,, Hide
}
Feel free to submit any bugs.
https://github.com/iseahound/Environment.ahk <- LATEST VERSION
Last edited by iseahound on 20 Jan 2022, 12:32, edited 7 times in total.
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Environment.ahk - Change User and System Environment Variables Permanently

03 Aug 2017, 11:27

Updated with a remove duplicates function.

Code: Select all

; Run this script as Admin
; Removes Duplicates in the System PATH
Env_SystemRemoveDuplicates("PATH")
Last edited by iseahound on 09 Sep 2017, 09:41, edited 1 time in total.
User avatar
Bon
Posts: 17
Joined: 11 Jan 2014, 07:31

Re: Environment.ahk - Change User and System Environment Variables Permanently

04 Aug 2017, 09:57

Cool! But I would suggest a name change of all functions to Env_xxx, making them standard lib compliant. Then you can just drop the main script (renamed Env.ahk) in the \Lib folder.
Quidquid Latine dictum sit altum videtur
"Anything said in Latin sounds profound"
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Environment.ahk - Change User and System Environment Variables Permanently

09 Sep 2017, 09:44

Bon wrote:Cool! But I would suggest a name change of all functions to Env_xxx, making them standard lib compliant. Then you can just drop the main script (renamed Env.ahk) in the \Lib folder.
Done. But you'll have to rename the script from Environment.ahk to Env.ahk yourself. It might not be obvious to most people that Env is short for Environment.
User avatar
Bon
Posts: 17
Joined: 11 Jan 2014, 07:31

Re: Environment.ahk - Change User and System Environment Variables Permanently

10 Sep 2017, 03:10

Thanks! I did notice a couple of misspellings, though - SystemEnviroment vs SystemEnvironment. I corrected my downloaded version; not sure of possible implications leaving them in. Maybe none at all?
Quidquid Latine dictum sit altum videtur
"Anything said in Latin sounds profound"
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Environment.ahk - Change User and System Environment Variables Permanently

10 Sep 2017, 09:23

Bon wrote:Thanks! I did notice a couple of misspellings, though - SystemEnviroment vs SystemEnvironment. I corrected my downloaded version; not sure of possible implications leaving them in. Maybe none at all?
Fixed! You've exposed my reliance on AHK's AutoCorrect scripts :c
Pintu
Posts: 4
Joined: 02 Nov 2017, 10:46

Re: Environment.ahk - Change User and System Environment Variables Permanently

02 Nov 2017, 10:52

A very nice script, I have been looking for this for a while.

I have noticed that reg.exe and cmd.exe stay open even though all entries have been added to path. I am using Env_SystemAdd() and Env_SystemRemoveDuplicates().

I have changed "run" to "runwait", and it seems to take a very long time to complete all commands. Any idea why that might be the case?
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Environment.ahk - Change User and System Environment Variables Permanently

02 Nov 2017, 16:27

Fixed.

Issue was due to spamming the Env_SystemBackup() more than once, prompting an overwrite of the existing file. It now displays the user prompt, and you can choose to overwrite the existing file or not.

EDIT: Forgot to change the "recent" date in the script, but I can assure you the bug has been fixed.
Pintu
Posts: 4
Joined: 02 Nov 2017, 10:46

Re: Environment.ahk - Change User and System Environment Variables Permanently

11 Nov 2017, 12:44

Excellent, thank you.

I would still suggest doing "runwait" instead of "run". Then you can output a success message when everything has run through.
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Environment.ahk - Change User and System Environment Variables Permanently

12 Nov 2017, 03:02

Pintu wrote:I would still suggest doing "runwait" instead of "run". Then you can output a success message when everything has run through.
Done. Outputs "SUCCESS" if backup works, and "FAIL" if not. Thanks for pointing out something I overlooked.
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Environment.ahk - Change User and System Environment Variables Permanently

06 Dec 2019, 17:23

Added a quick update to automatically detect relative paths. If you were thinking about using code like:

Code: Select all

Env_UserAdd("PATH", "..\bin")
it now works!
iseahound
Posts: 1444
Joined: 13 Aug 2016, 21:04
Contact:

Re: Environment.ahk - Change User and System Environment Variables Permanently

05 Feb 2021, 16:11

I changed the code a bit to execute SendMessage asynchronously. This provides a 10x speedup as the function no longer has to wait for explorer.exe + other applications to reset their environment variables. This will not affect your autohotkey scripts, as the autohotkey environment variables are reloaded using a separate function.

There is now a small possibility of a data race when you use autohotkey to ask an open explorer window to start a cmd window. Since most scripts will use the run command to directly run cmd.exe, the vast majority of users will be unaffected, and will enjoy a substantial speed up when loading their scripts.

Code: Select all

#If WinActive("ahk_class ExploreWClass") || WinActive("ahk_class CabinetWClass")
q::
Env_UserNew("pokemon", "898")
Send !f
Sleep 20 ; Increase this sleep to 500 to win the data race.
Send r
WinWait Windows PowerShell
WinActivate
Send echo $env:pokemon{Enter}
Env_UserDel("pokemon", "898")
return
#If

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: Chunjee, gwarble, jchestnut and 71 guests