从Python修改Windows环境variables的接口

我如何持久地从Python脚本中修改Windows环境variables? (这是setup.py脚本)

我正在寻找一个标准的function或模块来使用这个。 我已经熟悉registry的做法 ,但任何意见也是受欢迎的。

使用setx有一些缺点,特别是如果你试图附加到环境变量(例如,setx PATH%Path%; C:\ mypath),每次运行时都会重复追加到路径中,这可能是一个问题。 更糟糕的是,它没有区分机器路径(存储在HKEY_LOCAL_MACHINE中)和用户路径(存储在HKEY_CURRENT_USER中)。 您在命令提示符下看到的环境变量由这两个值的串联组成。 因此,在调用setx之​​前:

user PATH == u machine PATH == m %PATH% == m;u > setx PATH %PATH%;new Calling setx sets the USER path by default, hence now: user PATH == m;u;new machine PATH == m %PATH% == m;m;u;new 

每次调用setx附加到PATH时,系统路径都不可避免地在%PATH%环境变量中重复出现。 这些更改是永久性的,不会因重新启动而重置,因此会在整个机器的使用期限内累积。

在DOS下试图弥补这一点是超出我的能力。 所以我转向了Python。 我今天提出的解决方案是通过调整注册表来设置环境变量,包括在没有重复的情况下添加到PATH中,如下所示:

 from os import system, environ import win32con from win32gui import SendMessage from _winreg import ( CloseKey, OpenKey, QueryValueEx, SetValueEx, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, KEY_ALL_ACCESS, KEY_READ, REG_EXPAND_SZ, REG_SZ ) def env_keys(user=True): if user: root = HKEY_CURRENT_USER subkey = 'Environment' else: root = HKEY_LOCAL_MACHINE subkey = r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment' return root, subkey def get_env(name, user=True): root, subkey = env_keys(user) key = OpenKey(root, subkey, 0, KEY_READ) try: value, _ = QueryValueEx(key, name) except WindowsError: return '' return value def set_env(name, value): key = OpenKey(HKEY_CURRENT_USER, 'Environment', 0, KEY_ALL_ACCESS) SetValueEx(key, name, 0, REG_EXPAND_SZ, value) CloseKey(key) SendMessage( win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment') def remove(paths, value): while value in paths: paths.remove(value) def unique(paths): unique = [] for value in paths: if value not in unique: unique.append(value) return unique def prepend_env(name, values): for value in values: paths = get_env(name).split(';') remove(paths, '') paths = unique(paths) remove(paths, value) paths.insert(0, value) set_env(name, ';'.join(paths)) def prepend_env_pathext(values): prepend_env('PathExt_User', values) pathext = ';'.join([ get_env('PathExt_User'), get_env('PathExt', user=False) ]) set_env('PathExt', pathext) set_env('Home', '%HomeDrive%%HomePath%') set_env('Docs', '%HomeDrive%%HomePath%\docs') set_env('Prompt', '$P$_$G$S') prepend_env('Path', [ r'%SystemDrive%\cygwin\bin', # Add cygwin binaries to path r'%HomeDrive%%HomePath%\bin', # shortcuts and 'pass-through' bat files r'%HomeDrive%%HomePath%\docs\bin\mswin', # copies of standalone executables ]) # allow running of these filetypes without having to type the extension prepend_env_pathext(['.lnk', '.exe.lnk', '.py']) 

它不影响当前进程或父shell,但会影响运行后打开的所有cmd窗口,而不需要重新启动,可以安全地编辑和重新运行多次而不会引入任何重复。

使用外部Windows setx命令可能也很简单:

 C:\>set NEWVAR Environment variable NEWVAR not defined C:\>python Python 2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.system('setx NEWVAR newvalue') 0 >>> os.getenv('NEWVAR') >>> ^Z C:\>set NEWVAR Environment variable NEWVAR not defined 

现在打开一个新的命令提示符:

 C:\>set NEWVAR NEWVAR=newvalue 

正如你所看到的, setx既没有设置当前会话的变量,也没有设置父进程的变量(第一个命令提示符)。 但是它确实将变量永久地设置在注册表中以供将来的进程使用。

我认为根本没有改变父母程序环境的方法(如果有的话,我很乐意听到它!)。

千年前,我试图通过一个程序来改变当前DOS会议的环境。 问题是:该程序在其自己的DOS shell中运行,所以它必须在其父环境上运行。 从DOS信息块开始,沿着“内存控制块”的链条开始散步,找到父级环境的位置。 一旦我发现如何做到这一点,我对操纵环境变量的需求就消失了。 我会给你下面的Turbo Pascal代码,但我想至少有三种更好的方法可以做到这一点:

  1. 创建一个批处理文件:( 一)调用一个Python脚本(或其他),生成临时批处理文件包含适当的SET命令; (b)调用临时批处理文件(SET命令在当前shell中执行); (c)删除临时批处理文件。

  2. 创建一个Python脚本,将类似“VAR1 = val1 \ nVAR2 = val2 \ nVAR3 = val3 \ n”的内容写入STDOUT。 在批处理文件中以这种方式使用它:

    for /f "delims=|" %%X in (' for /f "delims=|" %%X in (' callYourPythonScript ') do set %%X

    等等:变量VAR1,VAR2和VAR3已被赋予一个值。

  3. 修改Windows注册表并按照Alexander Prokofyev 在此处所述广播设置更改。

这里是一个程序,只报告内存位置的Pascal代码(你可能需要一本荷兰语字典和一个Pascal编程书)。 它似乎仍然在Windows XP下工作,不管它报告我们正在运行DOS 5.00。 这只是第一个开始,为了操纵选定的环境,要做很多低级编程。 而且由于指针结构似乎是正确的,所以我不太确定1994年的环境模型是否仍然有效。

 program MCBKETEN; uses dos, HexConv; {----------------------------------------------------------------------------} { Programma: MCBKETEN.EXE } { Broncode : MCBKETEN.PAS } { Doel : Tocht langs de MCB's met rapportage } { date : 11 januari 1994 } { Auteur : Meindert Meindertsma } { Versie : 1.00 } {----------------------------------------------------------------------------} type MCB_Ptr = ^MCB; { MCB_PtrPtr = ^MCB_Ptr; vervallen wegens DOS 2.11 -- zie verderop } MCB = record Signatuur : char; Eigenaar : word; Paragrafen : word; Gereserveerd : array[1..3] of byte; Naam : array[1..8] of char; end; BlokPtr = ^BlokRec; BlokRec = record Vorige : BlokPtr; DitSegment, Paragrafen : word; Signatuur : string[6]; Eigenaar, Omgeving : word; Functie : String4; Oorsprong, Pijl : char; KorteNaam : string[8]; LangeNaam : string; Volgende : BlokPtr; end; PSP_Ptr = ^PSP; PSP = record Vulsel1 : array[1..44] of byte; Omgeving : word; Vulsel2 : array[47..256] of byte; end; var Zone : string[5]; ProgGevonden, EindeKeten, Dos3punt2 : boolean; Regs : registers; ActMCB : MCB_Ptr; EersteSchakel, Schakel, LaatsteSchakel : BlokPtr; ActPSP : PSP_Ptr; EersteProg, Meester, Ouder, TerugkeerSegment, TerugkeerOffset, TerugkeerSegment2, OuderSegment : word; Specificatie : string[8]; ReleaseNummer : string[2]; i : byte; {----------------------------------------------------------------------------} { PROCEDURES EN FUNCTIES } {----------------------------------------------------------------------------} function Coda (Omgeving : word; Paragrafen : word) : string; var i : longint; Vorige, Deze : char; Streng : string; begin i := 0; Deze := #0; repeat Vorige := Deze; Deze := char (ptr (Omgeving, i)^); inc (i); until ((Vorige = #0) and (Deze = #0)) or (i div $10 >= Paragrafen); if (i + 3) div $10 < Paragrafen then begin Vorige := char (ptr (Omgeving, i)^); inc (i); Deze := char (ptr (Omgeving, i)^); inc (i); if (Vorige = #01) and (Deze = #0) then begin Streng := ''; Deze := char (ptr (Omgeving, i)^); inc (i); while (Deze <> #0) and (i div $10 < Paragrafen) do begin Streng := Streng + Deze; Deze := char (ptr (Omgeving, i)^); inc (i); end; Coda := Streng; end else Coda := ''; end else Coda := ''; end {Coda}; {----------------------------------------------------------------------------} { HOOFDPROGRAMMA } {----------------------------------------------------------------------------} BEGIN {----- Initiatie -----} Zone := 'Lower'; ProgGevonden := FALSE; EindeKeten := FALSE; Dos3punt2 := (dosversion >= $1403) and (dosversion <= $1D03); Meester := $0000; Ouder := $0000; Specificatie[0] := #8; str (hi (dosversion) : 2, ReleaseNummer); if ReleaseNummer[1] = ' ' then ReleaseNummer[1] := '0'; {----- Pointer naar eerste MCB ophalen ------} Regs.AH := $52; { functie $52 geeft adres van DOS Info Block in ES:BX } msdos (Regs); { ActMCB := MCB_PtrPtr (ptr (Regs.ES, Regs.BX - 4))^; NIET onder DOS 2.11 } ActMCB := ptr (word (ptr (Regs.ES, Regs.BX - 2)^), $0000); {----- MCB-keten doorlopen -----} new (EersteSchakel); EersteSchakel^.Vorige := nil; Schakel := EersteSchakel; repeat with Schakel^ do begin DitSegment := seg (ActMCB^); Paragrafen := ActMCB^.Paragrafen; if DitSegment + Paragrafen >= $A000 then Zone := 'Upper'; Signatuur := Zone + ActMCB^.Signatuur; Eigenaar := ActMCB^.Eigenaar; ActPSP := ptr (Eigenaar, 0); if not ProgGevonden then EersteProg := DitSegment + 1; if Eigenaar >= EersteProg then Omgeving := ActPSP^.Omgeving else Omgeving := 0; if DitSegment + 1 = Eigenaar then begin ProgGevonden := TRUE; Functie := 'Prog'; KorteNaam[0] := #0; while (ActMCB^.Naam[ ord (KorteNaam[0]) + 1 ] <> #0) and (KorteNaam[0] < #8) do begin inc (KorteNaam[0]); KorteNaam[ ord (KorteNaam[0]) ] := ActMCB^.Naam[ ord (KorteNaam[0]) ]; end; if Eigenaar = prefixseg then begin TerugkeerSegment := word (ptr (prefixseg, $000C)^); TerugkeerOffset := word (ptr (prefixseg, $000A)^); LangeNaam := '-----> Terminate Vector = ' + WordHex (TerugkeerSegment) + ':' + WordHex (TerugkeerOffset ) ; end else LangeNaam := ''; end {if ÆProgØ} else begin if Eigenaar = $0008 then begin if ActMCB^.Naam[1] = 'S' then case ActMCB^.Naam[2] of 'D' : Functie := 'SysD'; 'C' : Functie := 'SysP'; else Functie := 'Data'; end {case} else Functie := 'Data'; KorteNaam := ''; LangeNaam := ''; end {if Eigenaar = $0008} else begin if DitSegment + 1 = Omgeving then begin Functie := 'Env '; LangeNaam := Coda (Omgeving, Paragrafen); if EersteProg = Eigenaar then Meester := Omgeving; end {if ÆEnvØ} else begin move (ptr (DitSegment + 1, 0)^, Specificatie[1], 8); if (Specificatie = 'PATH=' + #0 + 'CO') or (Specificatie = 'COMSPEC=' ) or (Specificatie = 'OS=DRDOS' ) then begin Functie := 'Env' + chr (39); LangeNaam := Coda (DitSegment + 1, Paragrafen); if (EersteProg = Eigenaar) and (Meester = $0000 ) then Meester := DitSegment + 1; end else begin if Eigenaar = 0 then Functie := 'Free' else Functie := 'Data'; LangeNaam := ''; if (EersteProg = Eigenaar) and (Meester = $0000 ) then Meester := DitSegment + 1; end; end {else: not ÆEnvØ}; KorteNaam := ''; end {else: Eigenaar <> $0008}; end {else: not ÆProgØ}; {----- KorteNaam redigeren -----} for i := 1 to length (KorteNaam) do if KorteNaam[i] < #32 then KorteNaam[i] := '.'; KorteNaam := KorteNaam + ' '; {----- Oorsprong vaststellen -----} if EersteProg = Eigenaar then Oorsprong := '*' else Oorsprong := ' '; {----- Actueel proces (uitgaande Pijl) vaststellen -----} if Eigenaar = prefixseg then Pijl := '>' else Pijl := ' '; end {with Schakel^}; {----- MCB-opeenvolging onderzoeken / schakelverloop vaststellen -----} if (Zone = 'Upper') and (ActMCB^.Signatuur = 'Z') then begin Schakel^.Volgende := nil; EindeKeten := TRUE; end else begin ActMCB := ptr (seg (ActMCB^) + ActMCB^.Paragrafen + 1, 0); if ((ActMCB^.Signatuur <> 'M') and (ActMCB^.Signatuur <> 'Z')) or ($FFFF - ActMCB^.Paragrafen < seg (ActMCB^) ) then begin Schakel^.Volgende := nil; EindeKeten := TRUE; end else begin new (LaatsteSchakel); Schakel^.Volgende := LaatsteSchakel; LaatsteSchakel^.Vorige := Schakel; Schakel := LaatsteSchakel; end {else: (ÆMØ or ÆZØ) and Æteveel_ParagrafenØ}; end {else: ÆLowerØ or not ÆZØ}; until EindeKeten; {----- Terugtocht -----} while Schakel <> nil do with Schakel^ do begin {----- Ouder-proces vaststellen -----} TerugkeerSegment2 := TerugkeerSegment + (TerugkeerOffset div $10); if (DitSegment <= TerugkeerSegment2) and (DitSegment + Paragrafen >= TerugkeerSegment2) then OuderSegment := Eigenaar; {----- Meester-omgeving markeren -----} if DitSegment + 1 = Meester then Oorsprong := 'M'; {----- Schakel-verloop -----} Schakel := Schakel^.Vorige; end {while Schakel <> nil}; {----- Rapportage -----} writeln ('Chain of Memory Control Blocks in DOS version ', lo (dosversion), '.', ReleaseNummer, ':'); writeln; writeln ('MCB@ #Par Signat PSP@ Env@ Type !! Name File'); writeln ('---- ---- ------ ---- ---- ---- -- -------- ', '-----------------------------------'); Schakel := EersteSchakel; while Schakel <> nil do with Schakel^ do begin {----- Ouder-omgeving vaststellen -----} if Eigenaar = OuderSegment then begin if not Dos3punt2 then begin if (Functie = 'Env ') then begin Ouder := DitSegment + 1; Pijl := 'Û'; end else Pijl := '<'; end {if not Dos3punt2} else begin if ((Functie = 'Env' + chr (39)) or (Functie = 'Data')) and (Ouder = $0000) then begin Ouder := DitSegment + 1; Pijl := 'Û'; end else Pijl := '<'; end {else: Dos3punt2}; end {with Schakel^}; {----- Keten-weergave -----} writeln (WordHex (DitSegment) , ' ', WordHex (Paragrafen) , ' ', Signatuur , ' ', WordHex (Eigenaar) , ' ', WordHex (Omgeving) , ' ', Functie , ' ', Oorsprong, Pijl , ' ', KorteNaam , ' ', LangeNaam ); {----- Schakel-verloop -----} Schakel := Schakel^.Volgende; end {while Schakel <> nil}; {----- Afsluiting rapportage -----} writeln; write ('* = First command interpreter at '); if ProgGevonden then writeln (WordHex (EersteProg), ':0000') else writeln ('?'); write ('M = Master environment at '); if Meester > $0000 then writeln (WordHex (Meester), ':0000') else writeln ('?'); write ('< = Parent proces at '); writeln (WordHex (OuderSegment), ':0000'); write ('Û = Parent environment at '); if Ouder > $0000 then writeln (WordHex (Ouder), ':0000') else writeln ('?'); writeln ('> = Current proces at ', WordHex (prefixseg), ':0000'); writeln (' returns to ', WordHex (TerugkeerSegment), ':', WordHex (TerugkeerOffset)); END. 

(在ASCII 127以上,本演示文稿中可能存在一些ASCII / ANSI转换问题。)

注册表的方式是,如果你想永久修改它,我想这是你想要的,因为它在setup.py。

暂时只是你的过程,然后os.environ是诀窍。

在os模块中,有getenv和putenv函数。 但是,似乎putenv不能正常工作,您必须使用Windows注册表

看看这个讨论

如果没有权限回退到用户的注册表,这个Python脚本[*]将尝试修改注册表中的GLOBAL env-vars,然后通知所有窗口关于更改:

 """ Show/Modify/Append registry env-vars (ie `PATH`) and notify Windows-applications to pickup changes. First attempts to show/modify HKEY_LOCAL_MACHINE (all users), and if not accessible due to admin-rights missing, fails-back to HKEY_CURRENT_USER. Write and Delete operations do not proceed to user-tree if all-users succeed. Syntax: {prog} : Print all env-vars. {prog} VARNAME : Print value for VARNAME. {prog} VARNAME VALUE : Set VALUE for VARNAME. {prog} +VARNAME VALUE : Append VALUE in VARNAME delimeted with ';' (ie used for `PATH`). {prog} -VARNAME : Delete env-var value. Note that the current command-window will not be affected, changes would apply only for new command-windows. """ import winreg import os, sys, win32gui, win32con def reg_key(tree, path, varname): return '%s\%s:%s' % (tree, path, varname) def reg_entry(tree, path, varname, value): return '%s=%s' % (reg_key(tree, path, varname), value) def query_value(key, varname): value, type_id = winreg.QueryValueEx(key, varname) return value def show_all(tree, path, key): i = 0 while True: try: n,v,t = winreg.EnumValue(key, i) print(reg_entry(tree, path, n, v)) i += 1 except OSError: break ## Expected, this is how iteration ends. def notify_windows(action, tree, path, varname, value): win32gui.SendMessage(win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 'Environment') print("---%s %s" % (action, reg_entry(tree, path, varname, value))) def manage_registry_env_vars(varname=None, value=None): reg_keys = [ ('HKEY_LOCAL_MACHINE', r'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'), ('HKEY_CURRENT_USER', r'Environment'), ] for (tree_name, path) in reg_keys: tree = eval('winreg.%s'%tree_name) try: with winreg.ConnectRegistry(None, tree) as reg: with winreg.OpenKey(reg, path, 0, winreg.KEY_ALL_ACCESS) as key: if not varname: show_all(tree_name, path, key) else: if not value: if varname.startswith('-'): varname = varname[1:] value = query_value(key, varname) winreg.DeleteValue(key, varname) notify_windows("Deleted", tree_name, path, varname, value) break ## Don't propagate into user-tree. else: value = query_value(key, varname) print(reg_entry(tree_name, path, varname, value)) else: if varname.startswith('+'): varname = varname[1:] value = query_value(key, varname) + ';' + value winreg.SetValueEx(key, varname, 0, winreg.REG_EXPAND_SZ, value) notify_windows("Updated", tree_name, path, varname, value) break ## Don't propagate into user-tree. except PermissionError as ex: print("!!!Cannot access %s due to: %s" % (reg_key(tree_name, path, varname), ex)) except FileNotFoundError as ex: print("!!!Cannot find %s due to: %s" % (reg_key(tree_name, path, varname), ex)) if __name__=='__main__': args = sys.argv argc = len(args) if argc > 3: print(__doc__.format(prog=args[0])) sys.exit() manage_registry_env_vars(*args[1:]) 

下面是一些用法示例,假设它已经保存在当前路径中某个名为setenv.py的文件中。 请注意,在这些示例中,我没有管理员权限 ,所以更改仅影响我本地用户的注册表树:

 > REM ## Print all env-vars > setenv.py !!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied HKEY_CURRENT_USER\Environment:PATH=... ... > REM ## Query env-var: > setenv.py PATH C:\foo !!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied !!!Cannot find HKEY_CURRENT_USER\Environment:PATH due to: [WinError 2] The system cannot find the file specified > REM ## Set env-var: > setenv.py PATH C:\foo !!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied ---Set HKEY_CURRENT_USER\Environment:PATH=C:\foo > REM ## Append env-var: > setenv.py +PATH D:\Bar !!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied ---Set HKEY_CURRENT_USER\Environment:PATH=C:\foo;D:\Bar > REM ## Delete env-var: > setenv.py -PATH !!!Cannot access HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment:PATH due to: [WinError 5] Access is denied ---Deleted HKEY_CURRENT_USER\Environment:PATH 

[*]改编自: http : //code.activestate.com/recipes/416087-persistent-environment-variables-on-windows/