Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do we tell if a C++ application is launched as a Windows service?

We have a console app which we launch from command prompt for debugging, but we also launch this as an NT service for production.

Right now, the code has this logic:

if (__argc <= 1) {
  assumeService();
} else {
  assumeForgound();
}

Is there a better way to check how the process has been launched? We're an open source project, so every time we get a new Windows developer we have to explain that they must specify the -f arg to stop the app from connecting to the service controller.

What about checking the parent process?

Update:

I forgot to mention that we're using C++ (unmanaged).

like image 940
Nick Bolton Avatar asked Dec 29 '09 14:12

Nick Bolton


People also ask

How to run any application as a Windows service?

How to Run Any Application as a Windows Service. Method 1. Run Application as Windows Service by using RunAsService utility. Method 2. Run Program as Service by using NSSM utility. Method 1. How to run any application as a Windows Service with 'Run As Service' Utility.

How to check if a service is running or not?

You could check whether process parent is services.exe or svchost.exe. Or you could query the service control manager using WinApi whether your service is started and the current process id is equal to the one of the started service.

How do I start a service from the command line?

At the Command Prompt, use the following syntax to create the new service: SC CREATE <servicename> Displayname= "<servicename>" binpath= "srvstart.exe <servicename> -c <path to srvstart config file>" start= <starttype> There are a couple of things to note in that command. First, each equal sign (=) has a space after it.

How do I create a service in Windows 10?

Your next step is using the Windows Service Controller (SC) command to create the new service based on the criteria in your configuration file. Open Command Prompt by right-clicking the Start menu (or pressing Windows+X), choosing “Command Prompt (Admin)”, and then clicking Yes to allow it to run with administrative privileges.


2 Answers

You could check whether process parent is services.exe or svchost.exe. Or you could query the service control manager using WinApi whether your service is started and the current process id is equal to the one of the started service.

In C# the following code would do that (since it is WinApi-based this should work similarly in C++, sample code here):

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        if (IsRunningAsService("myServiceName"))
        {
            Console.WriteLine("I'm a service.");
        }
        else
        {
            Console.WriteLine("I'm not a service.");
        }
    }

    static bool IsRunningAsService(string serviceName)
    {
        IntPtr serviceManagerHandle = WinApi.OpenSCManager(null, null, (uint)WinApi.SCM_ACCESS.SC_MANAGER_ALL_ACCESS);
        if (serviceManagerHandle == IntPtr.Zero)
        {
            throw new Win32Exception();
        }

        IntPtr serviceHandle = WinApi.OpenService(serviceManagerHandle, serviceName, (uint)WinApi.SERVICE_ACCESS.SERVICE_ALL_ACCESS);
        if (serviceHandle == IntPtr.Zero)
        {
            throw new Win32Exception();
        }

        WinApi.SERVICE_STATUS_PROCESS serviceStatus = new WinApi.SERVICE_STATUS_PROCESS();
        byte[] buffer = new byte[1000];
        int bytesNeeded;
        GCHandle bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            bool success = WinApi.QueryServiceStatusEx(serviceHandle, WinApi.SC_STATUS_PROCESS_INFO, buffer, 1000, out bytesNeeded);
            if (!success)
            {
                throw new Win32Exception();
            }
            IntPtr buffIntPtr = bufferHandle.AddrOfPinnedObject();
            Marshal.PtrToStructure(buffIntPtr, serviceStatus);
        }
        finally
        {
            bufferHandle.Free();
        }

        WinApi.CloseServiceHandle(serviceHandle);
        WinApi.CloseServiceHandle(serviceManagerHandle);

        return Process.GetCurrentProcess().Id == serviceStatus.processID;
    }
}

Windows API imports:

