【问题标题】:LogonUser + CreateProcessAsUser at Service = error 1314LogonUser + CreateProcessAsUser at Service = 错误 1314
【发布时间】:2012-10-31 22:20:36
【问题描述】:

我有一个使用 Delphi 7 创建的 Windows 服务,StartType = stSystem。

现在我需要启动一个应用程序来为我做一些事情。 此应用程序具有 MainForm 和其他 GDI 资源。 传递给应用程序的参数为某些控件(如编辑和备忘录)分配值,然后单击按钮......

我正在尝试这个:

var
  token: cardinal;
  si: TStartupInfo;
  pi: TProcessInformation;
begin
  if not LogonUser('admintest', '', 'secret123', LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, token) then
    RaiseLastOSError;

  try
    if not ImpersonateLoggedOnUser(token) then
      RaiseLastOSError;

    fillchar(si, sizeof(si), 0);
    si.cb := sizeof(si);
    si.lpDesktop := PChar('winsta0\default');
    if not CreateProcessAsUser(token, nil, '"c:\...\myapp.exe" -doCrazyThings', nil, nil, false, NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE, nil, nil, si, pi) then
      RaiseLastOSError;

    CloseHandle(pi.hThread);
    waitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
  finally
    CLoseHandle(token);
  end;
end;

当我将我的服务可执行文件作为普通应用程序 (-noservice) 运行时,它以 Forms.Application 启动并创建一个带有“开始”按钮的 MainForm。 *该按钮运行的代码与服务运行的代码相同,但它不起作用,并且它在 createprocessasuser 处发出错误代码 1314。*

为什么? SYSTEM服务和管理员启动的普通应用有什么区别?

我的环境是 Windows 7 Pro x64

我做错了什么? 我该如何解决这个问题? 有人可以发一个例子吗?

【问题讨论】:

  • CreateProcessAsUser error 1314 的可能重复项
  • Read the documentation。您用于在 noservice 模式下运行应用程序的用户帐户可能没有必要的权限,但在服务模式下运行应用程序时,SYSTEM 帐户确实具有权限。
  • 如果你正在运行询问 LOCALSYSTEM 那么你不需要登录用户和明文密码。如果你在桌面上运行,你可以简单地调用 CreateProcess。

标签: delphi


【解决方案1】:

错误 1314 是 ERROR_PRIVILEGE_NOT_HELD,这意味着您的调用线程缺少运行 CreateProcessAsUser() 所需的权限。您不需要也不应该冒充用户令牌来在用户桌面上启动新进程。在调用CreateProcessAsUser() 时,您应该让线程使用服务的凭据,而不是用户的凭据。它将确保新进程在您的用户帐户和桌面内运行,因此请摆脱对ImpersonateLoggedOnUser() 的调用,看看CreateProcessAsUser() 是否开始工作。

更新read the documentation

通常,调用 CreateProcessAsUser 函数的进程必须具有 SE_INCREASE_QUOTA_NAME 权限,并且如果令牌不可分配,则可能需要 SE_ASSIGNPRIMARYTOKEN_NAME 权限。如果此函数因 ERROR_PRIVILEGE_NOT_HELD (1314) 而失败,请改用 CreateProcessWithLogonW 函数。 CreateProcessWithLogonW 不需要特殊权限,但必须允许指定的用户帐户以交互方式登录。通常,最好使用 CreateProcessWithLogonW 创建具有备用凭据的进程。

【讨论】:

  • 我知道,我是说你应该删除ImpersonateLoggedOnUser()的调用。在服务中调用 CreateProcessAsUser() 时,您应该冒充用户。
  • 工作!我正在尝试将服务作为程序而不是作为服务运行的代码。我将重新提出我的问题...
  • @Remy:当我尝试启用 SeAssignPrimaryTokenPrivilege 时出现错误 1300。
  • 1300 is ERROR_NOT_ALL_ASSIGNED: "令牌不具有 NewState 参数中指定的一个或多个权限。即使没有调整权限,该函数也可能会成功并出现此错误值。PreviousState 参数表示已调整的权限。”。如果AdjustTokenPrivilege() 返回 1300,则表示您无权设置您尝试设置的权限。这是有道理的。例如,较低级别的用户不能分配较高级别的权限。如果可以,那将是安全漏洞。
【解决方案2】:

在 Vista 及更高版本上,Windows 服务在会话 0 中运行。交互式用户存在于会话 1 及更高版本中。这意味着 Windows 服务无法显示用户界面,并且确实无法在交互式会话中轻松启动进程。

