Creating a binary file that consists of a service and a normal application can be done very simple without using the service manager. Just check for SYSTEM account. It is very unlikely that your app is run as SYSTEM (only RunAsSys does ).

uses …, JwsclToken;
begin
  if JwIsSystem then
  begin
    try
    Application.Initialize;
    Application.CreateForm(TMyService, MyService);
    Application.Run;
  end
  else
  begin
  end;
end;

Update

Mark Robinson suggested (in a comment) to check for a “service.exe” parent process. Since I did not find a solution within seconds, I decided to show how it works. Quite in handy came a CodeProject article about finding a parent process. The code is also available through download. Use it on your own risk.

function GetParentProcess(out ProcessEntry32 : TProcessEntry32W; const ProcessID : DWORD = 0) : DWORD; overload;

function InternalGetParentProcess(
  const Condition : Boolean;
  out ProcessEntry32 : TProcessEntry32W; const ProcessID : DWORD = 0) : DWORD;
var
  hSnap : THandle;
  ProcEntry : TProcessEntry32W;
  currentID : DWORD;
  Continue : Boolean;

begin
  hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  result := INVALID_HANDLE_VALUE;

  ZeroMemory(@ProcessEntry32, sizeof(ProcessEntry32));

  try
    ProcEntry.dwSize := sizeof(ProcEntry);
    Continue := Process32FirstW(hSnap, ProcEntry);

    if (ProcessID = DWORD(-1)) or (ProcessID = 0) then
      CurrentID := GetCurrentProcessId
    else
      CurrentID := ProcessID;

    result := INVALID_HANDLE_VALUE;

    while (continue) do
    begin
      if CurrentID = ProcEntry.th32ProcessID then
      begin
        result := ProcEntry.th32ParentProcessID;

        if not Condition then
        begin
          InternalGetParentProcess(true, ProcessEntry32, result);
        end
        else
          ProcessEntry32 := ProcEntry;
      end;

      continue := Process32NextW(hSnap,ProcEntry) and (result = INVALID_HANDLE_VALUE);
    end;
  finally
    CloseHandle(hSnap);
  end;
end;

begin
  result := InternalGetParentProcess(false, ProcessEntry32, ProcessID);
end;

function GetParentProcess(const ProcessID : DWORD = 0) : DWORD; overload;
var ProcessEntry32 : TProcessEntry32W;
begin
  result := GetParentProcess(ProcessEntry32, ProcessID);
end;

function GetParentProcess(out ExeName : WideString; const ProcessID : DWORD = 0) : DWORD; overload;
var ProcessEntry32 : TProcessEntry32W;
begin
  result := GetParentProcess(ProcessEntry32, ProcessID);
  if result <> INVALID_HANDLE_VALUE then
    ExeName := ProcessEntry32.szExeFile
  else
    ExeName := ;
end;

var P : DWORD;
  Name : WideString;
begin
  P :=GetParentProcess(Name);
  writeln(P,‘ ‘,Name);
  readln;
end.

There are three versions of GetParentProcess, so you can get more information than usual necessary.
The reason why the first version has an internal function is that we need to iterate the process list twice to get the parent’s process name.

Finally all you have to do is to check for its name “services.exe”.

if CompareText(Name,‘services.exe’) then

However there can be a bad coincidence and another process who started your app is named like that. In this case we should just check for the full path.

function RetrieveModuleFileName(const PID : DWORD) : WideString;
var
  FileName : Array[0..MAX_PATH] of Widechar;
  hP : THandle;
begin
  hP := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, PID);
  if hP = 0 then
    RaiseLastOSError;

  try
    if GetModuleFileNameExW(hP,0,FileName, sizeof(FileName)) = 0 then
      RaiseLastOSError;
    result := FileName;
  finally
    CloseHandle(hP);
  end;
end;

Now we can be sure that it is the correct process.

function GetSystem32Path : WideString;
var Path : Array[0..MAX_PATH] of Widechar;
begin
  Result := ;
  if SUCCEEDED(SHGetFolderPathW(0,CSIDL_SYSTEM,0,SHGFP_TYPE_DEFAULT, @Path)) then
    result := IncludeTrailingBackslash(Path);  //Oopps, may convert unicode to ansicode
end;

var PID : DWORD;
begin
  PID := GetParentProcess(Name);

  Path := RetrieveModuleFileName(P);
  SysPath := GetSystem32Path+‘services.exe’;
  if CompareText(SysPath, Path) = 0 then
    //OK
  else
    //not a service
end.

We are not at the end. I should mention that your service application can also be in a service pool hosted by the parent process “svchost.exe”. In such a case of course, we have to check for “svchost.exe” instead of “services.exe”