从Python修改Windows环境变量的接口

2024-04-26 12:46:20 发布

您现在位置:Python中文网/ 问答频道 /正文

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

我正在寻找一个标准的功能或模块来使用这个。我已经对registry way of doing it很熟悉了,但是也欢迎对此发表任何评论。


Tags: 模块ofpy功能脚本标准windowssetup
3条回答

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

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

  2. 创建一个Python脚本,将“VAR1=val1\nVAR2=val2\nVAR3=val3\n”写入STDOUT。在批处理文件中这样使用:

    for /f "delims=|" %%X in ('称为pythonscript') do set %%X

    et voil a:变量VAR1、VAR2和VAR3被赋予了一个值。

  3. 修改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转换问题。)

使用外部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既不为当前会话设置变量,也不为父进程(第一个命令提示符)设置变量。但它确实在注册表中为将来的进程持久地设置了变量。

我认为根本没有办法改变父进程的环境(如果有,我很乐意听到)。

使用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窗口,而无需重新启动,并且可以安全地多次编辑和重新运行,而不会引入任何重复项。

相关问题 更多 >