class WinApi
{
    [DllImport("advapi32.dll", EntryPoint = "OpenSCManagerW", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern IntPtr OpenSCManager(string machineName, string databaseName, uint dwAccess);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CloseServiceHandle(IntPtr hSCObject);

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, uint dwDesiredAccess);

    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool ControlService(IntPtr hService, SERVICE_CONTROL dwControl, ref SERVICE_STATUS lpServiceStatus);

    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern bool QueryServiceStatusEx(IntPtr serviceHandle, int infoLevel, byte[] buffer, int bufferSize, out int bytesNeeded);

    [Flags]
    public enum SCM_ACCESS : uint
    {
        /// <summary>
        /// Required to connect to the service control manager.
        /// </summary>
        SC_MANAGER_CONNECT = 0x00001,

        /// <summary>
        /// Required to call the CreateService function to create a service
        /// object and add it to the database.
        /// </summary>
        SC_MANAGER_CREATE_SERVICE = 0x00002,

        /// <summary>
        /// Required to call the EnumServicesStatusEx function to list the
        /// services that are in the database.
        /// </summary>
        SC_MANAGER_ENUMERATE_SERVICE = 0x00004,

        /// <summary>
        /// Required to call the LockServiceDatabase function to acquire a
        /// lock on the database.
        /// </summary>
        SC_MANAGER_LOCK = 0x00008,

        /// <summary>
        /// Required to call the QueryServiceLockStatus function to retrieve
        /// the lock status information for the database.
        /// </summary>
        SC_MANAGER_QUERY_LOCK_STATUS = 0x00010,

        /// <summary>
        /// Required to call the NotifyBootConfigStatus function.
        /// </summary>
        SC_MANAGER_MODIFY_BOOT_CONFIG = 0x00020,

        /// <summary>
        /// Includes STANDARD_RIGHTS_REQUIRED, in addition to all access
        /// rights in this table.
        /// </summary>
        SC_MANAGER_ALL_ACCESS = ACCESS_MASK.STANDARD_RIGHTS_REQUIRED |
            SC_MANAGER_CONNECT |
            SC_MANAGER_CREATE_SERVICE |
            SC_MANAGER_ENUMERATE_SERVICE |
            SC_MANAGER_LOCK |
            SC_MANAGER_QUERY_LOCK_STATUS |
            SC_MANAGER_MODIFY_BOOT_CONFIG,

        GENERIC_READ = ACCESS_MASK.STANDARD_RIGHTS_READ |
            SC_MANAGER_ENUMERATE_SERVICE |
            SC_MANAGER_QUERY_LOCK_STATUS,

        GENERIC_WRITE = ACCESS_MASK.STANDARD_RIGHTS_WRITE |
            SC_MANAGER_CREATE_SERVICE |
            SC_MANAGER_MODIFY_BOOT_CONFIG,

        GENERIC_EXECUTE = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE |
            SC_MANAGER_CONNECT | SC_MANAGER_LOCK,

        GENERIC_ALL = SC_MANAGER_ALL_ACCESS,
    }

    [Flags]
    enum ACCESS_MASK : uint
    {
        DELETE = 0x00010000,
        READ_CONTROL = 0x00020000,
        WRITE_DAC = 0x00040000,
        WRITE_OWNER = 0x00080000,
        SYNCHRONIZE = 0x00100000,

        STANDARD_RIGHTS_REQUIRED = 0x000f0000,

        STANDARD_RIGHTS_READ = 0x00020000,
        STANDARD_RIGHTS_WRITE = 0x00020000,
        STANDARD_RIGHTS_EXECUTE = 0x00020000,

        STANDARD_RIGHTS_ALL = 0x001f0000,

        SPECIFIC_RIGHTS_ALL = 0x0000ffff,

        ACCESS_SYSTEM_SECURITY = 0x01000000,

        MAXIMUM_ALLOWED = 0x02000000,

        GENERIC_READ = 0x80000000,
        GENERIC_WRITE = 0x40000000,
        GENERIC_EXECUTE = 0x20000000,
        GENERIC_ALL = 0x10000000,

        DESKTOP_READOBJECTS = 0x00000001,
        DESKTOP_CREATEWINDOW = 0x00000002,
        DESKTOP_CREATEMENU = 0x00000004,
        DESKTOP_HOOKCONTROL = 0x00000008,
        DESKTOP_JOURNALRECORD = 0x00000010,
        DESKTOP_JOURNALPLAYBACK = 0x00000020,
        DESKTOP_ENUMERATE = 0x00000040,
        DESKTOP_WRITEOBJECTS = 0x00000080,
        DESKTOP_SWITCHDESKTOP = 0x00000100,

        WINSTA_ENUMDESKTOPS = 0x00000001,
        WINSTA_READATTRIBUTES = 0x00000002,
        WINSTA_ACCESSCLIPBOARD = 0x00000004,
        WINSTA_CREATEDESKTOP = 0x00000008,
        WINSTA_WRITEATTRIBUTES = 0x00000010,
        WINSTA_ACCESSGLOBALATOMS = 0x00000020,
        WINSTA_EXITWINDOWS = 0x00000040,
        WINSTA_ENUMERATE = 0x00000100,
        WINSTA_READSCREEN = 0x00000200,

        WINSTA_ALL_ACCESS = 0x0000037f
    }

    [Flags]
    public enum SERVICE_ACCESS : uint
    {
        STANDARD_RIGHTS_REQUIRED = 0xF0000,
        SERVICE_QUERY_CONFIG = 0x00001,
        SERVICE_CHANGE_CONFIG = 0x00002,
        SERVICE_QUERY_STATUS = 0x00004,
        SERVICE_ENUMERATE_DEPENDENTS = 0x00008,
        SERVICE_START = 0x00010,
        SERVICE_STOP = 0x00020,
        SERVICE_PAUSE_CONTINUE = 0x00040,
        SERVICE_INTERROGATE = 0x00080,
        SERVICE_USER_DEFINED_CONTROL = 0x00100,
        SERVICE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED |
                          SERVICE_QUERY_CONFIG |
                          SERVICE_CHANGE_CONFIG |
                          SERVICE_QUERY_STATUS |
                          SERVICE_ENUMERATE_DEPENDENTS |
                          SERVICE_START |
                          SERVICE_STOP |
                          SERVICE_PAUSE_CONTINUE |
                          SERVICE_INTERROGATE |
                          SERVICE_USER_DEFINED_CONTROL)
    }

