GetOriginalUser – Reliably detect the non-admin user in elevated installers (AV Safe)

77 views
Skip to first unread message

Derick Payne (Derick)

unread,
Jan 24, 2026, 1:07:40 AM (7 days ago) Jan 24
to innosetup

Hi everyone,

I wanted to share a small open-source utility I built to solve a recurring headache I’ve had with elevated installers (PrivilegesRequired=admin).

The Problem: As many of you know, once an installer elevates to Admin, constants such as {userdocs} or {userappdata} point to the Administrator’s profile. If you need to write to the actual user's config folder (the person running the desktop), you generally have to use workarounds or registry hacks.

I previously used the GetShellWindow method (finding the Explorer process) to identify the desktop owner. While this worked, recently it started triggering "Heuristic" false positives in several Antivirus engines (like Cynet and Acronis) because it mimics "Token Stealing" malware techniques.

The Solution: I wrote a new DLL that uses the Windows Terminal Services (WTS) API instead of process hunting.

  • AV Safe: It uses legitimate Admin APIs (WTS) instead of process injection/handles, so it currently sits at 0 detections.

  • Robust: It works correctly with Fast User Switching and RDP (checking for WTSActive).

  • Registry-based: It resolves paths via HKU\<SID>\... so it respects folder redirection (e.g., if the user moved Documents to a network drive).

GitHub: https://github.com/rizonesoft/GetOriginalUser

Usage: It exports standard C-style functions, so it’s easy to call from [Code].

It’s MIT licensed. I hope this saves someone else the time I spent debugging session IDs!

Jernej Simončič

unread,
Jan 24, 2026, 5:31:29 AM (7 days ago) Jan 24
to Derick Payne (Derick) on [innosetup]

On Saturday, January 24, 2026, 06:48:30, Derick Payne (Derick) wrote:


The Problem: As many of you know, once an installer elevates to Admin, constants such as {userdocs} or {userappdata} point to the Administrator’s profile. If you need to write to the actual user's config folder (the person running the desktop), you generally have to use workarounds or registry hacks.

Never use any user-specific folder from an installer that isn't running with PrivilegesRequired=lowest, because it will not work when your installer is run by an actual administrator (eg. as part of deployment scripts), as the user that will actually be running your program hadn't even logged in on the computer yet, so there's no way to get those files to them at install time.

 

-- 
< Jernej Simončič ><><><><>< https://eternallybored.org/ >
disallow')
Nothing can be done in one trip.
       -- Cook's Second Law of Travel

Derick Payne (Derick)

unread,
Jan 24, 2026, 12:46:48 PM (7 days ago) Jan 24
to innosetup
Hi Jernej,

You raise an excellent point, and I completely agree. Enterprise deployments (SCCM, Intune, PDQ Deploy, etc.) run in Session 0, where there's no interactive user at all—trying to resolve user folders in that context is fundamentally broken.

Thanks to your feedback, I've just pushed v1.1.0, which adds exactly this capability:

New functions:

IsInteractiveSession()
 — Returns 1 for desktop sessions, 0 for Session 0/headless
GetSessionId()
 — Returns the Windows session ID for debugging
This allows installers to implement a hybrid pattern:

This allows installers to implement a hybrid pattern:

function GetConfigDir(): String;
begin
  if IsInteractiveSession() then
    Result := GetOriginalUserAppData() + '\MyApp'  // User's AppData
  else
    Result := ExpandConstant('{commonappdata}\MyApp');  // ProgramData template
end;

Manual install (desktop user)  →  1+  →  Config → User's AppData
SCCM/Intune deployment  →  0  →  Config → ProgramData (template)

The installer can now detect "I can't find a human" and gracefully fall back to installing system-wide templates that can be provisioned to users later via Group Policy, login scripts, or first-run logic.

I've added a dedicated EnterpriseDemo.iss example demonstrating this pattern: https://github.com/rizonesoft/GetOriginalUser/blob/main/examples/EnterpriseDemo.iss

Thanks for keeping me honest—it's a much better solution now!

Martijn Laan

unread,
Jan 24, 2026, 4:15:32 PM (7 days ago) Jan 24
to innosetup
Writing configuration files to commonappdata from an elevated installer is not recommended either, see https://jrsoftware.org/ishelp/index.php?topic=setup_redirectionguard

Greetings,
Martijn

-------- Original Message --------
--
You received this message because you are subscribed to the Google Groups "innosetup" group.
To unsubscribe from this group and stop receiving emails from it, send an email to innosetup+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/innosetup/48b445e2-93c7-402a-bf15-9923e76f1c18n%40googlegroups.com.

Derick Payne (Derick)

unread,
Jan 25, 2026, 2:48:53 PM (6 days ago) Jan 25
to innosetup
Point taken, Martijn. I see the pitfall: blindly writing config files to commonappdata via an elevated installer is a recipe for permission/ACL headaches later on.

I am refining the recommended workflow to the 'Seed & Migrate' pattern:

Installer: Places a read-only 'Master Template' in {app} (Program Files).

Application: On first launch, detects the missing config and migrates the template to {userappdata} itself.

However, this makes GetOriginalUser even more critical. For that migration to work correctly, the installer must launch the application as the Standard User immediately post-install. If we use the standard Inno [Run] section, the app inherits Admin rights, performs the migration as Admin, and we are back to the exact same permission lock-out you warned about.

My tool ensures the 'First Run' happens in the correct user context so the file permissions settle correctly from the start.

