How do I modify the PATH environment variable when running an Inno Setup Installer?

Go To StackoverFlow.com

57

Inno Setup lets you set environment variables via the [Registry] sections (by setting registry key which correspond to environment variable)

However, sometimes you don't just wanna set an environment variable. Often, you wanna modify it. For example: upon installation, one may want to add/remove a directory to/from the PATH environment variable.

How can I modify the PATH environment variable from within InnoSetup?

2010-07-21 22:42
by bandana


74

The path in the registry key you gave is a value of type REG_EXPAND_SZ. As the Inno Setup documentation for the [Registry] section states there is a way to append elements to those:

On a string, expandsz, or multisz type value, you may use a special constant called {olddata} in this parameter. {olddata} is replaced with the previous data of the registry value. The {olddata} constant can be useful if you need to append a string to an existing value, for example, {olddata};{app}. If the value does not exist or the existing value isn't a string type, the {olddata} constant is silently removed.

So to append to the path a registry section similar to this may be used:

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

which would append the "C:\foo" directory to the path.

Unfortunately this would be repeated when you install a second time, which should be fixed as well. A Check parameter with a function coded in Pascal script can be used to check whether the path does indeed need to be expanded:

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

This function reads the original path value and checks whether the given directory is already contained in it. To do so it prepends and appends semicolon chars which are used to separate directories in the path. To account for the fact that the searched for directory may be the first or last element semicolon chars are prepended and appended to the original value as well:

[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;

Note that you may need to expand constants before you pass them as parameter to the check function, see the documentation for details.

Removing this directory from the path during uninstallation can be done in a similar fashion and is left as an exercise for the reader.

2010-08-07 17:32
by mghie
Wouldn't it be great if you could simply pass {olddata} to the Check function so you don't have to read the value again in code? (maybe you can - I haven't tried) ; - Oliver Giesen 2011-03-04 13:00
Another thing is that the path might be there but use a different character case (easily fixed by using UpperCase or somesuch function) or, even worse, use 8.3 path names (e.g. "C:\Progra~1\MyProg") or environment variables (e.g. "%programfiles%\MyProg"). It'd be a nightmare to detect those as well.. - Oliver Giesen 2011-03-04 13:03
what about when your program is uninstalled? How would you remove your path from the PATH Environment Variable - Drew Chapin 2011-12-07 15:42
I have to say the way you used Pos() was rather ingenious. I would have split the string by semi-colons into an array, and looped through each one. I don't think I would have thought of this approach - Drew Chapin 2011-12-07 15:48
Set ChangesEnvironment=yes in [Setup] and you can remove the requirement to restart with this. SourceBrais Gabin 2012-05-23 21:14
I really don't think remove path can be done in a similar fashion way.. - Jack 2014-08-02 02:16
NeedsAddPath doesn't seems to work. It always add path.

Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{drive:c:}{#WwwRoot}\php\php5.5"; Check: NeedsAddPath('{drive:c:}{#WwwRoot}\php\php5.5'); Flags: preservestringtype - vee 2014-11-05 15:30

@vee: Have you tried the ExpandConstant() function on your parameter - mghie 2014-11-05 20:35
Yes, it's done. : - vee 2014-11-05 23:27


17

You can use LegRoom.net's modpath.iss script in your InnoSetup script file:

#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"
2012-04-01 06:09
by ecle
Thanks works like a charm. By removing some pieces of code in modpath.iss it is also possible to make it run without asking the user (i.e. not as a Task with checkbox but always) - Johannes Thoma 2018-09-27 17:53


9

I had the same problem but despite the answers above I've ended up with a custom solution and I'd like to share it with you.

First of all I've created the environment.iss file with 2 methods - one for adding path to the environment's Path variable and second to remove it:

[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;

Reference: RegQueryStringValue, RegWriteStringValue

Now in main .iss file I could include this file and listen for the 2 events (more about events you can learn in Event Functions section in documentation), CurStepChanged to add path after installation and CurUninstallStepChanged to remove it when user uninstall an application. In below example script add/remove the bin directory (relative to the installation directory):

#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;

Reference: ExpandConstant

Note #1: Install step add path only once (ensures repeatability of the installation).

Note #2: Uninstall step remove only one occurrence of the path from variable.

Bonus: Installation step with checkbox "Add to PATH variable".

Inno Setup - Add to PATH variable

To add installation step with checkbox "Add to PATH variable" define new task in [Tasks] section (checked by default):

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

Then you can check it in CurStepChanged event:

procedure CurStepChanged(CurStep: TSetupStep);
begin
    if (CurStep = ssPostInstall) and IsTaskSelected('envPath')
    then EnvAddPath(ExpandConstant('{app}') +'\bin');
end;
2017-10-06 15:21
by Wojciech Mleczek
perfect, works out of the box. thank yo - georges abitbol 2018-05-04 15:14


7

The NeedsAddPath in the answer by @mghie doesn't check trailing \ and letter case. Fix it.

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;
2012-04-01 05:45
by Helen Dyakonova
How do I use a variable instead of 'C:\foo'? I tried NeedsAddPath('{app}') but it does not work - just concatenating the path although it's already exit. Can you advice please - Tamir Gefen 2012-09-21 09:46
Just to answer above comment, might be useful to others: You need to use ExpandConstant() function - Jack 2014-08-01 15:36
Thank you Jack. However I would love to see an example of NeedsAddPath('{app}\MoreDirectoriesHere' - vezenkov 2015-07-30 16:03


2

Here is a complete solution to the problem that ignores casing, checks for existence of path ending with \ and also expands the constants in the param:

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;
2015-07-30 20:57
by vezenkov
Pretty sure there is an issue here: It only checks the '\;' case only if the ; case was found - Stewart 2017-08-09 14:48
Ads