3

I have a windows service that launches my application. This service is a Scheduler, to perform some custom actions defined by end-users.

It runs as LocalSystem account, and will launch my application (that has windows).

For this I use the functions LogongUser, LoadUserProfile, CreateProcessAsUser. This works perfectly but only if the user, to log on, is member of Administrators group.

I cannot leave the user as an Administrator, he needs to be a default user (member of Users group).

As administrator it runs fine from the service. But when the process starts as a default user, it terminates abnormally with:

ExitCode: 0xC0000142 STATUS_DLL_INIT_FAILED
Message: {DLL Initialization Failed} Initialization of the dynamic link library %hs failed. The process is terminating abnormally.

If I logon with that user (member of Users group) and launch the application manually, everything goes fine.

I have not figured out what is the library that is causing the problem. I checked the system event log, but there is no logs. I already tried to ImporsonateLoggedOnUser but without success.

This is the code:

type 
    TProcessRec = record
        Token: Cardinal;
        ProfileInfo: TProfileInfo;
        Job: Cardinal;
        Environment: Pointer;
        ProcessInfo: TProcessInformation;
        WinStat: HWINSTA;
      end;

      TProcess = class
      private
        fProcess: TProcessRec;
      public
        constructor Create(aUser, aDomain, aPassword, aCommand, aWorkingDir: string; aUtil: TJJWServiceUtil);
        destructor Destroy; override;

        procedure Terminate(aExitCode: Cardinal);

        function getExitCode: Cardinal;
      end;

    constructor TProcess.Create(aUser, aDomain, aPassword, aCommand, aWorkingDir: string; aUtil: TJJWServiceUtil);
    type
      TCreateProcessFuncType = (cpAsUser, cpWithLogon);
    const
      DES_CREATION_TYPE: array [TCreateProcessFuncType] of string =
        ('CreateProcessAsUser', 'CreateProcessWithLogon');
    var
      sa: TSecurityAttributes;
      si: TStartupInfo;
      limits : TJobObjectExtendedLimitInformation;
      wUser, wDomain, wPassword, wCommand, wWorkingDir: WideString;
      creationType: TCreateProcessFuncType;
    begin

        // se estiver rodando com a conta LocalSystem (em serviço de windows)
        if
          IsPrivilegeEnabled(SE_TCB_NAME) or IsPrivilegeEnabled(SE_INCREASE_QUOTA_NAME)
        then
          creationType := cpAsUser
        else
          creationType := cpWithLogon;

      aUtil.debug('ENV: ' + AnsiReplaceStr(GetEnvironmentVariable('PATH'), '%', '%%'));
      aUtil.debug('Criando novo processo em modo: ' + DES_CREATION_TYPE[creationType]);

      FillChar(fProcess, SizeOf(fProcess), 0);

      FillChar(sa, SizeOf(sa), 0);
      sa.nLength := SizeOf(sa);

      FillChar(si, SizeOf(si), 0);
      si.cb := SizeOf(si);

      case creationType of

        cpAsUser:
          begin
            aUtil.debug('Efetuando o login do usuário %s', [aUser]);
            // login
            if not LogonUser(PChar(aUser), PChar(aDomain), PChar(aPassword), LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, fProcess.Token) then
              RaiseLastOSError;

            try
              aUtil.debug('Carrengado o profile do usuário %s', [aUser]);

              // carrega o profile do usuário
              fProcess.ProfileInfo.dwSize := SizeOf(fProcess.ProfileInfo);
              fProcess.ProfileInfo.dwFlags := PI_NOUI;
              fProcess.ProfileInfo.lpUserName := PChar(aUser);
              if not LoadUserProfile(fProcess.Token, fProcess.ProfileInfo) then
                RaiseLastOSError;

              try
                aUtil.debug('Criando o bloco de variáveis de ambiente do usuário %s', [aUser]);
                // variaveis de ambiente
                if not CreateEnvironmentBlock(fProcess.Environment, fProcess.Token, false) then
                  RaiseLastOSError;

                aUtil.debug('Criando o JOB para associar o processo filho ao processo pai');
                // job para associar
                fProcess.Job := CreateJobObject(@sa, nil);
                if fProcess.Job = 0 then
                  RaiseLastOSError;

                try
                  // limita o job para matar o seu processo caso o processo pai termine antes
                  FillChar(Limits,SizeOf(Limits),0);
                  with Limits,BasicLimitInformation do
                    LimitFlags := JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE or JOB_OBJECT_LIMIT_BREAKAWAY_OK;

                  if not SetInformationJobObject(fProcess.Job, JobObjectExtendedLimitInformation, @limits, SizeOf(limits)) then
                    RaiseLastOSError;

                  try
                    fProcess.WinStat := CreateWindowStation(SCHEDULER_WINSTAT, 0, 0, nil);
                    if fProcess.WinStat = 0 then
                      RaiseLastOSError;
                    si.lpDesktop := PChar(SCHEDULER_WINSTAT);

                    aUtil.debug('Criando o processo: %s', [aCommand]);

                    // processo!
                    if not CreateProcessAsUser(
                      fProcess.Token,
                      nil,
                      PChar(aCommand),
                      @sa,
                      nil,
                      false,
                      CREATE_SUSPENDED or CREATE_BREAKAWAY_FROM_JOB or NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP or CREATE_UNICODE_ENVIRONMENT,
                      fProcess.Environment,
                      PChar(aWorkingDir),
                      si,
                      fProcess.ProcessInfo)
                    then
                      RaiseLastOSError;

                    try
                      aUtil.debug('Associando o JOB ao processo filho');
                      // associa ao processo do plugin o JOB (gruda nele!)
                      if not AssignProcessToJobObject(fProcess.Job, fProcess.ProcessInfo.hProcess) then
                        RaiseLastOSError;

                      // VOA PROCESSO, VOA!
                      if ResumeThread(fProcess.ProcessInfo.hThread) = $FFFFFFFF then
                        RaiseLastOSError;

                      aUtil.debug('Processo disparado');
                    except
                      TerminateProcess(fProcess.ProcessInfo.hProcess, Cardinal(-1));

                      CloseHandle(fProcess.ProcessInfo.hThread);
                      CloseHandle(fProcess.ProcessInfo.hProcess);

                      FillChar(fProcess.ProcessInfo, SizeOf(fProcess.ProcessInfo), 0);

                      raise;
                    end;

                  except
                    DestroyEnvironmentBlock(fProcess.Environment);
                    fProcess.Environment := nil;
                    raise;
                  end;

                except
                  CloseHandle(fProcess.Job);
                  fProcess.Job := 0;
                  raise;
                end;

              except
                UnloadUserProfile(fProcess.Token, fProcess.ProfileInfo.hProfile);
                FillChar(fProcess.ProfileInfo, SizeOf(fProcess.ProfileInfo), 0);
                raise;
              end;

            except
              CloseHandle(fProcess.Token);
              fProcess.Token := 0;
              raise;
            end;
          end;

        //////////////////////////////////////////////////////////////////////////////

        cpWithLogon:
          begin
            wUser := aUser;
            wDomain := aDomain;
            wPassword := aPassword;
            wCommand := aCommand;
            wWorkingDir := aWorkingDir;

            aUtil.debug('Disparando o processo: %s', [aCommand]);

            // o CreateProcessWithLogon já cria um JOB para evitar
            // que o processo fique órfão no limbo
            if not CreateProcessWithLogon(
               PWideChar(wUser),
               PWideChar(wDomain),
               PWideChar(wPassword),
               LOGON_WITH_PROFILE,
               nil,
               PWideChar(wCommand),
               NORMAL_PRIORITY_CLASS or CREATE_NEW_CONSOLE or CREATE_NEW_PROCESS_GROUP,
               nil,
               PWideChar(wWorkingDir),
               si,
               fProcess.ProcessInfo
             )
            then
              RaiseLastOSError;
          end;

          end;
    end;

    destructor TProcess.Destroy;
    begin
      if fProcess.ProcessInfo.hProcess > 0 then
      begin
        if getExitCode = STILL_ACTIVE then
          TerminateProcess(fProcess.ProcessInfo.hProcess, Cardinal(-1));

        CloseHandle(fProcess.ProcessInfo.hThread);
        CloseHandle(fProcess.ProcessInfo.hProcess);
      end;

      if fProcess.Environment <> nil then
        DestroyEnvironmentBlock(fProcess.Environment);

      if fProcess.Job > 0 then
        CloseHandle(fProcess.Job);

      if fProcess.ProfileInfo.hProfile > 0 then
        UnloadUserProfile(fProcess.Token, fProcess.ProfileInfo.hProfile);

      if fProcess.Token > 0 then
        CloseHandle(fProcess.Token);

      if fProcess.WinStat > 0 then
        CloseWindowStation(fProcess.WinStat);

      FillChar(fProcess, SizeOf(fProcess), 0);

      inherited;
    end;

    function TProcess.getExitCode: Cardinal;
    begin
      if not GetExitCodeProcess(fProcess.ProcessInfo.hProcess, Result) then
        RaiseLastOSError;
    end;

