4
votes

I wrote an Inno Setup script which install a program and update the PATH environment variable with the directory in which the program in installed.

I want to update the PATH environment variable, to restore its previous installation status.

The installation path is chosen by the user while the installer is running.

This is the script, which uses code from How do I modify the PATH environment variable when running an Inno Setup Installer?

[Setup]
ChangesEnvironment=yes

[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
    ValueType: expandsz; ValueName: "PATH"; ValueData: "{olddata};{app}"; \
    Check: NeedsAddPath('{app}')
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
    ValueName: "PATH"; ValueData: "{app}"; Flags: uninsdeletevalue
[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;

Taking a look to the code, it's possible to note the following instruction:

Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
    ValueName: "PATH"; ValueData: "{app}"; Flags: uninsdeletevalue

I used that instruction, adapted (in my opinion) for the my example, reading Inno Setup. How to uninstall registry value?

The use of uninsdeletevalue should be delete the value when the program is uninstalled, and in fact, when I run the uninstaller, the entire PATH variable is deleted, but I need to restore the PATH environment variable to the previous installation value. I think it's possible reading its value before run the installer, but I don't have any idea to how use it in the uninstall phase.

Can someone help me with a code example?

3

3 Answers

8
votes

You cannot have Inno Setup remember the value on installation and restore it, when uninstalling using [Registry] section entry only.

While you can code it, it's not good approach anyway as the PATH likely changes after the installation and you will discard any such changes.


You have to search the PATH for your path and remove the path only.

const
  EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';

procedure RemovePath(Path: string);
var
  Paths: string;
  P: Integer;
begin
  if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then
  begin
    Log('PATH not found');
  end
    else
  begin
    Log(Format('PATH is [%s]', [Paths]));

    P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';');
    if P = 0 then
    begin
      Log(Format('Path [%s] not found in PATH', [Path]));
    end
      else
    begin
      if P > 1 then P := P - 1;
      Delete(Paths, P, Length(Path) + 1);
      Log(Format('Path [%s] removed from PATH => [%s]', [Path, Paths]));

      if RegWriteStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then
      begin
        Log('PATH written');
      end
        else
      begin
        Log('Error writing PATH');
      end;
    end;
  end;
end;

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
  if CurUninstallStep = usUninstall then
  begin
    RemovePath(ExpandConstant('{app}'));
  end;
end;
2
votes

The problem seems to stick if a previous version was already installed, containing erroneous uninstall routines, and then updated with the new Setup. The Path was deleted no matter what, even if the Registry section had no Flags anymore. So my task was to write a new Setup that when updating faulty versions makes sure the Path is still there after uninstall.

I came closest with Martin Prikryl's approach, but logging showed I need to save the Path at the beginning of the uninstall (usUninstall) and rewrite it at the end (usPostUninstall), else it wouldn't stick. Also, environment is already errorneous refreshed without Path when processing the usPostUninstall Step, so I added a manual environment refresh which I took from another post.

Here's the final result that worked for me:

var
  Paths: string;

const
  EnvironmentKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
  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 SaveOldPath();
begin
  if not RegQueryStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then
  begin
    Log('PATH not found');
  end else begin
    Log(Format('Old Path saved as [%s]', [Paths]));
  end;
end;

procedure RemovePath(Path: string);
var
  P: Integer;
begin
  Log(Format('Prepare to remove from Old PATH [%s]', [Paths]));

  P := Pos(';' + Uppercase(Path) + ';', ';' + Uppercase(Paths) + ';');
  if P = 0 then
  begin
    Log(Format('Path [%s] not found in PATH', [Path]));
  end
    else
  begin
    Delete(Paths, P - 1, Length(Path) + 1);
    Log(Format('Path [%s] removed from PATH => [%s]', [Path, Paths]));

    if RegWriteExpandStringValue(HKEY_LOCAL_MACHINE, EnvironmentKey, 'Path', Paths) then
    begin
      Log('PATH written');
    end
      else
    begin
      Log('Error writing PATH');
    end;
  end;
end;

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

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
  if CurUninstallStep =  usUninstall then
  begin
    SaveOldPath();
  end;
  if CurUninstallStep = usPostUninstall then
  begin
    RemovePath(ExpandConstant('{app}'));
    RefreshEnvironment();
  end;
end;
1
votes

These solutions for removing variable from PATH helped a lot, but for me there was a problem in Delete(Paths, P - 1, Length(Path) + 1); when path of my installer was at the beginning. Then Delete function will not work.

My simple modification which worked for me was Delete(Paths, P, Length(Path) + 1);