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

如何从管理员权限模式应用重启为非管理员权限模式?交替重启权限问题

解决Delphi应用交替管理员/非管理员权限重启的问题

嗨,这个问题我之前也碰到过!当你的应用处于管理员权限(elevated)状态时,直接用ShellExecute的open参数启动自身,Windows会默认让它继承当前的elevated权限,导致没法切换回普通用户模式。下面给你详细的解决方案:

问题根源

当进程处于elevated状态时,它运行在高权限的安全桌面中,直接启动子进程会默认继承这个高权限环境。要切换回非管理员模式,必须明确获取当前用户的非elevated访问令牌,并用这个令牌来启动新进程。

完整解决方案代码

首先,我们需要两个核心功能:判断当前进程是否为elevated,以及从elevated模式启动非elevated的自身进程。

1. 判断进程是否处于Elevated模式

先完善你的ThisIsElevated变量的赋值逻辑,确保判断准确:

function IsProcessElevated: Boolean;
var
  TokenHandle: THandle;
  Elevation: TTokenElevation;
  ReturnLength: DWORD;
begin
  Result := False;
  if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, TokenHandle) then
  try
    ReturnLength := SizeOf(Elevation);
    if GetTokenInformation(TokenHandle, TokenElevation, @Elevation, ReturnLength, ReturnLength) then
      Result := Elevation.TokenIsElevated;
  finally
    CloseHandle(TokenHandle);
  end;
end;

// 在Form创建时初始化
procedure TForm3.FormCreate(Sender: TObject);
begin
  ThisIsElevated := IsProcessElevated;
end;

2. 实现交替权限重启逻辑

修改你的btnRestartClick方法,针对两种情况分别处理:

procedure TForm3.btnRestartClick(Sender: TObject);
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  TokenHandle: THandle;
  DupTokenHandle: THandle;
  CurrentUserSid: PSID;
  TokenInformation: TTokenUser;
  ReturnLength: DWORD;
begin
  if ThisIsElevated then
  begin
    // 从管理员模式切换到非管理员模式:需要获取当前用户的非elevated令牌
    if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY or TOKEN_DUPLICATE or TOKEN_ASSIGN_PRIMARY, TokenHandle) then
    try
      // 获取当前用户的SID
      ReturnLength := 0;
      GetTokenInformation(TokenHandle, TokenUser, nil, 0, ReturnLength);
      GetMem(TokenInformation, ReturnLength);
      try
        if GetTokenInformation(TokenHandle, TokenUser, TokenInformation, ReturnLength, ReturnLength) then
        begin
          CurrentUserSid := TokenInformation.User.Sid;
          // 打开当前用户的非elevated令牌
          if OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, TokenHandle) then
          try
            if DuplicateTokenEx(TokenHandle, MAXIMUM_ALLOWED, nil, SecurityIdentification, TokenPrimary, DupTokenHandle) then
            try
              ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
              StartupInfo.cb := SizeOf(StartupInfo);
              // 用非elevated令牌启动自身程序
              if CreateProcessAsUser(DupTokenHandle, PChar(Application.ExeName), nil, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo) then
              begin
                CloseHandle(ProcessInfo.hThread);
                CloseHandle(ProcessInfo.hProcess);
                Application.Terminate;
              end;
            finally
              CloseHandle(DupTokenHandle);
            end;
          finally
            CloseHandle(TokenHandle);
          end;
        end;
      finally
        FreeMem(TokenInformation);
      end;
    finally
      CloseHandle(TokenHandle);
    end;
  end
  else
  begin
    // 从非管理员模式切换到管理员模式:用runas参数
    ShellExecute(Handle, 'runas', PChar(Application.ExeName), nil, nil, SW_SHOWNORMAL);
    Application.Terminate;
  end;
end;

关键说明

  • 当需要从elevated切换到非elevated时,核心是通过DuplicateTokenEx获取当前用户的非提升令牌,再用CreateProcessAsUser启动进程,这样新进程就会运行在普通用户权限下。
  • 非管理员切换到管理员时,直接用ShellExecute的runas参数即可,这个是你原本的逻辑,没问题。
  • 记得在代码中引入Windows单元,这些API都需要它。

内容的提问来源于stack exchange,提问作者user1580348

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 10:25:52