现在,有很多方法可以launch interactive processes from a service。如果您对从您的服务启动交互式流程一无所知,那么该文章会告诉您所有您需要知道的信息。但是这样的技术非常棘手,绝对不推荐。建议您找到一种不同的方式在您的服务和交互式桌面之间进行通信。

通常的方法是运行标准桌面应用程序,可能使用HKLM\Software\Microsoft\Windows\CurrentVersion\Run 启动。并使用某种形式的 IPC 在桌面应用程序和服务之间进行通信。

【讨论】:

  • “Windows 服务无法...在交互式会话中启动进程”这完全不正确。这正是 CreateProcessAsUser() 在服务中的用途。我有几个使用它的服务,正确使用时它工作得很好。甚至 Microsoft 的 Session 0 Isolation 文档也说了这么多。
  • @Remy 是的,如果你碰巧有纯文本的用户密码,那是真的。请您向我展示推荐会话 0 服务启动交互式进程的 MS 文档部分。
  • 好的,雷米,你能发布你是如何做到这一点的例子吗?谢谢!
  • @DavidHeffernan:在用户桌面上运行进程不需要用户密码。如果用户已经登录,您甚至不需要登录。在我的服务中,我使用 WTS API 访问现有的登录桌面,它工作正常。我知道我在说什么,我多年来一直在编写服务,并且之前不得不处理 UAC 和 Session 0 Isolation。
  • @Remy 您的服务以LOCALSYSTEM 运行,对吗?你读到我回答的第二段了吗?
【解决方案3】:

您应该创建服务来完成后台工作,并且 GUI 应用程序应该只调用该服务。 IE 你总是有一个服务在运行。

考虑在后端使用 DataSnap 的东西。 MVC 方法在 Delphi 中并不像在其他一些语言中那样纯粹。控制器去任何方便的地方。数据集主要是一种折衷方案,唯一真正快速的数据处理方法是使用 DBexpress 和客户端上的一些组件来保持一切顺利。但它有效,值得学习。

服务不能有 gui 控件。不允许 TForm 后代。仅限 TService。 Delphi Projects / Service Application 下的新项目。您将获得一个具有与数据模块几乎相同的单元/表单的项目。即,不允许视觉控制。原因很明显。您需要学习记录、对象设计等才能使用服务。 Datasnap 是您最好的选择。它为您完成了大部分艰苦的工作。

博士。 Bob 有一本关于 XE2/3 的很好的书。如果您不熟悉面向对象设计,则需要先彻底了解这一点。

【讨论】:

    【解决方案4】:

    这是我用来做这种事情的代码

    procedure CreateNewProcess;
    var
      CmdLine: AnsiString;
      ErrorCode: Cardinal;
      ConnSessID: Cardinal;
      Token: Cardinal;
      App: AnsiString;
      FProcessInfo: _PROCESS_INFORMATION;
      FStartupInfo: _STARTUPINFOA;
    begin
      ZeroMemory(@FStartupInfo, SizeOf(FStartupInfo));
      FStartupInfo.cb := SizeOf(FStartupInfo);
      FStartupInfo.lpDesktop := nil;
    
      ConnSessID := WTSGetActiveConsoleSessionId;
    
      if WTSQueryUserToken(ConnSessID, Token) then
      begin
        if CreateProcessAsUser(Token, PAnsiChar(App), PAnsiChar(CmdLine),
          nil, nil, false, 0, nil, nil, FStartupInfo, FProcessInfo) = False
        then
        begin
          ErrorCode := GetLastError;
          try
            RaiseLastOSError(ErrorCode);
          except on E: Exception do
            EventLog.LogError(e.ClassName +': '+ e.Message);
          end;
        end;
      end;
    end;
    

    希望对你有帮助

    如果您希望服务在继续之前等待新进程终止,请添加此

        if CreateProcessAsUser(Token, PAnsiChar(App), PAnsiChar(CmdLine),
          nil, nil, false, 0, nil, nil, FStartupInfo, FProcessInfo) = False
        then
        begin
          ErrorCode := GetLastError;
          try
            RaiseLastOSError(ErrorCode);
          except on E: Exception do
            EventLog.LogError(e.ClassName +': '+ e.Message);
          end;
        end
        else
          WaitForSingleObject(FProcessInfo.hProcess, INFINITE);
    

    虽然无限等待可能是不可取的。

    【讨论】:

      猜你喜欢
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-28
      • 1970-01-01
      • 1970-01-01
      • 2021-06-17
      相关资源
      最近更新 更多