Environment variable not recognized [not available] for [Run] programs in Inno Setup

I have a license.exe file that I call in my setup code at the end,

The code needs the environment variable to be set before working correctly,

The code is as follows:

[Registry]
; set PATH
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
    ValueType: string; ValueName: "PATH"; ValueData: "{app}"

[Setup]
; Tell Windows Explorer to reload the environment
ChangesEnvironment=yes

[Run]
Filename: "{app}\temp\installation_files\license.exe";

Here the code executes, but does not find the correct path.

When I check the system environment variable, it is set correctly,

When I run the license.exe code afterwards manually, it works correctly and sees the environment variable.

Can someone tell me how to fix this?

Or how to delay the [Run] section until the system recognizes the environment variable?


Solution 1:

The processes created for executing entries from the [Run] section inherits the environment block of its parent process, which is the installer itself. So you have to set the environment variable to the installer and let it inherit to your executed application. How to do that is shown in the below script:

[Run]
Filename: "{app}\temp\installation_files\license.exe"; BeforeInstall: SetEnvPath

[Code]
#ifdef UNICODE
  #define AW "W"
#else
  #define AW "A"
#endif
function SetEnvironmentVariable(lpName: string; lpValue: string): BOOL;
  external 'SetEnvironmentVariable{#AW}@kernel32.dll stdcall';

procedure SetEnvPath;
begin
  if not SetEnvironmentVariable('PATH', ExpandConstant('{app}')) then
    MsgBox(SysErrorMessage(DLLGetLastError), mbError, MB_OK);
end;

Previous answer for notifying rest of the system about variable change:

As @Jerry pointed out in his comment, a notification about the environment changes is performed after the [Run] section is processed. Actually, it is one of the last things executed by the installer.

So, to notify the system about environment changes before processing the [Run] section, you'll need to have a workaround. I rewrote the RefreshEnvironment procedure from Inno Setup code to script. It's the same function as it's executed if you have ChangesEnvironment directive set to yes.

In the following script I have removed the ChangesEnvironment directive and added execution of the RefreshEnvironment procedure from the AfterInstall parameter function of your registry entry:

[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
    ValueType: string; ValueName: "PATH"; ValueData: "{app}"; \
    AfterInstall: RefreshEnvironment;

[Run]
Filename: "{app}\temp\installation_files\license.exe";

[Code]
const
  SMTO_ABORTIFHUNG = 2;
  WM_WININICHANGE = $001A;
  WM_SETTINGCHANGE = WM_WININICHANGE;

type
  WPARAM = UINT_PTR;
  LPARAM = INT_PTR;
  LRESULT = INT_PTR;

function SendTextMessageTimeout(hWnd: HWND; Msg: UINT;
  wParam: WPARAM; lParam: PAnsiChar; fuFlags: UINT;
  uTimeout: UINT; out lpdwResult: DWORD): LRESULT;
  external '[email protected] stdcall';  

procedure RefreshEnvironment;
var
  S: AnsiString;
  MsgResult: DWORD;
begin
  S := 'Environment';
  SendTextMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
    PAnsiChar(S), SMTO_ABORTIFHUNG, 5000, MsgResult);
end;

Solution 2:

The solution with SetEnvironmentVariable in TLama's answer is correct for many situations.

But it won't work for [Run] tasks with runasoriginaluser flag (what is implied by postinstall flag). I.e. the variable won't be propagated to an application run with common "Run My Program" check box on the "Finished" page.

The reason is that the tasks with runasoriginaluser are executed by a un-elevated hidden parent process of the Inno Setup installer. The SetEnvironmentVariable will change environment for the installer, but not for its parent process. Unfortunately, the parent process of the installer cannot be controlled (imo).

As a workaround, to set the variable for the runasoriginaluser tasks, you have to inject an intermediate process between the installer parent process and the task, and have the intermediate process set the variable.

Such an intermediate process can easily be the cmd.exe with its set command:

[Run]
Filename: "{cmd}"; Parameters: "/C set PATH=%PATH%;{app} & ""{app}\MyProg.exe"""; \
    Description: "Run My Program"; Flags: postinstall runhidden

The runhidden flag hides the cmd.exe console window, not the application (assuming it's a GUI application). If it's a console application and you want the output to be visible, remove the runhidden flag. Alternatively, you can use start command to start the application in its own console window.

Solution 3:

after some modifications below, worked perfectly:

[Run]
Filename: "{app}\{#MyAppExeName}"; BeforeInstall: AppendToPathAndRefresh;Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}";Flags: nowait postinstall shellexec skipifsilent

[Code]
////////////////////////////////////////////////////////////
const
SMTO_ABORTIFHUNG = 2;
WM_WININICHANGE = $001A;

type
WPARAM = UINT_PTR;
LPARAM = INT_PTR;
LRESULT = INT_PTR;

function SendTextMessageTimeout(hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: PAnsiChar; fuFlags: UINT;
uTimeout: UINT; out lpdwResult: DWORD): LRESULT;
external '[email protected] stdcall';  

procedure RefreshEnvironment;
var
S: AnsiString;
MsgResult: DWORD;
begin
S := 'Environment';
SendTextMessageTimeout(HWND_BROADCAST, WM_WININICHANGE, 0,
    PAnsiChar(S), SMTO_ABORTIFHUNG, 5000, MsgResult);
end;
///PATH ENVINRONMENT//////////////////////////////////////////
function Replace(Dest, SubStr, Str: string): string;
var
Position: Integer;
Ok: Integer;
begin
Ok := 1;
while Ok > 0 do
begin
    Position:=Pos(SubStr, Dest);
    if Position > 0 then
    begin
    Delete(Dest, Position, Length(SubStr));
    Insert(Str, Dest, Position);
    end else
    Ok := 0;
end;
Result:=Dest;
end;

procedure AppendToPath();
var
V: string;
Str: string;
begin
RegQueryStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
Str := ExpandConstant('{app}\libav');
V := Replace(V, Str, '');
V := V + ';' + Str;
V := Replace(V,';;',';');
RegWriteStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)

// MsgBox(V, mbInformation, MB_OK); 
end;

procedure RemoveFromPath();
var
V: string;
Str: string;
begin
RegQueryStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
Str := ExpandConstant('{app}\dlls');
V := Replace(V, Str, '');
V := Replace(V,';;',';');
RegWriteStringValue(HKLM, 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', 'Path', V)
//MsgBox(V, mbInformation, MB_OK);
end;

procedure AppendToPathAndRefresh;
begin
AppendToPath;
RefreshEnvironment;
end;


procedure DeinitializeUninstall();
begin
RemoveFromPath();
end;
///END OF PATH ENVIRONMENT ///////////////////////////////////