7

I want to store my app in the current user's AppData directory to avoid problems with permissions we had when auto-updating our app (when it's stored in Program Files). We don't give the user the option of where to install the app. We've had complaints from non-admin users that the installer stores the app in the admin's AppData directory (after UAC of course), instead of the current user's AppData directory, which then prevents the app from running in the future.

Firstly, I had DefaultDirName={userappdata}\{#MyAppName}. Then I tried DefaultDirName={commonappdata}\{#MyAppName}. Then I tried that along with PrivilegesRequired=lowest and even as PrivilegesRequired=none as the Make InnoSetup installer request privileges elevation only when needed question suggested.

This is my script as of right now in case I'm missing something obvious:

; Script generated by the Inno Setup Script Wizard.
;SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "Teamwork Chat"
#define MyAppVersion "0.10.0"
#define MyAppPublisher "Digital Crew, Ltd."
#define MyAppURL "http://www.teamwork.com/"
#define MyAppExeName "TeamworkChat.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{0F063485-F5AF-4ADE-A9F9-661AB3BAA661}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={userappdata}\{#MyAppName}
DisableDirPage=yes
DefaultGroupName={#MyAppName}
OutputDir=E:\chat-client\dist
OutputBaseFilename={#MyAppName}_for_Windows32_Installer-{#MyAppVersion}
SetupIconFile=E:\chat-client\icons\teamwork_chat.ico
WizardImageFile=E:\chat-client\icons\chatWizardImageFile.bmp
Compression=lzma
SolidCompression=yes
PrivilegesRequired=none

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Files]
Source: "E:\chat-client\dist\TeamworkChat\win32\TeamworkChat.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "E:\chat-client\dist\TeamworkChat\win32\ffmpegsumo.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "E:\chat-client\dist\TeamworkChat\win32\icudtl.dat"; DestDir: "{app}"; Flags: ignoreversion
Source: "E:\chat-client\dist\TeamworkChat\win32\libEGL.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "E:\chat-client\dist\TeamworkChat\win32\libGLESv2.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "E:\chat-client\dist\TeamworkChat\win32\nw.pak"; DestDir: "{app}"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

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

Edit

I've changed two options but still no luck;

PrivilegesRequired=lowest
...
[Icons]
...
Name: "{userdesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

Edit 2:

I've added the runasoriginaluser flag and generated a new AppId (GUID) but still no luck;

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

Edit 3:

I've created a simple GitHub repository with the source files and Inno script.

Edit 4:

I had been testing on Windows 8.1. It seems to work when compiled on Windows 7, and ran on Windows 8, but not when compiled on 8 and ran on 8.

Edit 5:

It's solved now but to clear things up regarding Edit 4, it wasn't working only on my machine. It worked on other Windows 8 machines. Must've been some weird caching or something (even though I changed the AppId).

Community
  • 1
  • 1
Adam Lynch
  • 3,341
  • 5
  • 36
  • 66
  • 1
    Under the [Icons] section, have you tried changing {commondesktop} to {userdesktop} ? Creating something on the common desktop (all users) would most likely require elevated privileges. – Dale M Mar 31 '15 at 20:57
  • Also, you stated you used `PrivilegesRequired=latest` which may have been a typo - but latest is not valid. `PrivilegesRequired=lowest` would be valid. – Dale M Mar 31 '15 at 21:05
  • Good ideas @DaleM but unfortunately not. It's not showing UAC when I launch it, but it's still going to the wrong path. (To be honest, I can't remember if I got it to stop showing UAC before or after I wrote this question, I've tried a lot of different ways) – Adam Lynch Mar 31 '15 at 21:38
  • I copied your original code above, only changing the E:\ to C:\, and created all the file names your setup uses. I compiled the script and installed it as a "normal" non-admin user on Windows 7 (clicking the create desktop icon). It worked as expected. No UAC prompting. And I was able to run the exe successfully. What operating system are you installing on? Is it a fresh install (the app, not the OS), or has an admin already installed it on this machine? Could a group policy be enforcing a more strict user environment, preventing a normal user to install? – Dale M Apr 01 '15 at 00:41
  • The installation I described above was done under a normal user account named "Test", and the files were installed to C:\Users\Test\AppData\Roaming\Teamwork Chat\ – Dale M Apr 01 '15 at 01:35
  • @DaleM I've had reports on Windows 7 and 8. I was on 8.1. On my machine, the admin had already installed it, although I deleted the files before a few attempts. I don't know much about group policy. When you installed as Test, was your admin acccount still logged in as well? – Adam Lynch Apr 01 '15 at 11:29
  • No, the admin account was not logged in. I logged out as the admin account, logged in as Test and ran the install. I do not have a Windows 8 or 8.1 machine to try. Try it as a "fresh" install on an account. Maybe just create a normal "Test" user like I did, if you have the ability to do that on your machine. – Dale M Apr 01 '15 at 13:01
  • @DaleM I tried creating more fresh accounts, I tried logging out of the admin account while being logged into a fresh one, I tried moving the files from `E:` to `C:`. No luck :/. I might create a quick GitHub repo with the source files. – Adam Lynch Apr 01 '15 at 14:44
  • @DaleM this might help: https://github.com/adam-lynch/inno-setup-always-admin-appdata-problem – Adam Lynch Apr 01 '15 at 15:05

2 Answers2

3

From the wording of your question and if I am understanding you correctly, it sounds like this is because you are "validating with an admin account for the install to run." If this is the case and you are entering a different account (from that which you are logged in with) at the UAC prompt, the current user then actually becomes the Administrator account you just entered at the UAC prompt and not the account you are logged in with. Therefore, as long as you are being asked to elevate the installation using UAC, it will not end up in the logged in user's AppData directory.

What you may need to do is use the runasoriginaluser function, which will use the logged in user credentials instead of the account you entered at the UAC prompt, or find what is causing the UAC elevation prompt.

See also Inno Setup Creating registry key for logged in user (not admin user).

The MSDN documentation on Installation Context may also be useful.

Community
  • 1
  • 1
Robert Wigley
  • 1,917
  • 22
  • 42
  • 3
    This is something that Windows has been getting wrong for a *long* time. User-installed applications do not belong in the system-protected "C:\Program Files" directory. Having installers conform to the principle of least privilege is long overdue. – Ben Voigt Mar 31 '15 at 22:20
  • Thank you for pointing this out @Ben. You are indeed correct. It should go in %LocalAppData%\Programs. That information was only correct for Windows XP. I have just checked the MSDN documentation [here](https://msdn.microsoft.com/en-us/library/windows/desktop/dd765197(v=vs.85).aspx). I will modify my answer to remove the incorrect information. – Robert Wigley Mar 31 '15 at 22:36
  • This didn't sort it. I commented out the `DisableDirPage=yes` line to be sure and yes it's still going to the admin's AppData. – Adam Lynch Apr 01 '15 at 14:37
  • See "Edit 2" in the question to make sure I've added the flag in the right place. – Adam Lynch Apr 01 '15 at 14:52
  • @RobertWigley I've created a quick repo with my files in case it might help: https://github.com/adam-lynch/inno-setup-always-admin-appdata-problem – Adam Lynch Apr 01 '15 at 15:05
  • Will try and take a look at this later. I guess the question now is are you getting a UAC prompt? – Robert Wigley Apr 01 '15 at 15:09
  • @RobertWigley No, I'm not. – Adam Lynch Apr 01 '15 at 15:42
  • Between this and @DaleM's comments on the question, it's solved. It wasn't working on my own machine only for some reason (even though I assume if there's any kind of caching that the appId change should've sorted that). – Adam Lynch Apr 01 '15 at 15:43
2

It should work with PrivilegesRequired=lowest. And it's the correct approach. If it does not, we want to see a log file.

The lowest will make the installer run within context of the current unprivileged user. Hence all constants, like {userappdata}, {userdesktop}, etc, will refer to the current user.


Anyway, to answer your question, you can use the code below.

But that's just for special situations, when the installer really needs administrator privileges (e.g. to register some system DLL), but still needs to deploy files for the original user. Like here: Inno Setup - Register components as an administrator.

[Files]
Source: "MyProg.exe"; DestDir: "{code:GetAppData}"

[Code]

var
  AppDataPath: string;

function GetAppData(Param: string): string;
begin
  Result := AppDataPath;
end;  

function InitializeSetup(): Boolean;
var
  Uniq: string;
  TempFileName: string;
  Cmd: string;
  Params: string;
  ResultCode: Integer;
  Buf: AnsiString;
begin
  AppDataPath := ExpandConstant('{userappdata}');
  Log(Format('Default/Fallback application data path is %s', [AppDataPath]));
  Uniq := ExtractFileName(ExpandConstant('{tmp}'));
  TempFileName :=
    ExpandConstant(Format('{commondocs}\appdata-%s.txt', [Uniq]));
  Params := Format('/C echo %%APPDATA%% > %s', [TempFileName]);
  Log(Format('Resolving APPDATA using %s', [Params]));
  Cmd := ExpandConstant('{cmd}');
  if ExecAsOriginalUser(Cmd, Params, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and
     (ResultCode = 0) then
  begin
    if LoadStringFromFile(TempFileName, Buf) then
    begin
      AppDataPath := Trim(Buf);
      Log(Format('APPDATA resolved to %s', [AppDataPath]));
    end
      else
    begin
      Log(Format('Error reading %s', [TempFileName]));
    end;
    DeleteFile(TempFileName);
  end
    else
  begin
    Log(Format('Error %d resolving APPDATA', [ResultCode]));
  end;

  Result := True;
end;

More similar questions:

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992