    [Flags]
    public enum SERVICE_CONTROL : uint
    {
        STOP = 0x00000001,
        PAUSE = 0x00000002,
        CONTINUE = 0x00000003,
        INTERROGATE = 0x00000004,
        SHUTDOWN = 0x00000005,
        PARAMCHANGE = 0x00000006,
        NETBINDADD = 0x00000007,
        NETBINDREMOVE = 0x00000008,
        NETBINDENABLE = 0x00000009,
        NETBINDDISABLE = 0x0000000A,
        DEVICEEVENT = 0x0000000B,
        HARDWAREPROFILECHANGE = 0x0000000C,
        POWEREVENT = 0x0000000D,
        SESSIONCHANGE = 0x0000000E
    }

    public enum SERVICE_STATE : uint
    {
        SERVICE_STOPPED = 0x00000001,
        SERVICE_START_PENDING = 0x00000002,
        SERVICE_STOP_PENDING = 0x00000003,
        SERVICE_RUNNING = 0x00000004,
        SERVICE_CONTINUE_PENDING = 0x00000005,
        SERVICE_PAUSE_PENDING = 0x00000006,
        SERVICE_PAUSED = 0x00000007
    }

    [Flags]
    public enum SERVICE_ACCEPT : uint
    {
        STOP = 0x00000001,
        PAUSE_CONTINUE = 0x00000002,
        SHUTDOWN = 0x00000004,
        PARAMCHANGE = 0x00000008,
        NETBINDCHANGE = 0x00000010,
        HARDWAREPROFILECHANGE = 0x00000020,
        POWEREVENT = 0x00000040,
        SESSIONCHANGE = 0x00000080,
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct SERVICE_STATUS
    {
        public static readonly int SizeOf = Marshal.SizeOf(typeof(SERVICE_STATUS));
        public SERVICE_TYPES dwServiceType;
        public SERVICE_STATE dwCurrentState;
        public uint dwControlsAccepted;
        public uint dwWin32ExitCode;
        public uint dwServiceSpecificExitCode;
        public uint dwCheckPoint;
        public uint dwWaitHint;
    }

    [Flags]
    public enum SERVICE_TYPES : int
    {
        SERVICE_KERNEL_DRIVER = 0x00000001,
        SERVICE_FILE_SYSTEM_DRIVER = 0x00000002,
        SERVICE_WIN32_OWN_PROCESS = 0x00000010,
        SERVICE_WIN32_SHARE_PROCESS = 0x00000020,
        SERVICE_INTERACTIVE_PROCESS = 0x00000100
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public class SERVICE_STATUS_PROCESS
    {
        public int serviceType;
        public int currentState;
        public int controlsAccepted;
        public int win32ExitCode;
        public int serviceSpecificExitCode;
        public int checkPoint;
        public int waitHint;
        public int processID;
        public int serviceFlags;
    }

