16

I have code which is used both in services and within VCL Form applications (win32 application). How can I determine if the underlying application is running as a NT Service or as an application?

Thanks.

stukelly
  • 4,257
  • 3
  • 37
  • 44
M Schenkel
  • 6,294
  • 12
  • 62
  • 107
  • 1
    I'm curious about what your code does such that it would need to know the difference. – Rob Kennedy Oct 14 '09 at 15:20
  • 1
    @Rob -- Actually I could see this as an issue where you have a common routine that is used in BOTH application and a service...when running as a service errors should be logged, but when running as an application errors should also be displayed to the user. – skamradt Oct 14 '09 at 15:31
  • 3
    The application code should display or log the exception. The library code should do neither. If the library code must do one of those things, it can provide a callback function for the application code to set. The application knows whether it's a service intrinsically. – Rob Kennedy Oct 14 '09 at 15:35
  • You need to know if you're running as a service if you are using WinInet... It fails quietly if you're running as a service, and it's good to raise an exception to let the user of your library know. – Nat Jun 04 '12 at 14:36

12 Answers12

11

BEGIN OF EDIT

Since this still seems to be getting some attention I decided to update the answer with missing info and newer windows patches. In any case you should not copy / paste the code. The code is just a showcase on how the things should be done.

END OF EDIT:

You can check if the parent process is SCM (service control manager). If you are running as service this is always the case and never the case if running as standard application. Also I think that SCM has always the same PID.

You can check it like this:

type
  TAppType = (atUnknown, atDesktop, atService);

var
  AppType: TAppType;

function InternalIsService: Boolean;
var
  PL: TProcessList;
  MyProcessId: DWORD;
  MyProcess: PPROCESSENTRY32;
  ParentProcess: PPROCESSENTRY32;
  GrandParentProcess: PPROCESSENTRY32;
begin
  Result := False;

  PL := TProcessList.Create;
  try
    PL.CreateSnapshot;
    MyProcessId := GetCurrentProcessId;

    MyProcess := PL.FindProcess(MyProcessId);
    if MyProcess <> nil then
    begin
      ParentProcess := PL.FindProcess(MyProcess^.th32ParentProcessID);
      if ParentProcess <> nil then
      begin
        GrandParentProcess := PL.FindProcess(ParentProcess^.th32ParentProcessID);

        if GrandParentProcess <> nil then
        begin
          Result := SameText(string(ParentProcess^.szExeFile), 'services.exe') and
            (SameText(string(GrandParentProcess^.szExeFile), 'winlogon.exe') or
             SameText(string(GrandParentProcess^.szExeFile), 'wininit.exe'));
        end;
      end;
    end;
  finally
    PL.Free;
  end; 
end;

function IsService: Boolean;
begin
  if AppType = atUnknown then
  begin
    try
      if InternalIsService then
        AppType := atService
      else
        AppType := atDesktop;
    except
      AppType := atService;
    end;
  end;

  Result := AppType = atService;
end;

initialization
  AppType := atUnknown;

The TProcessList is implemented like this (again THashTable is not included but any hash table should be fine):

type
  TProcessEntryList = class(TList)
  private
    function Get(Index: Integer): PPROCESSENTRY32;
    procedure Put(Index: Integer; const Value: PPROCESSENTRY32);
  public
    property Items[Index: Integer]: PPROCESSENTRY32 read Get write Put; default;
    function Add(const Entry: TProcessEntry32): Integer; reintroduce;
    procedure Clear; override;
  end;

  TProcessList = class
  private
    ProcessIdHashTable: THashTable;
    ProcessEntryList: TProcessEntryList;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure CreateSnapshot;
    function FindProcess(const ProcessId: DWORD): PPROCESSENTRY32;
  end;

implementation

{ TProcessEntryList }

procedure TProcessEntryList.Clear;
var
  i: Integer;
begin
  i := 0;
  while i < Count do
  begin
    FreeMem(Items[i]);
    Inc(i);
  end;

  inherited;
end;

procedure TProcessEntryList.Put(Index: Integer; const Value: PPROCESSENTRY32);
var
  Item: Pointer;
begin
  Item := inherited Get(Index);
  CopyMemory(Item, Value, SizeOf(tagPROCESSENTRY32));
end;

function TProcessEntryList.Get(Index: Integer): PPROCESSENTRY32;
begin
  Result := PPROCESSENTRY32(inherited Get(Index));
end;

function TProcessEntryList.Add(const Entry: TProcessEntry32): Integer;
var
  EntryCopy: PPROCESSENTRY32;
begin
  GetMem(EntryCopy, SizeOf(tagPROCESSENTRY32));
  CopyMemory(EntryCopy, @Entry, SizeOf(tagPROCESSENTRY32));

  Result := inherited Add(EntryCopy);  