Basically: The installer builds the house (Admin), but my tool hands the keys to the owner (User) so they can move the furniture in themselves.

Thanks for the catch!

Martijn Laan

unread,
Jan 25, 2026, 2:59:46 PM (6 days ago) Jan 25
to innosetup
Hi,

Op 25-1-2026 om 20:48 schreef Derick Payne:
For that migration to work correctly, the installer must launch the application as the Standard User immediately post-install. If we use the standard Inno [Run] section, the app inherits Admin rights, performs the migration as Admin

Sorry, but you're incorrect. Post-install [Run] entries default to running unelevated.

See https://jrsoftware.org/ishelp/index.php?topic=runsection&anchor=runasoriginaluser

Greetings,
Martijn

Derick Payne (Derick)

unread,
Jan 25, 2026, 3:16:42 PM (6 days ago) Jan 25
to innosetup

Touché, Martijn! You are absolutely right—I overlooked that modern Inno handles the privilege drop in the [Run] section automatically. Thanks for the correction.

However, the 'Seed & Migrate' pattern exposed a different gap that [Run] doesn't solve: Path Resolution in Pascal Script.

Even if Inno launches the app as the user, the installer script itself (running as Admin) still resolves constants like {userappdata} to the Administrator's profile.

I need the installer to know the Standard User's actual AppData path during the installation so I can write a 'Receipt' (e.g., to uninstall.ini). This allows the Uninstaller to clean up the user's config files later, even if the Uninstaller is run by a different Admin.

So, while Inno handles the Execution ([Run]), my tool handles the Detection (GetOriginalUserAppData) in the [Code] section, allowing the script to log exactly where the data is going to end up.

I'll update the README to reflect that distinction. Thanks again!

Martijn Laan

unread,
Jan 25, 2026, 3:52:26 PM (6 days ago) Jan 25
to innosetup
Hi,

Op 25-1-2026 om 21:16 schreef Derick Payne:

I overlooked that modern Inno handles the privilege drop in the [Run] section automatically.


It does so since version 5.2, released in 2007, so not exactly modern...


This allows the Uninstaller to clean up the user's config files later, even if the Uninstaller is run by a different Admin.


Even as an admin you can't assume you can reliably access a users files. You might have to take ownership first or change permissions. Even then it can fail, for example if the user profile was redirected to an unreachable location like a removable drive.

Even if it would work, you can't assume there is only one user on the machine.

The recommendation is to not delete per user config files during uninstall. Not only because of the technical issues above, but also because it is the user's data, and they might not appreciate you deciding to remove it.

Greetings,
Martijn

Derick Payne (Derick)

unread,
Jan 25, 2026, 4:15:12 PM (6 days ago) Jan 25
to innosetup
Thanks for the historical context on the privilege drop—I clearly gave Inno less credit than it deserves!

Regarding the removal of user data, I completely agree that an automated, "silent" wipe of %APPDATA% is a recipe for permission errors and frustrated users. However, our approach is to provide an explicit checkbox during the uninstallation process (e.g., "Remove personal settings and configuration files").

Our reasoning for including this choice:
  • User Agency: Many users prefer a "clean slate" if they are troubleshooting or moving on from the software, and they appreciate the installer doing the housecleaning for them.
  • Targeted Scope: We only attempt to remove data for the current user performing the uninstall. We acknowledge your point about other user profiles on the machine; we leave those untouched to avoid the permission/ownership hurdles you mentioned.
  • Transparency: By making it an opt-in choice rather than a default action, we avoid the "rude" behavior of deleting data the user might want to keep for a future reinstall.
Do you still see significant risks with this "opt-in" approach for the current user?

Martijn Laan

unread,
Jan 26, 2026, 4:41:42 AM (5 days ago) Jan 26
to innosetup
Hey,

I don't know. Earlier you described admins uninstalling software on behalf of other users, but now you are talking about deleting data for the current user only.

Also note that the uninstaller still runs elevated. Deleting files from a user writable location while elevated is risky. A malicious user could potentially trick the admin into deleting system files, although the RedirectionGuard feature should mitigate that.

You also mentioned user agency, but I do not think administrative installers align well with that goal. If you truly want the user to be in control rather than the administrator, the better match is a per user, non administrative installer. Do note that we support installers that offer that choice at startup.

Greetings,
Martijn

Op 25-1-2026 om 22:15 schreef Derick Payne:
--
You received this message because you are subscribed to the Google Groups "innosetup" group.
To unsubscribe from this group and stop receiving emails from it, send an email to innosetup+...@googlegroups.com.

Derick Payne (Derick)

unread,
Jan 26, 2026, 4:03:36 PM (5 days ago) Jan 26
to innosetup
Hi Martijn,

You are absolutely right. I realize now that I have been conflating different scenarios (Enterprise vs. Personal, Admin vs. User uninstall) to justify the workflow, which has only caused more confusion. I apologize for that.

Your point regarding "User Agency" is the clincher: if the goal is truly to put the user in control, the correct architectural choice is a per-user, non-administrative installer, not a workaround within an elevated one. I also take your warning about the security risks of elevated file operations in user-writable paths to heart.

I was too focused on how to make the code work rather than whether it was the right design pattern to begin with.

I will step back and update the repository documentation to highlight these caveats so others don't fall into the same traps. Thank you for your patience and for the masterclass on installer security and best practices.
Reply all
Reply to author
Forward
0 new messages