    public const int SC_STATUS_PROCESS_INFO = 0;
}

A C++ version of the same function:

#include <windows.h>
#include <tchar.h>
#include <strsafe.h>
#include <aclapi.h>
#include <stdio.h>

bool IsRunningAsService(const TCHAR* szSvcName)
{
    SERVICE_STATUS_PROCESS ssStatus; 
    DWORD dwBytesNeeded;

    SC_HANDLE schSCManager = OpenSCManager( 
        NULL,                    // local computer
        NULL,                    // servicesActive database 
        SC_MANAGER_ALL_ACCESS);  // full access rights 

    if (NULL == schSCManager) 
    {
        printf("OpenSCManager failed (%d)\n", GetLastError());
        return false;
    }

    // Get a handle to the service.
    SC_HANDLE schService = OpenService( 
        schSCManager,         // SCM database 
        szSvcName,            // name of service 
        SERVICE_ALL_ACCESS);  // full access 

    if (schService == NULL)
    { 
        printf("OpenService failed (%d)\n", GetLastError()); 
        CloseServiceHandle(schSCManager);
        return false;
    }    

    // Check the status in case the service is not stopped. 

    if (!QueryServiceStatusEx( 
            schService,                     // handle to service 
            SC_STATUS_PROCESS_INFO,         // information level
            (LPBYTE) &ssStatus,             // address of structure
            sizeof(SERVICE_STATUS_PROCESS), // size of structure
            &dwBytesNeeded ) )              // size needed if buffer is too small
    {
        printf("QueryServiceStatusEx failed (%d)\n", GetLastError());
        CloseServiceHandle(schService); 
        CloseServiceHandle(schSCManager);
        return false; 
    }

    return GetCurrentProcessId() == ssStatus.dwProcessId;
}
like image 183
Dirk Vollmar Avatar answered Oct 15 '22 17:10

Dirk Vollmar


Here's some code I created (seems to work nicely). Apologies for missing headers, #defines, etc. If you want to see the full version, look here.

bool
CArchMiscWindows::wasLaunchedAsService() 
{
    CString name;
    if (!getParentProcessName(name)) {
        LOG((CLOG_ERR "cannot determine if process was launched as service"));
        return false;
    }

    return (name == SERVICE_LAUNCHER);
}

bool
CArchMiscWindows::getParentProcessName(CString &name) 
{   
    PROCESSENTRY32 parentEntry;
    if (!getParentProcessEntry(parentEntry)){
        LOG((CLOG_ERR "could not get entry for parent process"));
        return false;
    }

    name = parentEntry.szExeFile;
    return true;
}

BOOL WINAPI 
CArchMiscWindows::getSelfProcessEntry(PROCESSENTRY32& entry)
{
    // get entry from current PID
    return getProcessEntry(entry, GetCurrentProcessId());
}

BOOL WINAPI 
CArchMiscWindows::getParentProcessEntry(PROCESSENTRY32& entry)
{
    // get the current process, so we can get parent PID
    PROCESSENTRY32 selfEntry;
    if (!getSelfProcessEntry(selfEntry)) {
        return FALSE;
    }

    // get entry from parent PID
    return getProcessEntry(entry, selfEntry.th32ParentProcessID);
}

BOOL WINAPI 
CArchMiscWindows::getProcessEntry(PROCESSENTRY32& entry, DWORD processID)
{
    // first we need to take a snapshot of the running processes
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE) {
        LOG((CLOG_ERR "could not get process snapshot (error: %i)", 
            GetLastError()));
        return FALSE;
    }

    entry.dwSize = sizeof(PROCESSENTRY32);

    // get the first process, and if we can't do that then it's 
    // unlikely we can go any further
    BOOL gotEntry = Process32First(snapshot, &entry);
    if (!gotEntry) {
        LOG((CLOG_ERR "could not get first process entry (error: %i)", 
            GetLastError()));
        return FALSE;
    }

    while(gotEntry) {

        if (entry.th32ProcessID == processID) {
            // found current process
            return TRUE;
        }

        // now move on to the next entry (when we reach end, loop will stop)
        gotEntry = Process32Next(snapshot, &entry);
    }

return FALSE;

}

like image 41
Nick Bolton Avatar answered Oct 15 '22 16:10

Nick Bolton