Skip to content

Instantly share code, notes, and snippets.

@takase1121
Last active June 27, 2021 08:48
Show Gist options
  • Save takase1121/018859f323f8f369cb7b0f607d658a9e to your computer and use it in GitHub Desktop.
Save takase1121/018859f323f8f369cb7b0f607d658a9e to your computer and use it in GitHub Desktop.
Add exe files to user PATH

This Python script is used to add new executables to PATH.

  1. It recursively searches for .exe in the specified path.
  2. After that, it will compare what it finds in the path to PATH variable in the registry.
  3. It will remove invalid paths and add new ones to it.
  4. It saves the new PATH back to registry and sends a WM_SETTINGSCHANGE message so that apps update their environment variables.
$cwd = $PSScriptRoot.ToLower()
$excludes = ,"*antidupl*"
function excluded {
foreach ($e in $excludes) {
if ($_ -like $e) { return $true }
}
return $false
}
$path_set = [Environment]::GetEnvironmentVariables("User").Path.ToLower() -split ";"
$prog = Get-ChildItem -Path $cwd -Filter "*.exe" -Recurse -File | %{ $_.DirectoryName.ToLower() } | Select-Object -Unique | ?{ -not (excluded $_) }
$new_path = ($path_set | ?{ -not $_.StartsWith($cwd) }) + $prog | Sort-Object -Unique
Write-Host "Environment Variables (Modified)"
Write-Host ($new_path | Format-List | Out-String)
$response = $host.ui.PromptForChoice("Modify user PATH", "Do you want to modify user PATH?", ("&Yes", "&No"), 0)
if ($response -eq 0) {
$new_path = $new_path -join ";"
[Environment]::SetEnvironmentVariable("PATH", $new_path, "User")
Write-Host "Environment variable set!"
} else {
Write-Host "Discarding changes..."
}
import re
import winreg
from pathlib import Path
from ctypes import windll
from configparser import ConfigParser
def replace_placeholder(s):
"""Replace all placeholder with the correct string"""
return re.sub(r"%[a-zA-Z]+%", lambda x: winreg.ExpandEnvironmentStrings(x[0]), s)
def match_any_parent(path, pattern):
"""Matches every parent of path against pattern"""
return any(p.match(pattern) for p in path.parents)
def get_user_path(reg):
"""
Get user path variable
returns {Path: str} where Path is the expanded form while str is the raw string
"""
env_key = winreg.OpenKeyEx(reg, "Environment", access=winreg.KEY_QUERY_VALUE)
path, regtype = winreg.QueryValueEx(env_key, "PATH")
if regtype != winreg.REG_EXPAND_SZ:
raise Exception("PATH not found in registry")
# remove ; and whitespaces so later we don't have to
path = path.strip(" ;")
fullpath = replace_placeholder(path)
return {Path(p): q for p, q in zip(fullpath.split(";"), path.split(";"))}
def set_user_path(reg, path):
"""Set user path and send WM_SETTINGSCHANGE"""
env_key = winreg.OpenKeyEx(reg, "Environment", access=winreg.KEY_SET_VALUE)
winreg.SetValueEx(env_key, "PATH", 0, winreg.REG_EXPAND_SZ, ";".join(path))
winreg.FlushKey(env_key)
# 0xFFFF - HWND_BROADCAST
# 0x001A - WM_SETTINGSCHANGE
windll.user32.SendMessageA(0xFFFF, 0x001A, None, "Environment")
def get_current_path(path, excl):
"""Get candidates to add to PATH"""
return {
p.parent
for p in path.rglob("*.exe")
if not any(match_any_parent(p, e) for e in excl)
}
def main():
"""Main function"""
config = ConfigParser()
config.read("config.ini")
cwd = Path(config["DEFAULT"]["path"]).expanduser()
excludes = {
name.strip() for name in config["DEFAULT"]["excludes"].split(",") if name
}
# read the registry
reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
user_path = get_user_path(reg)
current_path = get_current_path(cwd, excludes)
user_path_set = set(user_path.keys())
# path that is related (managed)
related_path = {p for p in user_path_set if cwd in p.parents}
# unrelated path (not managed)
unrelated_path = user_path_set - related_path
if related_path != current_path:
# paths that should be removed from env
rem_path = {p for p in related_path if p not in current_path}
new_path = unrelated_path | current_path - rem_path
# replace some Path instances with value in registry
new_path = sorted(user_path.get(p, str(p)) for p in new_path)
print("DIRECTORIES TO ADD")
print("==================")
for p in related_path - current_path:
print(p)
print("DIRECTORIES TO REMOVE")
print("=====================")
for p in rem_path:
print(p)
set_user_path(reg, new_path)
print("PATH set!")
else:
print("No change detected")
if __name__ == "__main__":
main()
[DEFAULT]
path = ~/Documents/stuffs/app
excludes =
cmder/vendor/,
antidupl/,
kitty/,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment