2

I have a windows service created with Delphi 7, with StartType = stSystem.

Now I need to launch an application to make some things for me. This application has a MainForm and other GDI resources. The parameter passed to the application assigns values for some controls (like edits and memos) and then click at a button....

I'm trying this:

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;

When I run my service executable as a normal application (-noservice), it starts as a Forms.Application and creates a MainForm with a button "Start". *The button runs the same code that service run, but it doesn't works and it's rasing the eror code 1314 at createprocessasuser.*

Why? What is the diference between SYSTEM service and a normal application launched by a administrator?

My environment is a Windows 7 Pro x64

What am I doing wrong? How can I solve this? Can someone post an example?

Beto Neto
  • 3,962
  • 7
  • 47
  • 81
  • 1
    possible duplicate of [CreateProcessAsUser error 1314](http://stackoverflow.com/questions/1475577/createprocessasuser-error-1314) – Ken White Oct 30 '12 at 19:09
  • [Read the documentation](http://msdn.microsoft.com/en-us/library/windows/desktop/ms682429.aspx). The user account you are using to run the app in noservice mode likely does not have the necessary privileges, but when running the app in service mode, the SYSTEM account does have the privileges. – Remy Lebeau Oct 30 '12 at 19:55
  • If you are running ask LOCALSYSTEM then you don't need LogonUser and plaintext password. If you are running on desktop you can simply call CreateProcess. – David Heffernan Oct 30 '12 at 20:19

4 Answers4

3

Error 1314 is ERROR_PRIVILEGE_NOT_HELD, which means your calling thread is missing a required privilege to run CreateProcessAsUser(). You don't need to, nor should you be, impersonating the user token in order to launch a new process in the user's desktop. You should be letting the thread use the service's credentials, not the user's credentials, when calling CreateProcessAsUser(). It will make sure the new process is run inside the user's account and desktop for you, so get rid of the call to ImpersonateLoggedOnUser() and see if CreateProcessAsUser() starts working.

Update: read the documentation:

Typically, the process that calls the CreateProcessAsUser function must have the SE_INCREASE_QUOTA_NAME privilege and may require the SE_ASSIGNPRIMARYTOKEN_NAME privilege if the token is not assignable. If this function fails with ERROR_PRIVILEGE_NOT_HELD (1314), use the CreateProcessWithLogonW function instead. CreateProcessWithLogonW requires no special privileges, but the specified user account must be allowed to log on interactively. Generally, it is best to use CreateProcessWithLogonW to create a process with alternate credentials.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I know, I'm saying you should **REMOVE** the call to `ImpersonateLoggedOnUser()`. You should **NOT** be impersonating the user when calling `CreateProcessAsUser()` in a service. – Remy Lebeau Oct 30 '12 at 19:05
  • WORKED! I was trying the code running the service as a program not as service. I will reformulate my question... – Beto Neto Oct 30 '12 at 19:33
  • @Remy: when I try to enable the SeAssignPrimaryTokenPrivilege occurred the error 1300. – Beto Neto Oct 31 '12 at 10:36
  • 1300 is `ERROR_NOT_ALL_ASSIGNED`: "The token does not have one or more of the privileges specified in the NewState parameter. The function may succeed with this error value even if no privileges were adjusted. The PreviousState parameter indicates the privileges that were adjusted.". If `AdjustTokenPrivilege()` returns 1300, it means you don't have permission to set the privilege(s) that you are trying to set. Which makes sense. Lower-level users cannot assign higher-level privileges, for instance. That would be a security breach if they could. – Remy Lebeau Oct 31 '12 at 17:51
1

On Vista and later, windows services run in session 0. Interactive users exist in session 1 and up. This means that windows services cannot show user interface and indeed cannot easily start processes in the interactive session.

Now, there are ways to launch interactive processes from a service. If you are dead set on launching an interactive process from your service, then that article tells you all your need to know. But such techniques are very tricky and absolutely not to be recommended. The recommendation is that you find a different way to communicate between your service and the interactive desktop.

The normal approach is to run a standard desktop app, perhaps started using HKLM\Software\Microsoft\Windows\CurrentVersion\Run. And use some form of IPC to communicate between the desktop app and the service.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • "windows services cannot ... start processes in the interactive session" that is completely not true. That is exactly what CreateProcessAsUser() is used for in a service. I have several services that use it, it works just fine when used correctly. Even Microsoft's Session 0 Isolation documentation says as much. – Remy Lebeau Oct 30 '12 at 18:50
  • @Remy Yeah, if you happen to have the user's password in plain text that is true. Please can you show me the part of the MS documentation that recommends session 0 services launching interactive processes. – David Heffernan Oct 30 '12 at 18:54
  • OK Remy, and can you post example how you do this? Thanks! – Beto Neto Oct 30 '12 at 18:55
  • @DavidHeffernan: You don't need the user's password to run processes in the user's desktop. You don't even need to log in to the user if they are already logged in. In my services, I use the WTS API to access existing logged in desktops, it works fine. I know what I'm talking about, I've been writing services for years, and have had to deal with UAC and Session 0 Isolation before. – Remy Lebeau Oct 30 '12 at 18:58
  • @Remy Your service runs as `LOCALSYSTEM` right? Did you get to the second paragraph of my answer? – David Heffernan Oct 30 '12 at 18:58
  • @DavidHeffernan: [Session 0 Isolation](http://msdn.microsoft.com/en-us/library/windows/desktop/bb756986.aspx): "For more complex UI, use the CreateProcessAsUser function to create a process in the user's session." – Remy Lebeau Oct 30 '12 at 19:02
  • @DavidHeffernan: [Impact of Session 0 Isolation on Services and Drivers in Windows](http://msdn.microsoft.com/en-us/windows/hardware/gg463353.aspx): "In the rare situation where the service must initiate a user interaction and the agent is not already running, the service should call the CreateProcessAsUser API to start the agent. The agent can then initiate all UI interactions." – Remy Lebeau Oct 30 '12 at 19:02
  • @DavidHeffernan: some of my services run a `LOCALSYSTEM`, some as `LOCALSERVICE` or `NETWORKSERVICE`, and some as specific users. – Remy Lebeau Oct 30 '12 at 19:03
  • @RemyLebeau As I read the article I linked to, you can either use `LOCALSYSTEM` and WTS API. But we aren't supposed to use `LOCALSYSTEM`. Or you can use a different account for the service, but then you need token stealing. – David Heffernan Oct 30 '12 at 19:06
1

You should be creating the service to do the background work, and the GUI application should only call the service. IE you always have a service running.

Consider using the DataSnap stuff for the back end. An MVC approach is not pure in Delphi like it is in some other languages. The controller goes wherever it is convenient. Datasets are mostly a compromise, and the only really fast way to do data is with DBexpress and a whack of components on the client to keep it all happy. But it works and it is worth learning.

Services cant have gui controls. No TForm descendents allowed. TService only. New project under Delphi Projects / Service Application. You get a project with a unit / form that is pretty much the same as a data module. ie, no visual controls allowed. Reasons are pretty obvious. You need to learn records, object design etc. to use services. Datasnap is your best bet for that. It does most of the hard work for you.

Dr. Bob has a pretty good book on it for XE2/3. If you are new to object oriented design you need to learn that thoroughly first.

Michael
  • 587
  • 3
  • 14
0

Here is the code i use to do this kind of thing

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;

Hope this helps

if you want the service to wait for the new process to terminate before continuing add this

    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);

although and infinite wait is probably not advisable.

Mike Taylor
  • 2,376
  • 2
  • 17
  • 33