如何从Python脚本中持久修改Windows环境变量? (这是setup.py脚本)
我正在寻找用于此目的的标准功能或模块。我已经熟悉了registry way of doing it,但对此也有任何意见。
答案 0 :(得分:20)
使用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窗口,无需重新启动,并且可以安全地编辑和重新运行多次,而不会引入任何重复项。< / p>
答案 1 :(得分:4)
使用外部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
既没有为当前会话设置变量,也没有为父进程(第一个命令提示符)设置变量。但它确实在注册表中持久设置变量以用于将来的过程。
我认为根本没有办法改变父进程的环境(如果有的话,我很乐意听到它!)。
答案 2 :(得分:3)
我必须在一千年前尝试通过一个程序来改变当前DOS会话的环境。问题是:该程序在自己的DOS shell中运行,因此必须在其父环境中运行。它需要一个步行,从DOS信息块开始,一直到内存控制块链,找到该父环境的位置。一旦我发现了如何做到这一点,我对操纵环境变量的需求就消失了。我将在下面给你Turbo Pascal代码,但我想至少有三种更好的方法可以解决这个问题:
创建一个批处理文件:(a)调用生成包含相应SET命令的临时批处理文件的Python脚本(或其他); (b)调用临时批处理文件(SET命令在当前shell中执行);和(c)删除临时批处理文件。
创建一个Python脚本,将“VAR1 = val1 \ nVAR2 = val2 \ nVAR3 = val3 \ n”之类的内容写入STDOUT。在批处理文件中以这种方式使用它:
for /f "delims=|" %%X in ('
callYourPythonScript ') do set %%X
etvoilà:变量VAR1,VAR2和VAR3已赋值。
修改Windows注册表并按照Alexander Prokofyev所描述的here广播设置更改。
这里有一个只报告内存位置的程序的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 }
{ Datum : 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转换问题。)
答案 3 :(得分:1)
注册表的方式是,如果你想永久修改它,我想这就是你想要的,因为它在setup.py中。
暂时只是你的过程,然后os.environ是诀窍。
答案 4 :(得分:1)
在os模块中,有getenv和putenv函数。但是,似乎putenv无法正常工作,您必须使用Windows注册表
答案 5 :(得分:1)
如果没有权限回退到用户的注册表,这个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 ';' (i.e. 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/