end;

{ TProcessList }

constructor TProcessList.Create;
begin
  inherited;

  ProcessEntryList := TProcessEntryList.Create;
  ProcessIdHashTable := THashTable.Create;
end;

destructor TProcessList.Destroy;
begin
  FreeAndNil(ProcessIdHashTable);
  FreeAndNil(ProcessEntryList);

  inherited;
end;

function TProcessList.FindProcess(const ProcessId: DWORD): PPROCESSENTRY32;
var
  ItemIndex: Integer;
begin
  Result := nil;
  if not ProcessIdHashTable.ContainsKey(IntToStr(ProcessId)) then
    Exit;

  ItemIndex := Integer(ProcessIdHashTable.Item[IntToStr(ProcessId)]);
  Result := ProcessEntryList.Items[ItemIndex];
end;

procedure TProcessList.CreateSnapshot;
var
  SnapShot: THandle;
  ProcessEntry: TProcessEntry32;
  ItemIndex: Integer;
begin
  SnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if SnapShot <> 0 then
  try
    ProcessEntry.dwSize := SizeOf(ProcessEntry);
    if Process32First(SnapShot, ProcessEntry) then
    repeat
      ItemIndex := ProcessEntryList.Add(ProcessEntry);
      ProcessIdHashTable.Add(IntToStr(ProcessEntry.th32ProcessID), TObject(ItemIndex));
    until not Process32Next(SnapShot, ProcessEntry);
  finally
    CloseHandle(SnapShot);
  end;