This can be usefull

Starting an Interactive Client Process in C++

Beto Neto
  • 3,962
  • 7
  • 47
  • 81

1 Answers1

1

According to this: Starting an Interactive Client Process in C++

And this: CreateProcessAsUser() windowstations and desktops

I solved the problem creating a new WindowStation and Deskyop to launch the process:

myWinStat := CreateWindowStation('myWinStat', 0, GENERIC_READ or GENERIC_WRITE, nil);
myDesk := CreateDesktop('myDesktop', nil, nil, 0, GENERIC_READ or GENERIC_WRITE, nil);
startupInfo.lpDesktop := 'myDesktop';
try
   // createProcessAsUser...
   // Wait for process termination...
finally
   CloseDesktop(myDesk);
   CloseWindowStation(myWinStat);
end;
Beto Neto
  • 3,962
  • 7
  • 47
  • 81
  • 5
    I assumed that you were already running the process on an interactive station. If you'd shown some code it would have been trivial for us to have noticed that you were not doing so. For this to be a valid answer you need to 1. Fill out the question with the missing details and 2. Explain what this does and why it helps. – David Heffernan Mar 01 '13 at 15:15
  • 6
    Why are you creating a new workstation instead of using the user's existing desktop? – Remy Lebeau Mar 01 '13 at 15:52
  • @remy this is a windows service, I cannot assume that always will exists a user desktop active. This service is for computers that are Servers. – Beto Neto Mar 01 '13 at 16:55
  • 2
    @BetoNeto: you are logging in the user interactively, so there should be an interactive desktop available for that user, even if it is not visible to the person sitting at the machine. So I would think that setting `startupInfo.lpDesktop := 'Winsta0\default';` would work. `CreateWindowStation()` creates a Window Station, not a Desktop. Call `CreateDesktop()` after `CreateWindowStation()` if needed. `startupInfo.lpDesktop` expects a name of a Desktop, not a Window Station. Both articles you link to show `lpDesktop` being set to a Window Station and a Desktop, not a Window Station by itself. – Remy Lebeau Mar 01 '13 at 21:15
  • @RemyLebeau I think that comment deserves to be an answer. After all it is the answer. – David Heffernan Mar 01 '13 at 23:10
  • @Remy: I'm tried startupInfo.lpDesktop := 'Winsta0\default', but I got the same error at exitcode. I tried CreateWindowStation and CreateDesktop (using 'MY_WINSTAT_NAME\MY_DESK_NAME') and I got the same error again. But using only the CreateWindowStation name all works perfectly. – Beto Neto Mar 05 '13 at 12:15