M Schenkel
M Schenkel

Reputation: 6364

Determine if running as VCL Forms or Service

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.

Upvotes: 16

Views: 5565

Answers (12)

Daniel Marschall
Daniel Marschall

Reputation: 3879

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

Upvotes: 3

Z.B.
Z.B.

Reputation: 1205

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;

Upvotes: 2

Molochnik
Molochnik

Reputation: 742

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

Upvotes: 2

kobik
kobik

Reputation: 21252

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.

Upvotes: 7

Ed Salgado
Ed Salgado

Reputation: 1

Check if your Applicatoin is an instance of TServiceApplication:

IsServiceApp := Application is TServiceApplication;

Upvotes: -1

Runner
Runner

Reputation: 6103

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;

Upvotes: 11

MajidTaheri
MajidTaheri

Reputation: 3983

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.

Upvotes: 2

Deltics
Deltics

Reputation: 23036

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.

Upvotes: 4

RRUZ
RRUZ

Reputation: 136401

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;

Upvotes: 4

M Schenkel
M Schenkel

Reputation: 6364

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.

Upvotes: 1

skamradt
skamradt

Reputation: 15538

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;

Upvotes: 10

Lieven Keersmaekers
Lieven Keersmaekers

Reputation: 58451

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

Upvotes: 5

Related Questions