end;
Runner
  • 6,073
  • 26
  • 38
  • +1, much better approach (even though the whole idea of checking for execution in a service isn't sound). I have a service coded without any VCL support for services, so most of the other checks would fail for it. – mghie Oct 14 '09 at 21:23
  • I agree, the whole idea is a little hack. But the reality is, that there are legitimate reasons for checking sometimes. – Runner Oct 15 '09 at 06:24
  • How can you check the "Parent Process"? – M Schenkel Oct 16 '09 at 14:05
  • What is a `TProcessList`? (on behalf of poster of [this](http://stackoverflow.com/a/23425449/243614) answer) – Sertac Akyuz May 02 '14 at 12:48
  • **Problem:** I could not use it since neither `TProcessList`, nor `CreateSnapshot` is defined. Searching for "TProcessList CreateSnapshot" in Google will just find 7 pages, which are mirrors/quotes of this page. There is probably no public code available for `TProcessList`. **Another problem:** At my computer (Win7 Pro x64), the "services.exe" is NOT inside "winlogon.exe". It is inside "wininit.exe". Since it seems to be an implementation detail of Windows, I would suggest not querying the grand parent. Also, services.exe does not need to be the direct parent, since processes could be forked. – Daniel Marschall Jun 01 '14 at 19:56
  • I clarified things a little. But it was about approach not he code copy / paste ;) By the way, we use this approach in production and have been for years and it still works. – Runner Jun 03 '14 at 11:45
  • Uses list is missing, what/where is THashTable ? –  May 06 '20 at 08:45
10

The application object (Forms.application) mainform will be nil if it is not a forms based application.

uses
  Forms, ... ;

function IsFormBased : boolean;
begin
  Result := Assigned(Forms.Application.MainForm);
end;
skamradt
  • 15,366
  • 2
  • 36
  • 53
7

How about matching GetCurrentProcessId against EnumServicesStatusEx?
The lpServices parameter points to a buffer that receives an array of ENUM_SERVICE_STATUS_PROCESS structures. The match is done against the enumerated service process ID: ServiceStatusProcess.dwProcessId in that structure.

Another option is using WMI to query for Win32_Service instances where ProcessId=GetCurrentProcessId.

kobik
  • 21,001
  • 4
  • 61
  • 121
5

I doubt that

System.IsConsole
System.IsLibrary

will give you the expected results.

All I can think of is to pass an Application object as TObject to the method where you need to make that distinction and test for the passed object's classname being a

TServiceApplication 
or
TApplication

That said, there shouldn't be a need for you to know if your code is running in a service or a GUI. You should probably rethink your design and make the caller to pass an object to handle messages you want (or don't want) to show. (I assume it is for showing messages/exceptions you'd like to know).

Lieven Keersmaekers
  • 57,207
  • 13
  • 112
  • 146
  • Unfortunately Application is declared in BOTH Forms and SvcMgr and just using either automatically creates an instance so you can't check application directly. – skamradt Oct 14 '09 at 16:08
  • 1
    @skamradt, if you pass it as a TObject and check on classname there's no need to use SvcMgr and/or Forms, hence they don't get created automatically. The calling code offcourse uses either SvcMgr or Forms. – Lieven Keersmaekers Oct 14 '09 at 17:17
4

You can try something like this

Function IsMyformInsideaWindowsService(aForm:TObject) :Boolean;
Begin
   Result:=aForm.ClassParent.ClassName='TService';  //When a form is running under a service the Class Parent is a TService
End;
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • The problem with the second function is one of scope and order of units in the uses clause. If you use forms after svcmgr in your uses clause then this will ALWAYS return false, or vice versa. – skamradt Oct 14 '09 at 16:06
4

A single project cannot (or I should say ideally is not) both a service and a forms application, at least not if you are able to distinguish between the Forms Application object and the SvcMgr Application object - you must presumably have separate projects for the forms code and the service code.

So perhaps the easiest solution is a project conditional define. i.e. in your project settings for the service project add "SERVICEAPP" to the Conditional Defines.

Then whenever you need to change behaviour simply:

{$ifdef SERVICEAPP}
{$else}
{$endif}

For belts and braces you might adopt one of the previously described tests within some startup code to ensure that your project has been compiled with the expected symbol defined.

program ... ;

 :

begin
{$ifdef SERVICEAPP}
  // test for service app - ASSERT if not
{$else}
  // test for forms app - ASSERT if not
{$endif}
  :
end.

It is possible that your Forms app is actually running as a service, using the crude technique that allows any application to be running as a service.

In that case of course your app will always be a Forms application and the easiest way to handle that situation is to have a command line switch that you specify only in the service definition for your executable so that your app can respond appropriate by testing for that command line switch.

This does allow you to more easily test your "service mode" behaviour of course, since you can run your app in "debug" mode with that switch defined from within the IDE, but it's not an ideal way to build a service application so I would not recommend it on the strength of that alone. It's a technique that is usually only used when you have an EXE that you wish to run as a service but have no way to modify the source code to turn it into a "proper" service.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • It is possible (with some conditional code in the dpr) to create a single EXE which acts as both a service and GUI application - not always a good idea, but possible. – Gerry Coll Oct 14 '09 at 20:37
  • Yes it's possible, for example see the socket server (scktsrvr.dpr). – Ondrej Kelle Oct 15 '09 at 02:01
  • We have used the conditional defines in the past. The problem is, sometimes we forgot to include it. But I think your "assert" is a good "check". – M Schenkel Oct 16 '09 at 14:01
3

The answer from "Runner" ( https://stackoverflow.com/a/1568462 ) looked very helpful, but I could not use it since neither TProcessList, nor CreateSnapshot is defined. Searching for "TProcessList CreateSnapshot" in Google will just find 7 pages, including this one and mirrors/quotes of this page. No code exists. Alas, my reputation is too low to send him a comment, asking where I can find the code of TProcessList.

Another problem: At my computer (Win7 x64), the "services.exe" is NOT inside "winlogon.exe". It is inside "wininit.exe". Since it seems to be an implementation detail of Windows, I would suggest not querying the grand parent. Also, services.exe does not need to be the direct parent, since processes could be forked.

So this is my version using TlHelp32 directly, solving all the problems:

uses
  Classes, TlHelp32;

function IsRunningAsService: boolean;

  function FindProcess(FSnapshotHandle: THandle; PID: DWORD; var lppe: TProcessEntry32): boolean;
  var
    ContinueLoop: BOOL;
  begin
    ContinueLoop := Process32First(FSnapshotHandle, lppe);
    while Integer(ContinueLoop) <> 0 do
    begin
      if lppe.th32ProcessID = PID then
      begin
        result := true;
        Exit;
      end;
      ContinueLoop := Process32Next(FSnapshotHandle, lppe);
    end;
    result := false;
  end;

var
  CurProcessId: DWORD;
  FSnapshotHandle: THandle;
  FProcessEntry32: TProcessEntry32;
  ExeName, PrevExeName: string;
  DeadlockProtection: TList<Integer>;
begin
  Result := false;

  FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  DeadlockProtection := TList<Integer>.Create;
  try
    CurProcessId := GetCurrentProcessId;
    FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
    ExeName := '';
    while FindProcess(FSnapshotHandle, CurProcessId, FProcessEntry32) do
    begin
      if DeadlockProtection.IndexOf(FProcessEntry32.th32ProcessID) > -1 then break;
      DeadlockProtection.Add(FProcessEntry32.th32ProcessID);

      PrevExeName := ExeName;
      ExeName     := FProcessEntry32.szExeFile;

      (*
      Result := SameText(PrevExeName, 'services.exe') and // Parent
                SameText(ExeName,     'winlogon.exe');    // Grandparent
      *)

      Result := SameText(ExeName, 'services.exe'); // Parent

      if Result then Exit;

      CurProcessId := FProcessEntry32.th32ParentProcessID;
    end;
  finally
    CloseHandle(FSnapshotHandle);
    DeadlockProtection.Free;
  end;
end;

This code works, also even in applications without MainForm (e.g. CLI apps).

Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
  • 1
    Note: I found another problem today. For some reason I had a circular reference between explorer.exe and bds.exe (Delphi XE4): PID=4656; Parent=3928; szExeName=explorer.exe PID=3928; Parent=4656; szExeName=bds.exe PID=4656; Parent=3928; szExeName=explorer.exe ... . Therefore I have added a deadlock protection. – Daniel Marschall Jun 23 '14 at 10:35
  • This looks like a good answer to a thorny problem. (Long time ago, but I think you forgot to create the DeadlockProtection list.) – TomB Mar 09 '22 at 01:17
2

you can use GetStdHandle method for get out console handle.when applications run as windows service has not output console.if GetStdHandle equals zero means your application run as windows service.

{$APPTYPE CONSOLE} // important

uses
   uServerForm in 'uServerForm.pas' {ServerForm},
 uWinService in 'uWinService.pas' {mofidWinServer: TService},

  Windows,
  System.SysUtils,
  WinSvc,
  SvcMgr,
  Forms,etc;
function RunAsWinService: Boolean;
var
  H: THandle;
begin
  if FindCmdLineSwitch('install', ['-', '/'], True) then
    Exit(True);
  if FindCmdLineSwitch('uninstall', ['-', '/'], True) then
    Exit(True);
  H := GetStdHandle(STD_OUTPUT_HANDLE);
  Result := H = 0;
end;


begin       
  if RunAsWinService then
  begin

    SvcMgr.Application.Initialize;
    SvcMgr.Application.CreateForm(TmofidWinServer, mofidWinServer);
    SvcMgr.Application.Run;
  end
  else
  begin
    Forms.Application.Initialize;
    Forms.Application.CreateForm(TServerForm, ServerForm);
    Forms.Application.Run;
  end;
end.
MajidTaheri
  • 3,813
  • 6
  • 28
  • 46
2

I didn't find the simple answer which can be used easily and does not require recompilation and allows using one exe as a service and an application. You can install your program as a service with the command line parameter like “…\myapp.exe –s” and then check it from the program:

if ParamStr(ParamCount) = '-s' then

Molochnik
  • 704
  • 5
  • 23
2

You can base the check on checking the session ID of the current process. All services runs with session ID = 0.

function IsServiceProcess: Boolean;
var
  LSessionID, LSize: Cardinal;
  LToken: THandle;
begin
  Result := False;
  LSize := 0;
  if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, LToken) then
    Exit;

  try
    if not GetTokenInformation(LToken, TokenSessionId, @LSessionID, SizeOf(LSessionID), LSize) then
      Exit;

    if LSize = 0 then
      Exit;

    Result := LSessionID = 0;
  finally
    CloseHandle(LToken);
  end;
end;
Z.B.
  • 1,185
  • 9
  • 18
  • Be aware, that this wont work in WinXP. In Windows XP [all services run in session 0 as well as the first user who logs on](https://techcommunity.microsoft.com/t5/ask-the-performance-team/application-compatibility-session-0-isolation/ba-p/372361)! – yonojoy Aug 11 '20 at 12:54
  • @yonojoy that is because on XP the first user to login runs in session 0. Another reason this might not work, in any Windows version, is if this code runs in an EXE that is spawned by a service. It will run in the service's session by default, but won't itself be a service process. – Remy Lebeau Mar 10 '22 at 00:07
1

I actually ended up checking the application.showmainform variable.

The problem with skamradt's isFormBased is that some of this code is called before the main form is created.

I am using a software library called SvCom_NTService from aldyn-software. One of purposes is for errors; either to log them or show a message. I totally agree with @Rob; our code should be better maintained and handle this outside of the functions.

The other intention is for failed database connections and queries; I have different logic in my functions to open queries. If it is a service then it will return nil but continue the process. But if failed queries/connections occur in an application then I would like to display a messaage and halt the application.

M Schenkel
  • 6,294
  • 12
  • 62
  • 107
-1

Check if your Applicatoin is an instance of TServiceApplication:

IsServiceApp := Application is TServiceApplication;
ataman
  • 2,644
  • 17
  • 22
  • If this is a n answer then please reword it to make it one. And please explain why it would work. – Rohit Gupta Nov 07 '15 at 20:03
  • Unfortunately, this simple approach is not working. As soon you uses Vcl.SvcMgr for referencing to TServiceApplication, you get a valid variable Application of TSerivceApplication. – Schneider Infosystems Ltd Dec 29 '20 at 15:39
  • `Vcl.SvcMgr` uses `Vcl.Forms` internally, so there will be two `Application` objects active at runtime - `Vcl.SvcMgr.Application` and `Vcl.Forms.Application`. – Remy Lebeau Mar 10 '22 at 00:09