【问题标题】:How do I modify the PATH environment variable when running an Inno Setup Installer?运行 Inno Setup Installer 时如何修改 PATH 环境变量?
【发布时间】:2011-03-19 06:46:09
【问题描述】:

Inno Setup 允许您通过 [Registry] 部分设置环境变量(通过设置与环境变量对应的注册表项)

但是,有时您不只是想设置一个环境变量。通常,您想对其进行修改。例如:在安装时,可能需要在 PATH 环境变量中添加/删除目录。

如何在 InnoSetup 中修改 PATH 环境变量?

【问题讨论】:

    标签: environment-variables inno-setup


    【解决方案1】:

    您提供的注册表项中的路径是REG_EXPAND_SZ 类型的值。正如 [Registry] 部分的 Inno Setup 文档所述,有一种方法可以将元素附加到这些:

    在string、expandsz 或multisz 类型值上,您可以在此参数中使用称为{olddata} 的特殊常量。 {olddata} 替换为注册表值的先前数据。如果您需要将字符串附加到现有值(例如,{olddata};{app}),{olddata} 常量会很有用。如果值不存在或现有值不是字符串类型,则 {olddata} 常量将被静默删除。

    因此,可以使用与此类似的注册表部分附加到路径:

    [Registry]
    Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
        ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};C:\foo"
    

    这会将“C:\foo”目录附加到路径中。

    不幸的是,当您第二次安装时,这会重复,这也应该修复。一个Check参数和一个用Pascal脚本编码的函数可以用来检查路径是否确实需要扩展:

    [Registry]
    Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
        ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};C:\foo"; \
        Check: NeedsAddPath('C:\foo')
    

    此函数读取原始路径值并检查给定目录是否已包含在其中。为此,它会在路径中添加用于分隔目录的分号字符。考虑到搜索目录可能是第一个或最后一个元素的事实,分号字符也被预先添加并附加到原始值:

    [Code]
    
    function NeedsAddPath(Param: string): boolean;
    var
      OrigPath: string;
    begin
      if not RegQueryStringValue(HKEY_LOCAL_MACHINE,
        'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
        'Path', OrigPath)
      then begin
        Result := True;
        exit;
      end;
      { look for the path with leading and trailing semicolon }
      { Pos() returns 0 if not found }
      Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0;
    end;
    

    请注意,在将常量作为参数传递给检查函数之前,您可能需要展开常量,详情请参阅文档。

    在卸载过程中从路径中删除此目录可以以类似的方式完成,留给读者作为练习。

    【讨论】:

    • 如果您可以简单地将{olddata} 传递给 Check 函数,这样您就不必在代码中再次读取该值,那不是很好吗? (也许你可以——我没试过);)
    • 另一件事是路径可能存在但使用不同的字符大小写(通过使用UpperCase 或类似函数很容易修复),或者更糟糕的是,使用 8.3 路径名(例如“C:\ Progra~1\MyProg") 或环境变量(例如 "%programfiles%\MyProg")。检测到这些也将是一场噩梦......
    • 当你的程序被卸载了怎么办?您将如何从 PATH 环境变量中删除您的路径?
    • 我不得不说你使用Pos()的方式相当巧妙。我会用分号将字符串拆分成一个数组,然后遍历每个数组。我想我不会想到这种方法。
    • 在[Setup] 中设置ChangesEnvironment=yes,您可以删除重新启动的要求。 Source
    【解决方案2】:

    我遇到了同样的问题,但尽管有上述答案,我最终还是得到了一个自定义解决方案,我想与您分享。

    首先,我使用 2 种方法创建了 environment.iss 文件 - 一种用于将路径添加到环境的 Path 变量,第二种用于删除它:

    [Code]
    const EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
    
    procedure EnvAddPath(Path: string);
    var
        Paths: string;
    begin
        { Retrieve current path (use empty string if entry not exists) }
        if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
        then Paths := '';
    
        { Skip if string already found in path }
        if Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';') > 0 then exit;
    
        { App string to the end of the path variable }
        Paths := Paths + ';'+ Path +';'
    
        { Overwrite (or create if missing) path environment variable }
        if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
        then Log(Format('The [%s] added to PATH: [%s]', [Path, Paths]))
        else Log(Format('Error while adding the [%s] to PATH: [%s]', [Path, Paths]));
    end;
    
    procedure EnvRemovePath(Path: string);
    var
        Paths: string;
        P: Integer;
    begin
        { Skip if registry entry not exists }
        if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then
            exit;
    
        { Skip if string not found in path }
        P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';');
        if P = 0 then exit;
    
        { Update path variable }
        Delete(Paths, P - 1, Length(Path) + 1);
    
        { Overwrite path environment variable }
        if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths)
        then Log(Format('The [%s] removed from PATH: [%s]', [Path, Paths]))
        else Log(Format('Error while removing the [%s] from PATH: [%s]', [Path, Paths]));
    end;
    

    参考:RegQueryStringValue,RegWriteStringValue

    现在在主 .iss 文件中,我可以包含此文件并监听 2 个事件(有关事件的更多信息,您可以在文档中的 Event Functions 部分了解),CurStepChanged 用于在安装后添加路径,CurUninstallStepChanged 用于删除当用户卸载应用程序时。在下面的示例脚本中添加/删除bin 目录(相对于安装目录):

    #include "environment.iss"
    
    [Setup]
    ChangesEnvironment=true
    
    ; More options in setup section as well as other sections like Files, Components, Tasks...
    
    [Code]
    procedure CurStepChanged(CurStep: TSetupStep);
    begin
        if CurStep = ssPostInstall 
         then EnvAddPath(ExpandConstant('{app}') +'\bin');
    end;
    
    procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
    begin
        if CurUninstallStep = usPostUninstall
        then EnvRemovePath(ExpandConstant('{app}') +'\bin');
    end;
    

    参考:ExpandConstant

    注意#1:安装步骤只添加一次路径(确保安装的可重复性)。

    注意 #2:卸载步骤仅从变量中删除一次出现的路径。

    奖励:带有复选框“添加到 PATH 变量”的安装步骤。

    使用复选框“添加到路径变量”添加安装步骤在[Tasks]部分定义新任务(默认选中):

    [Tasks]
    Name: envPath; Description: "Add to PATH variable" 
    

    那你可以在CurStepChanged事件中查看:

    procedure CurStepChanged(CurStep: TSetupStep);
    begin
        if (CurStep = ssPostInstall) and IsTaskSelected('envPath')
        then EnvAddPath(ExpandConstant('{app}') +'\bin');
    end;
    

    【讨论】:

    • @wojciech 谢谢。如果我必须将 两个 文件夹添加到 PATH 怎么办?
    • @SumanKhanal 在CurStepChanged 和CurUninstallStepChanged 函数中调用EnvAddPath 和EnvRemovePath 2 次。
    • 最佳答案在这里,适用于干净地安装和卸载。
    • @WojciechMleczek 谢谢!这是我尝试将文件夹添加到 PATH 的最佳方法!
    【解决方案3】:

    您可以在 InnoSetup 脚本文件中使用 LegRoom.net 的 modpath.iss 脚本:

    #define MyTitleName "MyApp" 
    
    [Setup]
    ChangesEnvironment=yes
    
    [CustomMessages]
    AppAddPath=Add application directory to your environmental path (required)
    
    [Files]
    Source: "install\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; 
    
    [Icons]
    Name: "{group}\{cm:UninstallProgram,{#MyTitleName}}"; Filename: "{uninstallexe}"; Comment: "Uninstalls {#MyTitleName}"
    Name: "{group}\{#MyTitleName}"; Filename: "{app}\{#MyTitleName}.EXE"; WorkingDir: "{app}"; AppUserModelID: "{#MyTitleName}"; Comment: "Runs {#MyTitleName}"
    Name: "{commondesktop}\{#MyTitleName}"; Filename: "{app}\{#MyTitleName}.EXE"; WorkingDir: "{app}"; AppUserModelID: "{#MyTitleName}"; Comment: "Runs {#MyTitleName}"
    
    [Registry]
    Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"
    
    [Tasks]
    Name: modifypath; Description:{cm:AppAddPath};   
    
    [Code]
    
    const
        ModPathName = 'modifypath';
        ModPathType = 'system';
    
    function ModPathDir(): TArrayOfString;
    begin
        setArrayLength(Result, 1)
        Result[0] := ExpandConstant('{app}');
    end;
    
    #include "modpath.iss"
    

    【讨论】:

    • 感谢就像一个魅力。通过删除 modpath.iss 中的一些代码,也可以在不询问用户的情况下使其运行(即不是作为带有复选框的任务,而是始终)。
    • @JohannesSchaub-litb 哇,这太重了。由于原始链接现在被标记为危险(我不知道为什么),但一个简单的存档查找将脚本标记为 GPL3 许可证web.archive.org/web/20170610232441/https://www.legroom.net/…
    【解决方案4】:

    the answer by @mghie 中的 NeedsAddPath 不检查尾随 \ 和字母大小写。修复它。

    function NeedsAddPath(Param: string): boolean;
    var
      OrigPath: string;
    begin
      if not RegQueryStringValue(
        HKEY_LOCAL_MACHINE,
        'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
        'Path', OrigPath)
      then begin
        Result := True;
        exit;
      end;
      { look for the path with leading and trailing semicolon }
      { Pos() returns 0 if not found }
      Result :=
        (Pos(';' + UpperCase(Param) + ';', ';' + UpperCase(OrigPath) + ';') = 0) and
        (Pos(';' + UpperCase(Param) + '\;', ';' + UpperCase(OrigPath) + ';') = 0); 
    end;
    

    【讨论】:

    • 如何使用变量而不是 'C:\foo'?我试过 NeedsAddPath('{app}') 但它不起作用 - 尽管它已经退出,但只是连接路径。能给点建议吗?
    • 只是回答上面的评论,可能对其他人有用:您需要使用ExpandConstant()函数。
    • 谢谢杰克。但是我很想看一个 NeedsAddPath('{app}\MoreDirectoriesHere') 的例子
    【解决方案5】:

    我要感谢大家对这个问题的贡献。我已将 Wojciech Mleczek 发布的大约 95% 的代码合并到我的应用程序的安装程序中。我确实对该代码进行了一些更正,这可能对其他人有用。我的改变:

    • 将形式参数 Path 重命名为 instlPath。减少代码中“路径”的多次使用(更易于阅读,IMO)。

    • 安装/卸载时,为以\; 结尾的instlPath 添加存在性检查。

    • 在安装过程中,不要在当前的%PATH% 中加倍;。

    • 在安装过程中处理缺失或空的%PATH%。

    • 在卸载过程中,确保没有将起始索引 0 传递给 Delete()。

    这是我更新后的EnvAddPath():

    const EnvironmentKey = 'Environment';
    
    procedure EnvAddPath(instlPath: string);
    var
        Paths: string;
    begin
        { Retrieve current path (use empty string if entry not exists) }
        if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', Paths) then
            Paths := '';
    
        if Paths = '' then
            Paths := instlPath + ';'
        else
        begin
            { Skip if string already found in path }
            if Pos(';' + Uppercase(instlPath) + ';',  ';' + Uppercase(Paths) + ';') > 0 then exit;
            if Pos(';' + Uppercase(instlPath) + '\;', ';' + Uppercase(Paths) + ';') > 0 then exit;
    
            { Append App Install Path to the end of the path variable }
            Log(Format('Right(Paths, 1): [%s]', [Paths[length(Paths)]]));
            if Paths[length(Paths)] = ';' then
                Paths := Paths + instlPath + ';'  { don't double up ';' in env(PATH) }
            else
                Paths := Paths + ';' + instlPath + ';' ;
        end;
    
        { Overwrite (or create if missing) path environment variable }
        if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', Paths)
        then Log(Format('The [%s] added to PATH: [%s]', [instlPath, Paths]))
        else Log(Format('Error while adding the [%s] to PATH: [%s]', [instlPath, Paths]));
    end;
    

    还有EnvRemovePath()的更新版本:

    procedure EnvRemovePath(instlPath: string);
    var
        Paths: string;
        P, Offset, DelimLen: Integer;
    begin
        { Skip if registry entry not exists }
        if not RegQueryStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', Paths) then
            exit;
    
        { Skip if string not found in path }
        DelimLen := 1;     { Length(';') }
        P := Pos(';' + Uppercase(instlPath) + ';', ';' + Uppercase(Paths) + ';');
        if P = 0 then
        begin
            { perhaps instlPath lives in Paths, but terminated by '\;' }
            DelimLen := 2; { Length('\;') }
            P := Pos(';' + Uppercase(instlPath) + '\;', ';' + Uppercase(Paths) + ';');
            if P = 0 then exit;
        end;
    
        { Decide where to start string subset in Delete() operation. }
        if P = 1 then
            Offset := 0
        else
            Offset := 1;
        { Update path variable }
        Delete(Paths, P - Offset, Length(instlPath) + DelimLen);
    
        { Overwrite path environment variable }
        if RegWriteStringValue(HKEY_CURRENT_USER, EnvironmentKey, 'Path', Paths)
        then Log(Format('The [%s] removed from PATH: [%s]', [instlPath, Paths]))
        else Log(Format('Error while removing the [%s] from PATH: [%s]', [instlPath, Paths]));
    end;
    

    【讨论】:

      【解决方案6】:

      这里是忽略大小写问题的完整解决方案,检查是否存在以\ 结尾的路径并扩展参数中的常量:

      function NeedsAddPath(Param: string): boolean;
      var
        OrigPath: string;
        ParamExpanded: string;
      begin
        //expand the setup constants like {app} from Param
        ParamExpanded := ExpandConstant(Param);
        if not RegQueryStringValue(HKEY_LOCAL_MACHINE,
          'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
          'Path', OrigPath)
        then begin
          Result := True;
          exit;
        end;
        // look for the path with leading and trailing semicolon and with or without \ ending
        // Pos() returns 0 if not found
        Result := Pos(';' + UpperCase(ParamExpanded) + ';', ';' + UpperCase(OrigPath) + ';') = 0;  
        if Result = True then
           Result := Pos(';' + UpperCase(ParamExpanded) + '\;', ';' + UpperCase(OrigPath) + ';') = 0; 
      end;
      

      【讨论】:

      • 很确定这里有一个问题:只有在找到; 的情况下,它才会检查'\;' 的情况。
      【解决方案7】:

      如果您可以使用外部 DLL,PathMgr.dll 也可以是一个选项。

      有一个sample .iss script 演示了如何在 Inno Setup 6 或更高版本中使用 DLL。

      PathMgr.dll 受 LPGL 许可保护。

      【讨论】:

        猜你喜欢
        • 2016-01-06
        • 1970-01-01
        • 1970-01-01
        • 2021-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-14
        • 2023-03-06
        相关资源
        最近更新 更多