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

泪湿孤枕 提交于 2020-07-20 03:46:40

问题


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?


回答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 'SendMessageTimeoutA@user32.dll stdcall';  

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



回答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 MYVAR=MyValue & ""{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, use start to start it in its own (visible) console window.




回答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 'SendMessageTimeoutA@user32.dll 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 ///////////////////////////////////


来源:https://stackoverflow.com/questions/62906879/how-to-set-a-short-delay-using-inno-setup

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!