You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Inno Setup卸载时如何以原始用户身份运行外部程序?

在Inno Setup卸载阶段以原始用户身份执行PowerShell命令的解决方案

由于Inno Setup卸载程序默认以管理员权限运行,且卸载阶段无法使用ExecAsOriginalUser()函数,要让PowerShell命令以触发卸载的原始用户身份执行,需要通过Windows API获取原始用户的访问令牌,再依托该令牌启动进程。以下是具体实现步骤:

1. 引入必要的Windows API函数

在脚本的[Code]段中添加以下API声明,用于获取用户会话令牌并创建进程:

const
  WTS_CURRENT_SESSION = $FFFFFFFF;
  TOKEN_QUERY = $0008;
  TOKEN_DUPLICATE = $0002;
  TOKEN_ASSIGN_PRIMARY = $0001;
  CREATE_NEW_CONSOLE = $00000010;
  SW_HIDE = 0;

type
  TTokenPrivileges = record
    PrivilegeCount: DWORD;
    Privileges: array[0..0] of record
      Luid: TLargeInteger;
      Attributes: DWORD;
    end;
  end;

function WTSGetActiveConsoleSessionId: DWORD;
  external 'WTSGetActiveConsoleSessionId@kernel32.dll stdcall';

function WTSQueryUserToken(SessionId: DWORD; out Token: THandle): BOOL;
  external 'WTSQueryUserToken@wtsapi32.dll stdcall';

function DuplicateTokenEx(hExistingToken: THandle; dwDesiredAccess: DWORD;
  lpTokenAttributes: PSecurityAttributes; ImpersonationLevel: DWORD;
  TokenType: DWORD; out phNewToken: THandle): BOOL;
  external 'DuplicateTokenEx@advapi32.dll stdcall';

function CreateProcessAsUserW(hToken: THandle; lpApplicationName: string;
  lpCommandLine: string; lpProcessAttributes: PSecurityAttributes;
  lpThreadAttributes: PSecurityAttributes; bInheritHandles: BOOL;
  dwCreationFlags: DWORD; lpEnvironment: Pointer; lpCurrentDirectory: string;
  var lpStartupInfo: TStartupInfoW; var lpProcessInformation: TProcessInformation): BOOL;
  external 'CreateProcessAsUserW@advapi32.dll stdcall';

function CloseHandle(hObject: THandle): BOOL;
  external 'CloseHandle@kernel32.dll stdcall';

2. 封装执行命令的函数

添加一个自定义函数,用于以原始用户身份执行指定命令:

function ExecAsOriginalUserUninstall(const Command, Params: string): Boolean;
var
  SessionId: DWORD;
  UserToken, PrimaryToken: THandle;
  StartupInfo: TStartupInfoW;
  ProcessInfo: TProcessInformation;
  CmdLine: string;
begin
  Result := False;
  // 获取当前活跃控制台会话ID
  SessionId := WTSGetActiveConsoleSessionId;
  if SessionId = WTS_CURRENT_SESSION then Exit;

  // 获取会话对应的用户令牌
  if not WTSQueryUserToken(SessionId, UserToken) then Exit;

  try
    // 复制令牌为主要令牌,用于创建进程
    if not DuplicateTokenEx(UserToken, TOKEN_QUERY or TOKEN_DUPLICATE or TOKEN_ASSIGN_PRIMARY,
      nil, SecurityImpersonation, TokenPrimary, PrimaryToken) then Exit;

    try
      // 构造完整命令行
      CmdLine := Format('"%s" %s', [Command, Params]);
      // 初始化启动信息
      FillChar(StartupInfo, SizeOf(StartupInfo), 0);
      StartupInfo.cb := SizeOf(StartupInfo);
      StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
      StartupInfo.wShowWindow := SW_HIDE;

      // 以原始用户身份创建进程
      Result := CreateProcessAsUserW(PrimaryToken, '', CmdLine, nil, nil, False,
        CREATE_NEW_CONSOLE, nil, '', StartupInfo, ProcessInfo);

      if Result then
      begin
        CloseHandle(ProcessInfo.hThread);
        CloseHandle(ProcessInfo.hProcess);
      end;
    finally
      CloseHandle(PrimaryToken);
    end;
  finally
    CloseHandle(UserToken);
  end;
end;

3. 在卸载阶段调用函数

可以在CurUninstallStepChanged事件中触发命令执行,确保卸载完成后再恢复扩展:

procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
  if CurUninstallStep = usPostUninstall then
  begin
    // 执行指定的PowerShell命令
    ExecAsOriginalUserUninstall('powershell.exe', '-Command "winget install -e -h --id=9PMMSR1CGPWG --source=msstore --accept-package-agreements --accept-source-agreements --disable-interactivity"');
  end;
end;

注意事项

  • 确保脚本在管理员权限下运行(Inno Setup卸载程序默认满足),否则无法调用WTSQueryUserToken等API。
  • 若系统中存在多个活跃会话,WTSGetActiveConsoleSessionId会返回当前物理控制台对应的会话ID,确保命令在正确的用户环境中执行。
  • PowerShell命令参数需要正确转义,避免空格或特殊字符导致命令解析错误。

内容的提问来源于stack exchange,提问作者Jean-Milost Reymond

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 19:43:39