以其他管理员用户运行时获取NSIS安装器的登录用户名
解决NSIS安装器提升权限后写入管理员AppData的问题
这个问题的核心原因很明确:当受限用户通过UAC提升为管理员权限运行安装器时,安装器的进程上下文切换到了管理员用户,此时$PROFILE、$APPDATA这类变量都会指向管理员的用户目录,而不是发起安装请求的受限用户目录。
下面给你两种实用的解决方案,从简单到进阶:
方案一:提前获取原用户路径并传递参数(推荐)
这种方法的思路是先以普通用户身份启动安装器,收集好原用户的AppData路径,再请求管理员权限并传递这个路径,避免上下文切换后丢失原用户信息:
首先在脚本开头设置安装器默认以普通用户权限运行:
RequestExecutionLevel user在安装初始化阶段,先获取当前用户(受限用户)的AppData路径并保存,同时判断是否需要提升权限:
Var OriginalAppData Function .onInit ; 获取原用户的AppData路径 StrCpy $OriginalAppData $APPDATA ; 检查当前是否为管理员权限 AccessControl::IsAdmin Pop $0 If $0 == 0 Then ; 不是管理员,重新以管理员身份运行并传递原AppData参数 ExecShell "runas" "$EXEPATH" "/OriginalAppData=`$OriginalAppData`" Quit EndIf FunctionEnd在安装器启动时解析命令行参数,拿到传递过来的原AppData路径:
Function .onInit ; 优先读取命令行传递的原AppData路径 ${GetParameters} $R0 ${GetOptions} $R0 "/OriginalAppData=" $OriginalAppData ; 如果没有参数(比如管理员直接运行),则使用当前进程的$APPDATA StrLen $0 $OriginalAppData If $0 == 0 Then StrCpy $OriginalAppData $APPDATA EndIf FunctionEnd后续安装文件到AppData时,直接使用
$OriginalAppData替代$PROFILE或$APPDATA:Section "Install Files" SetOutPath "$OriginalAppData\YourAppFolder" File "yourfile.exe" SectionEnd
方案二:通过Windows API获取原登录用户的Profile路径
如果不想重新启动安装器,可以通过Windows API获取当前登录的交互式用户(即发起安装的受限用户)的Profile路径,再拼接AppData目录。这种方法代码稍复杂,但不需要重启安装器:
Var OriginalUserProfile Function GetOriginalUserProfile ; 获取当前交互式用户的令牌 System::Call "user32.dll#OpenInputDesktop(i 0, i 0, i 0) p.r0" System::Call "user32.dll#GetUserObjectInformation(p r0, i 4, p 0, i 0, *i.r1)" System::Call "kernel32.dll#LocalAlloc(i 0, i r1) p.r2" System::Call "user32.dll#GetUserObjectInformation(p r0, i 4, p r2, i r1, *i.r3)" System::Call "user32.dll#CloseDesktop(p r0)" ; 通过用户名获取用户SID System::Call "advapi32.dll#LookupAccountName(p 0, p r2, p 0, *i.r4, p 0, *i.r5, *i.r6)" System::Call "kernel32.dll#LocalAlloc(i 0, i r4) p.r7" System::Call "kernel32.dll#LocalAlloc(i 0, i r5) p.r8" System::Call "advapi32.dll#LookupAccountName(p 0, p r2, p r7, *i.r4, p r8, *i.r5, *i.r6)" ; 通过SID获取用户Profile路径 System::Call "advapi32.dll#GetUserProfileDirectory(p r7, p 0, *i.r9)" System::Call "kernel32.dll#LocalAlloc(i 0, i r9) p.r10" System::Call "advapi32.dll#GetUserProfileDirectory(p r7, p r10, *i.r9)" StrCpy $OriginalUserProfile $r10 ; 释放分配的内存 System::Call "kernel32.dll#LocalFree(p r2)" System::Call "kernel32.dll#LocalFree(p r7)" System::Call "kernel32.dll#LocalFree(p r8)" System::Call "kernel32.dll#LocalFree(p r10)" FunctionEnd ; 在初始化阶段调用函数并构造AppData路径 Function .onInit Call GetOriginalUserProfile StrCpy $OriginalAppData "$OriginalUserProfile\AppData\Roaming" FunctionEnd
注意:这种方法需要安装器已获得管理员权限,所以脚本开头要设置RequestExecutionLevel admin,或者在需要时触发权限提升。
两种方案里,方案一更简单可靠,尤其是在多用户环境或自定义用户目录的场景下,不容易出现路径解析错误。
内容的提问来源于stack exchange,提问作者Joe Uhren
相关产品推荐
相关产品推荐

