diff --git a/PInvoke/Security/AdvApi32/WinSvc.cs b/PInvoke/Security/AdvApi32/WinSvc.cs index 10ebd3fb..13934e22 100644 --- a/PInvoke/Security/AdvApi32/WinSvc.cs +++ b/PInvoke/Security/AdvApi32/WinSvc.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Text; using Vanara.Extensions; using Vanara.InteropServices; @@ -77,7 +78,76 @@ namespace Vanara.PInvoke // void LphandlerFunction( DWORD dwControl ) {...} [PInvokeData("winsvc.h", MSDNShortId = "e2d6d3a7-070e-4343-abd7-b4b9f8dd6fbc")] [UnmanagedFunctionPointer(CallingConvention.Winapi)] - public delegate void LphandlerFunction(uint dwControl); + public delegate void Handler(ServiceControl dwControl); + + /// Callback function used in to alert changes registered by . + /// A pointer to the SERVICE_NOTIFY structure provided by the caller. + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + [PInvokeData("winsvc.h", MSDNShortId = "52ede72e-eb50-48e2-b5c1-125816f6fe57")] + public delegate void PFN_SC_NOTIFY_CALLBACK(ref SERVICE_NOTIFY_2 pParameter); + + /// + /// The entry point for a service. + /// + /// The LPSERVICE_MAIN_FUNCTION type defines a pointer to this callback function. ServiceMain is a placeholder for an + /// application-defined function name. + /// + /// + /// Number of arguments in the array. + /// + /// Array of strings. The first string is the name of the service and subsequent strings are passed by the process that called the + /// StartService function to start the service. + /// + /// This function does not return a value. + /// + /// + /// A service program can start one or more services. A service process has a SERVICE_TABLE_ENTRY structure for each service that it + /// can start. The structure specifies the service name and a pointer to the ServiceMain function for that service. + /// + /// + /// When the service control manager receives a request to start a service, it starts the service process (if it is not already + /// running). The main thread of the service process calls the StartServiceCtrlDispatcher function with a pointer to an array of + /// SERVICE_TABLE_ENTRY structures. Then the service control manager sends a start request to the service control dispatcher for this + /// service process. The service control dispatcher creates a new thread to execute the ServiceMain function of the service + /// being started. + /// + /// + /// The ServiceMain function should immediately call the RegisterServiceCtrlHandlerEx function to specify a HandlerEx function + /// to handle control requests. Next, it should call the SetServiceStatus function to send status information to the service control + /// manager. After these calls, the function should complete the initialization of the service. Do not attempt to start another + /// service in the ServiceMain function. + /// + /// + /// The Service Control Manager (SCM) waits until the service reports a status of SERVICE_RUNNING. It is recommended that the service + /// reports this status as quickly as possible, as other components in the system that require interaction with SCM will be blocked + /// during this time. Some functions may require interaction with the SCM either directly or indirectly. + /// + /// + /// The SCM locks the service control database during initialization, so if a service attempts to call StartService during + /// initialization, the call will block. When the service reports to the SCM that it has successfully started, it can call + /// StartService. If the service requires another service to be running, the service should set the required dependencies. + /// + /// + /// Furthermore, you should not call any system functions during service initialization. The service code should call system + /// functions only after it reports a status of SERVICE_RUNNING. + /// + /// + /// The ServiceMain function should create a global event, call the RegisterWaitForSingleObject function on this event, and + /// exit. This will terminate the thread that is running the ServiceMain function, but will not terminate the service. When + /// the service is stopping, the service control handler should call SetServiceStatus with SERVICE_STOP_PENDING and signal this + /// event. A thread from the thread pool will execute the wait callback function; this function should perform clean-up tasks, + /// including closing the global event, and call SetServiceStatus with SERVICE_STOPPED. After the service has stopped, you + /// should not execute any additional service code because you can introduce a race condition if the service receives a start control + /// and ServiceMain is called again. Note that this problem is more likely to occur when multiple services share a process. + /// + /// Examples + /// For an example, see Writing a ServiceMain Function. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nc-winsvc-lpservice_main_functiona LPSERVICE_MAIN_FUNCTIONA + // LpserviceMainFunctiona; void LpserviceMainFunctiona( DWORD dwNumServicesArgs, LPSTR *lpServiceArgVectors ) {...} + [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "d7f3235e-91bd-4107-a30c-4a8f9a6c731e")] + public delegate void ServiceMain(uint dwNumServicesArgs, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPTStr, SizeParamIndex = 0)] string[] lpServiceArgVectors); /// Defines the action to be performed in a . [PInvokeData("winsvc.h", MSDNShortId = "e2c355a6-affe-46bf-a3e6-f8c420422d46")] @@ -99,6 +169,16 @@ namespace Vanara.PInvoke SC_ACTION_OWN_RESTART = 4 } + /// The service attributes that are to be returned from . + public enum SC_ENUM_TYPE + { + /// + /// Retrieve the name and service status information for each service in the database. The lpServices parameter is a pointer to a + /// buffer that receives an array of ENUM_SERVICE_STATUS_PROCESS structures. + /// + SC_ENUM_PROCESS_INFO = 0 + } + /// Info levels for public enum SC_STATUS_TYPE { @@ -146,6 +226,132 @@ namespace Vanara.PInvoke SC_MANAGER_MODIFY_BOOT_CONFIG } + [Flags] + public enum ServiceAccessRights : uint + { + /// Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights in this table. + SERVICE_ALL_ACCESS = 0xF01FF, + /// Required to call the ChangeServiceConfig or ChangeServiceConfig2 function to change the service configuration. Because this grants the caller the right to change the executable file that the system runs, it should be granted only to administrators. + SERVICE_CHANGE_CONFIG = 0x0002, + /// Required to call the EnumDependentServices function to enumerate all the services dependent on the service. + SERVICE_ENUMERATE_DEPENDENTS = 0x0008, + /// Required to call the ControlService function to ask the service to report its status immediately. + SERVICE_INTERROGATE = 0x0080, + /// Required to call the ControlService function to pause or continue the service. + SERVICE_PAUSE_CONTINUE = 0x0040, + /// Required to call the QueryServiceConfig and QueryServiceConfig2 functions to query the service configuration. + SERVICE_QUERY_CONFIG = 0x0001, + /// Required to call the QueryServiceStatus or QueryServiceStatusEx function to ask the service control manager about the status of the service. + /// Required to call the NotifyServiceStatusChange function to receive notification when a service changes status. + SERVICE_QUERY_STATUS = 0x0004, + /// Required to call the StartService function to start the service. + SERVICE_START = 0x0010, + /// Required to call the ControlService function to stop the service. + SERVICE_STOP = 0x0020, + /// Required to call the ControlService function to specify a user-defined control code. + SERVICE_USER_DEFINED_CONTROL = 0x0100, + + /// Required to call the QueryServiceObjectSecurity or SetServiceObjectSecurity function to access the SACL. The proper way to obtain this access is to enable the SE_SECURITY_NAMEprivilege in the caller's current access token, open the handle for ACCESS_SYSTEM_SECURITY access, and then disable the privilege. + ACCESS_SYSTEM_SECURITY = 0x01000000, + /// Required to call the DeleteService function to delete the service. + DELETE = ACCESS_MASK.DELETE, + /// Required to call the QueryServiceObjectSecurity function to query the security descriptor of the service object. + READ_CONTROL = ACCESS_MASK.READ_CONTROL, + /// Required to call the SetServiceObjectSecurity function to modify the Dacl member of the service object's security descriptor. + WRITE_DAC = ACCESS_MASK.WRITE_DAC, + /// Required to call the SetServiceObjectSecurity function to modify the Owner and Group members of the service object's security descriptor. + WRITE_OWNER = ACCESS_MASK.WRITE_OWNER, + + GENERIC_READ = ACCESS_MASK.STANDARD_RIGHTS_READ | SERVICE_QUERY_CONFIG | SERVICE_QUERY_STATUS | SERVICE_INTERROGATE | SERVICE_ENUMERATE_DEPENDENTS, + GENERIC_WRITE = ACCESS_MASK.STANDARD_RIGHTS_WRITE | SERVICE_CHANGE_CONFIG, + GENERIC_EXECUTE = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_USER_DEFINED_CONTROL, + } + + /// The type of status changes that should be reported. + [PInvokeData("winsvc.h", MSDNShortId = "e22b7f69-f096-486f-97fa-0465bef499cd")] + [Flags] + public enum SERVICE_NOTIFY_FLAGS + { + /// + /// Report when the service has been created. + /// The hService parameter must be a handle to the SCM. + /// + SERVICE_NOTIFY_CREATED = 0x00000080, + + /// + /// Report when the service is about to continue. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_CONTINUE_PENDING = 0x00000010, + + /// + /// Report when an application has specified the service in a call to the DeleteService function. Your application should close + /// any handles to the service so it can be deleted. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_DELETE_PENDING = 0x00000200, + + /// + /// Report when the service has been deleted. An application cannot receive this notification if it has an open handle to the service. + /// The hService parameter must be a handle to the SCM. + /// + SERVICE_NOTIFY_DELETED = 0x00000100, + + /// + /// Report when the service is pausing. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_PAUSE_PENDING = 0x00000020, + + /// + /// Report when the service has paused. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_PAUSED = 0x00000040, + + /// + /// Report when the service is running. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_RUNNING = 0x00000008, + + /// + /// Report when the service is starting. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_START_PENDING = 0x00000002, + + /// + /// Report when the service is stopping. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_STOP_PENDING = 0x00000004, + + /// + /// Report when the service has stopped. + /// The hService parameter must be a handle to the service. + /// + SERVICE_NOTIFY_STOPPED = 0x00000001, + } + + /// The state of the services to be enumerated. + [PInvokeData("winsvc.h", MSDNShortId = "905d4453-96d4-4055-8a17-36714c547cdd")] + [Flags] + public enum SERVICE_STATE + { + /// + /// Enumerates services that are in the following states: SERVICE_START_PENDING, SERVICE_STOP_PENDING, SERVICE_RUNNING, + /// SERVICE_CONTINUE_PENDING, SERVICE_PAUSE_PENDING, and SERVICE_PAUSED. + /// + SERVICE_ACTIVE = 0x00000001, + + /// Enumerates services that are in the SERVICE_STOPPED state. + SERVICE_INACTIVE = 0x00000002, + + /// Combines the following states: SERVICE_ACTIVE and SERVICE_INACTIVE. + SERVICE_STATE_ALL = 0x00000003, + } + [Flags] public enum SERVICE_STOP_REASON : uint { @@ -1828,6 +2034,623 @@ namespace Vanara.PInvoke [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DeleteService(SC_HANDLE hService); + /// + /// Retrieves the name and status of each service that depends on the specified service; that is, the specified service must be + /// running before the dependent services can run. + /// + /// + /// A handle to the service. This handle is returned by the OpenService or CreateService function, and it must have the + /// SERVICE_ENUMERATE_DEPENDENTS access right. For more information, see Service Security and Access Rights. + /// + /// The state of the services to be enumerated. This parameter can be one of the following values. + /// + /// + /// A pointer to an array of ENUM_SERVICE_STATUS structures that receives the name and service status information for each dependent + /// service in the database. The buffer must be large enough to hold the structures, plus the strings to which their members point. + /// + /// + /// The order of the services in this array is the reverse of the start order of the services. In other words, the first service in + /// the array is the one that would be started last, and the last service in the array is the one that would be started first. + /// + /// + /// The maximum size of this array is 64,000 bytes. To determine the required size, specify NULL for this parameter and 0 for + /// the cbBufSize parameter. The function will fail and GetLastError will return ERROR_MORE_DATA. The pcbBytesNeeded parameter + /// will receive the required size. + /// + /// + /// The size of the buffer pointed to by the lpServices parameter, in bytes. + /// + /// A pointer to a variable that receives the number of bytes needed to store the array of service entries. The variable only + /// receives this value if the buffer pointed to by lpServices is too small, indicated by function failure and the + /// ERROR_MORE_DATA error; otherwise, the contents of pcbBytesNeeded are undefined. + /// + /// A pointer to a variable that receives the number of service entries returned. + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// The following error codes may be set by the service control manager. Other error codes may be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The handle does not have the SERVICE_ENUMERATE_DEPENDENTS access right. + /// + /// + /// ERROR_INVALID_HANDLE + /// The specified handle is invalid. + /// + /// + /// ERROR_INVALID_PARAMETER + /// A parameter that was specified is invalid. + /// + /// + /// ERROR_MORE_DATA + /// + /// The buffer pointed to by lpServices is not large enough. The function sets the variable pointed to by lpServicesReturned to the + /// actual number of service entries stored into the buffer. The function sets the variable pointed to by pcbBytesNeeded to the + /// number of bytes required to store all of the service entries. + /// + /// + /// + /// + /// + /// + /// The returned services entries are ordered in the reverse order of the start order, with group order taken into account. If you + /// need to stop the dependent services, you can use the order of entries written to the lpServices buffer to stop the dependent + /// services in the proper order. + /// + /// Examples + /// For an example, see Stopping a Service. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumdependentservicesa BOOL EnumDependentServicesA( + // SC_HANDLE hService, DWORD dwServiceState, LPENUM_SERVICE_STATUSA lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, LPDWORD + // lpServicesReturned ); + [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "905d4453-96d4-4055-8a17-36714c547cdd")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumDependentServices(SC_HANDLE hService, SERVICE_STATE dwServiceState, IntPtr lpServices, uint cbBufSize, out uint pcbBytesNeeded, out uint lpServicesReturned); + + /// + /// Retrieves the name and status of each service that depends on the specified service; that is, the specified service must be + /// running before the dependent services can run. + /// + /// + /// A handle to the service. This handle is returned by the OpenService or CreateService function, and it must have the + /// SERVICE_ENUMERATE_DEPENDENTS access right. For more information, see Service Security and Access Rights. + /// + /// The state of the services to be enumerated. This parameter can be one of the following values. + /// + /// + /// An array of ENUM_SERVICE_STATUS structures that receives the name and service status information for each dependent service in + /// the database. + /// + /// + /// The order of the services in this array is the reverse of the start order of the services. In other words, the first service in + /// the array is the one that would be started last, and the last service in the array is the one that would be started first. + /// + /// + [PInvokeData("winsvc.h", MSDNShortId = "905d4453-96d4-4055-8a17-36714c547cdd")] + public static IEnumerable EnumDependentServices(SC_HANDLE hService, SERVICE_STATE dwServiceState = SERVICE_STATE.SERVICE_STATE_ALL) + { + EnumDependentServices(hService, dwServiceState, IntPtr.Zero, 0, out var sz, out var cnt); + if (sz == 0) Win32Error.ThrowLastError(); + var mem = new SafeHGlobalHandle((int)sz); + if (!EnumDependentServices(hService, dwServiceState, (IntPtr)mem, (uint)mem.Size, out sz, out cnt)) + Win32Error.ThrowLastError(); + return mem.ToEnumerable((int)cnt); + } + + /// + /// Enumerates services in the specified service control manager database. The name and status of each service are provided. + /// + /// This function has been superseded by the EnumServicesStatusEx function. It returns the same information EnumServicesStatus + /// returns, plus the process identifier and additional information for the service. In addition, EnumServicesStatusEx enables + /// you to enumerate services that belong to a specified group. + /// + /// + /// + /// A handle to the service control manager database. This handle is returned by the OpenSCManager function, and must have the + /// SC_MANAGER_ENUMERATE_SERVICE access right. For more information, see Service Security and Access Rights. + /// + /// The type of services to be enumerated. This parameter can be one or more of the following values. + /// The state of the services to be enumerated. This parameter can be one of the following values. + /// + /// + /// A pointer to a buffer that contains an array of ENUM_SERVICE_STATUS structures that receive the name and service status + /// information for each service in the database. The buffer must be large enough to hold the structures, plus the strings to which + /// their members point. + /// + /// + /// The maximum size of this array is 256K bytes. To determine the required size, specify NULL for this parameter and 0 for the + /// cbBufSize parameter. The function will fail and GetLastError will return ERROR_INSUFFICIENT_BUFFER. The pcbBytesNeeded parameter + /// will receive the required size. + /// + /// + /// Windows Server 2003 and Windows XP: The maximum size of this array is 64K bytes. This limit was increased as of Windows + /// Server 2003 with SP1 and Windows XP with SP2. + /// + /// + /// The size of the buffer pointed to by the lpServices parameter, in bytes. + /// + /// A pointer to a variable that receives the number of bytes needed to return the remaining service entries, if the buffer is too small. + /// + /// A pointer to a variable that receives the number of service entries returned. + /// + /// A pointer to a variable that, on input, specifies the starting point of enumeration. You must set this value to zero the first + /// time this function is called. On output, this value is zero if the function succeeds. However, if the function returns zero and + /// the GetLastError function returns ERROR_MORE_DATA, this value is used to indicate the next service entry to be read when the + /// function is called to retrieve the additional data. + /// + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// The following error codes can be set by the service control manager. Other error codes can be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The handle does not have the SC_MANAGER_ENUMERATE_SERVICE access right. + /// + /// + /// ERROR_INVALID_HANDLE + /// The specified handle is invalid. + /// + /// + /// ERROR_INVALID_PARAMETER + /// A parameter that was specified is invalid. + /// + /// + /// ERROR_MORE_DATA + /// + /// There are more service entries than would fit into the lpServices buffer. The actual number of service entries written to + /// lpServices is returned in the lpServicesReturned parameter. The number of bytes required to get the remaining entries is returned + /// in the pcbBytesNeeded parameter. The remaining services can be enumerated by additional calls to EnumServicesStatus with the + /// lpResumeHandle parameter indicating the next service to read. + /// + /// + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumservicesstatusa BOOL EnumServicesStatusA( SC_HANDLE + // hSCManager, DWORD dwServiceType, DWORD dwServiceState, LPENUM_SERVICE_STATUSA lpServices, DWORD cbBufSize, LPDWORD pcbBytesNeeded, + // LPDWORD lpServicesReturned, LPDWORD lpResumeHandle ); + [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "3a82ac0e-f3e8-4a5a-9b13-84e952712229")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumServicesStatus(SC_HANDLE hSCManager, ServiceTypes dwServiceType, SERVICE_STATE dwServiceState, IntPtr lpServices, uint cbBufSize, out uint pcbBytesNeeded, out uint lpServicesReturned, ref uint lpResumeHandle); + + /// + /// Enumerates services in the specified service control manager database. The name and status of each service are provided. + /// + /// This function has been superseded by the EnumServicesStatusEx function. It returns the same information EnumServicesStatus + /// returns, plus the process identifier and additional information for the service. In addition, EnumServicesStatusEx enables + /// you to enumerate services that belong to a specified group. + /// + /// + /// + /// A handle to the service control manager database. This handle is returned by the OpenSCManager function, and must have the + /// SC_MANAGER_ENUMERATE_SERVICE access right. For more information, see Service Security and Access Rights. + /// + /// The type of services to be enumerated. This parameter can be one or more of the following values. + /// The state of the services to be enumerated. This parameter can be one of the following values. + /// A list of ENUM_SERVICE_STATUS structures with the name and service status information for each service in the database. + public static IEnumerable EnumServicesStatus(SC_HANDLE hSCManager, ServiceTypes dwServiceType = ServiceTypes.SERVICE_TYPE_ALL, SERVICE_STATE dwServiceState = SERVICE_STATE.SERVICE_STATE_ALL) + { + var hRes = 0U; + Win32Error lastErr; + var res = EnumServicesStatus(hSCManager, dwServiceType, dwServiceState, default, 0, out var sz, out _, ref hRes); + if (!res && (lastErr = Win32Error.GetLastError()) != Win32Error.ERROR_MORE_DATA) + lastErr.ThrowIfFailed(); + using (var mem = new SafeHGlobalHandle((int)sz)) + { + do + { + res = EnumServicesStatus(hSCManager, dwServiceType, dwServiceState, (IntPtr)mem, sz, out sz, out var cnt, ref hRes); + if (!res && (lastErr = Win32Error.GetLastError()) != Win32Error.ERROR_MORE_DATA) + lastErr.ThrowIfFailed(); + foreach (var i in mem.ToEnumerable((int)cnt)) + yield return i; + } while (!res); + } + } + + /// + /// Enumerates services in the specified service control manager database. The name and status of each service are provided, along + /// with additional data based on the specified information level. + /// + /// + /// A handle to the service control manager database. This handle is returned by the OpenSCManager function, and must have the + /// SC_MANAGER_ENUMERATE_SERVICE access right. For more information, see Service Security and Access Rights. + /// + /// + /// + /// The service attributes that are to be returned. Use SC_ENUM_PROCESS_INFO to retrieve the name and service status + /// information for each service in the database. The lpServices parameter is a pointer to a buffer that receives an array of + /// ENUM_SERVICE_STATUS_PROCESS structures. The buffer must be large enough to hold the structures as well as the strings to which + /// their members point. + /// + /// Currently, no other information levels are defined. + /// + /// The type of services to be enumerated. This parameter can be one or more of the following values. + /// The state of the services to be enumerated. This parameter can be one of the following values. + /// + /// + /// A pointer to the buffer that receives the status information. The format of this data depends on the value of the InfoLevel parameter. + /// + /// + /// The maximum size of this array is 256K bytes. To determine the required size, specify NULL for this parameter and 0 for + /// the cbBufSize parameter. The function will fail and GetLastError will return ERROR_MORE_DATA. The pcbBytesNeeded parameter + /// will receive the required size. + /// + /// + /// Windows Server 2003 and Windows XP: The maximum size of this array is 64K bytes. This limit was increased as of Windows + /// Server 2003 with SP1 and Windows XP with SP2. + /// + /// + /// The size of the buffer pointed to by the lpServices parameter, in bytes. + /// + /// A pointer to a variable that receives the number of bytes needed to return the remaining service entries, if the buffer is too small. + /// + /// A pointer to a variable that receives the number of service entries returned. + /// + /// A pointer to a variable that, on input, specifies the starting point of enumeration. You must set this value to zero the first + /// time the EnumServicesStatusEx function is called. On output, this value is zero if the function succeeds. However, if the + /// function returns zero and the GetLastError function returns ERROR_MORE_DATA, this value indicates the next service entry + /// to be read when the EnumServicesStatusEx function is called to retrieve the additional data. + /// + /// + /// The load-order group name. If this parameter is a string, the only services enumerated are those that belong to the group that + /// has the name specified by the string. If this parameter is an empty string, only services that do not belong to any group are + /// enumerated. If this parameter is NULL, group membership is ignored and all services are enumerated. + /// + /// + /// If the function succeeds, the return value is nonzero. + /// + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. The following errors may + /// be returned. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The handle does not have the SC_MANAGER_ENUMERATE_SERVICE access right. + /// + /// + /// ERROR_MORE_DATA + /// + /// The buffer is too small. Not all data in the active database could be returned. The pcbBytesNeeded parameter contains the number + /// of bytes required to receive the remaining entries. + /// + /// + /// + /// ERROR_INVALID_PARAMETER + /// An illegal parameter value was used. + /// + /// + /// ERROR_INVALID_HANDLE + /// The handle is invalid. + /// + /// + /// ERROR_INVALID_LEVEL + /// The InfoLevel parameter contains an unsupported value. + /// + /// + /// ERROR_SHUTDOWN_IN_PROGRESS + /// The system is shutting down; this function cannot be called. + /// + /// + /// + /// + /// If the caller does not have the SERVICE_QUERY_STATUS access right to a service, the service is silently omitted from the + /// list of services returned to the client. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-enumservicesstatusexa BOOL EnumServicesStatusExA( SC_HANDLE + // hSCManager, SC_ENUM_TYPE InfoLevel, DWORD dwServiceType, DWORD dwServiceState, LPBYTE lpServices, DWORD cbBufSize, LPDWORD + // pcbBytesNeeded, LPDWORD lpServicesReturned, LPDWORD lpResumeHandle, LPCSTR pszGroupName ); + [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "7d7940c3-b562-455f-9a21-6d5fb5953030")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool EnumServicesStatusEx(SC_HANDLE hSCManager, SC_ENUM_TYPE InfoLevel, ServiceTypes dwServiceType, SERVICE_STATE dwServiceState, IntPtr lpServices, uint cbBufSize, out uint pcbBytesNeeded, + out uint lpServicesReturned, ref uint lpResumeHandle, string pszGroupName); + + /// + /// Enumerates services in the specified service control manager database. The name and status of each service are provided. + /// + /// This function has been superseded by the EnumServicesStatusEx function. It returns the same information EnumServicesStatus + /// returns, plus the process identifier and additional information for the service. In addition, EnumServicesStatusEx enables + /// you to enumerate services that belong to a specified group. + /// + /// + /// + /// A handle to the service control manager database. This handle is returned by the OpenSCManager function, and must have the + /// SC_MANAGER_ENUMERATE_SERVICE access right. For more information, see Service Security and Access Rights. + /// + /// The type of services to be enumerated. This parameter can be one or more of the following values. + /// The state of the services to be enumerated. This parameter can be one of the following values. + /// + /// The load-order group name. If this parameter is a string, the only services enumerated are those that belong to the group that + /// has the name specified by the string. If this parameter is an empty string, only services that do not belong to any group are + /// enumerated. If this parameter is NULL, group membership is ignored and all services are enumerated. + /// + /// A list of ENUM_SERVICE_STATUS structures with the name and service status information for each service in the database. + public static IEnumerable EnumServicesStatusEx(SC_HANDLE hSCManager, ServiceTypes dwServiceType = ServiceTypes.SERVICE_TYPE_ALL, SERVICE_STATE dwServiceState = SERVICE_STATE.SERVICE_STATE_ALL, string pszGroupName = null) + { + var hRes = 0U; + Win32Error lastErr; + var res = EnumServicesStatusEx(hSCManager, 0, dwServiceType, dwServiceState, default, 0, out var sz, out _, ref hRes, pszGroupName); + if (!res && (lastErr = Win32Error.GetLastError()) != Win32Error.ERROR_MORE_DATA) + lastErr.ThrowIfFailed(); + using (var mem = new SafeHGlobalHandle((int)sz)) + { + do + { + res = EnumServicesStatusEx(hSCManager, 0, dwServiceType, dwServiceState, (IntPtr)mem, sz, out sz, out var cnt, ref hRes, pszGroupName); + if (!res && (lastErr = Win32Error.GetLastError()) != Win32Error.ERROR_MORE_DATA) + lastErr.ThrowIfFailed(); + foreach (var i in mem.ToEnumerable((int)cnt)) + yield return i; + } while (!res); + } + } + + /// Retrieves the display name of the specified service. + /// A handle to the service control manager database, as returned by the OpenSCManager function. + /// + /// The service name. This name is the same as the service's registry key name. It is best to choose a name that is less than 256 characters. + /// + /// + /// + /// A pointer to a buffer that receives the service's display name. If the function fails, this buffer will contain an empty string. + /// + /// + /// The maximum size of this array is 4K bytes. To determine the required size, specify NULL for this parameter and 0 for the + /// lpcchBuffer parameter. The function will fail and GetLastError will return ERROR_INSUFFICIENT_BUFFER. The lpcchBuffer + /// parameter will receive the required size. + /// + /// This parameter can specify a localized string using the following format: + /// @[path]dllname,-strID + /// The string with identifier strID is loaded from dllname; the path is optional. For more information, see RegLoadMUIString. + /// Windows Server 2003 and Windows XP: Localized strings are not supported until Windows Vista. + /// + /// + /// A pointer to a variable that specifies the size of the buffer pointed to by lpDisplayName, in TCHARs. + /// + /// On output, this variable receives the size of the service's display name, in characters, excluding the null-terminating character. + /// + /// + /// If the buffer pointed to by lpDisplayName is too small to contain the display name, the function does not store it. When the + /// function returns, lpcchBuffer contains the size of the service's display name, excluding the null-terminating character. + /// + /// + /// + /// If the functions succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// + /// There are two names for a service: the service name and the display name. The service name is the name of the service's key in + /// the registry. The display name is a user-friendly name that appears in the Services control panel application, and is used with + /// the NET START command. To map the service name to the display name, use the GetServiceDisplayName function. To map + /// the display name to the service name, use the GetServiceKeyName function. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-getservicedisplaynamea BOOL GetServiceDisplayNameA( + // SC_HANDLE hSCManager, LPCSTR lpServiceName, LPSTR lpDisplayName, LPDWORD lpcchBuffer ); + [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "704812f3-134c-4161-b3b4-a955d87ff563")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetServiceDisplayName(SC_HANDLE hSCManager, string lpServiceName, StringBuilder lpDisplayName, ref uint lpcchBuffer); + + /// Retrieves the service name of the specified service. + /// A handle to the computer's service control manager database, as returned by OpenSCManager. + /// The service display name. This string has a maximum length of 256 characters. + /// + /// A pointer to a buffer that receives the service name. If the function fails, this buffer will contain an empty string. + /// + /// The maximum size of this array is 4K bytes. To determine the required size, specify NULL for this parameter and 0 for the + /// lpcchBuffer parameter. The function will fail and GetLastError will return ERROR_INSUFFICIENT_BUFFER. The lpcchBuffer + /// parameter will receive the required size. + /// + /// + /// + /// + /// A pointer to variable that specifies the size of the buffer pointed to by the lpServiceName parameter, in TCHARs. When the + /// function returns, this parameter contains the size of the service name, in TCHARs, excluding the null-terminating character. + /// + /// + /// If the buffer pointed to by lpServiceName is too small to contain the service name, the function stores no data in it. When the + /// function returns, lpcchBuffer contains the size of the service name, excluding the NULL terminator. + /// + /// + /// + /// If the functions succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// + /// + /// There are two names for a service: the service name and the display name. The service name is the name of the service's key in + /// the registry. The display name is a user-friendly name that appears in the Services control panel application, and is used with + /// the NET START command. Both names are specified with the CreateService function and can be modified with the + /// ChangeServiceConfig function. Information specified for a service is stored in a key with the same name as the service name under + /// the HKEY_LOCAL_MACHINE<b>System<b>CurrentControlSet<b>Services<i>ServiceName registry key. + /// + /// + /// To map the service name to the display name, use the GetServiceDisplayName function. To map the display name to the service name, + /// use the GetServiceKeyName function. + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-getservicekeynamea BOOL GetServiceKeyNameA( SC_HANDLE + // hSCManager, LPCSTR lpDisplayName, LPSTR lpServiceName, LPDWORD lpcchBuffer ); + [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "d2421566-de4a-49e5-bb41-ea98c6f6d19d")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetServiceKeyName(SC_HANDLE hSCManager, string lpDisplayName, StringBuilder lpServiceName, ref uint lpcchBuffer); + + /// + /// [As of Windows Vista, this function is provided for application compatibility and has no effect on the database.] + /// + /// Requests ownership of the service control manager (SCM) database lock. Only one process can own the lock at any specified time. + /// + /// + /// + /// A handle to the SCM database. This handle is returned by the OpenSCManager function, and must have the SC_MANAGER_LOCK + /// access right. For more information, see Service Security and Access Rights. + /// + /// + /// If the function succeeds, the return value is a lock to the specified SCM database. + /// If the function fails, the return value is NULL. To get extended error information, call GetLastError. + /// + /// The following error codes can be set by the SCM. Other error codes can be set by registry functions that are called by the SCM. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The handle does not have the SC_MANAGER_LOCK access right. + /// + /// + /// ERROR_INVALID_HANDLE + /// The specified handle is not valid. + /// + /// + /// ERROR_SERVICE_DATABASE_LOCKED + /// The database is locked. + /// + /// + /// + /// + /// + /// A lock is a protocol used by setup and configuration programs and the SCM to serialize access to the service tree in the + /// registry. The only time the SCM requests ownership of the lock is when it is starting a service. + /// + /// + /// A program that acquires the SCM database lock and fails to release it prevents the SCM from starting other services. Because of + /// the severity of this issue, processes are no longer allowed to lock the database. For compatibility with older applications, the + /// LockServiceDatabase function returns a lock but has no other effect. + /// + /// + /// Windows Server 2003 and Windows XP: Acquiring the SCM database lock prevents the SCM from starting a service until the + /// lock is released. For example, a program that must configure several related services before any of them starts could call + /// LockServiceDatabase before configuring the first service. Alternatively, it could ensure that none of the services are + /// started until the configuration has been completed. + /// + /// + /// A call to the StartService function to start a service in a locked database fails. No other SCM functions are affected by a lock. + /// + /// + /// The lock is held until the SC_LOCK handle is specified in a subsequent call to the UnlockServiceDatabase function. If a + /// process that owns a lock terminates, the SCM automatically cleans up and releases ownership of the lock. + /// + /// Failing to release the lock can cause system problems. A process that acquires the lock should release it as soon as possible. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-lockservicedatabase SC_LOCK LockServiceDatabase( SC_HANDLE + // hSCManager ); + [DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("winsvc.h", MSDNShortId = "87861465-c966-479a-b906-27ae36cc83c8")] + public static extern SC_LOCK LockServiceDatabase(SC_HANDLE hSCManager); + + /// + /// Enables an application to receive notification when the specified service is created or deleted or when its status changes. + /// + /// + /// + /// A handle to the service or the service control manager. Handles to services are returned by the OpenService or CreateService + /// function and must have the SERVICE_QUERY_STATUS access right. Handles to the service control manager are returned by the + /// OpenSCManager function and must have the SC_MANAGER_ENUMERATE_SERVICE access right. For more information, see Service Security + /// and Access Rights. + /// + /// There can only be one outstanding notification request per service. + /// + /// + /// The type of status changes that should be reported. This parameter can be one or more of the following values. + /// + /// + /// + /// A pointer to a SERVICE_NOTIFY structure that contains notification information, such as a pointer to the callback function. This + /// structure must remain valid until the callback function is invoked or the calling thread cancels the notification request. + /// + /// + /// Do not make multiple calls to NotifyServiceStatusChange with the same buffer parameter until the callback function from + /// the first call has finished with the buffer or the first notification request has been canceled. Otherwise, there is no guarantee + /// which version of the buffer the callback function will receive. + /// + /// + /// Windows Vista: The address of the callback function must be within the address range of a loaded module. Therefore, the + /// callback function cannot be code that is generated at run time (such as managed code generated by the JIT compiler) or native + /// code that is decompressed at run time. This restriction was removed in Windows Server 2008 and Windows Vista with SP1. + /// + /// + /// + /// + /// If the function succeeds, the return value is ERROR_SUCCESS. If the service has been marked for deletion, the return value is + /// ERROR_SERVICE_MARKED_FOR_DELETE and the handle to the service must be closed. If service notification is lagging too far behind + /// the system state, the function returns ERROR_SERVICE_NOTIFY_CLIENT_LAGGING. In this case, the client should close the handle to + /// the SCM, open a new handle, and call this function again. + /// + /// If the function fails, the return value is one of the system error codes. + /// + /// + /// + /// The NotifyServiceStatusChange function can be used to receive notifications about service applications. It cannot be used + /// to receive notifications about driver services. + /// + /// + /// When the service status changes, the system invokes the specified callback function as an asynchronous procedure call (APC) + /// queued to the calling thread. The calling thread must enter an alertable wait (for example, by calling the SleepEx function) to + /// receive notification. For more information, see Asynchronous Procedure Calls. + /// + /// + /// If the service is already in any of the requested states when NotifyServiceStatusChange is called, the callback function + /// is queued immediately. If the service state has not changed by the next time the function is called with the same service and + /// state, the callback function is not queued immediately; the callback function is queued the next time the service enters the + /// requested state. + /// + /// + /// The NotifyServiceStatusChange function calls the OpenThread function on the calling thread with the THREAD_SET_CONTEXT + /// access right. If the calling thread does not have this access right, NotifyServiceStatusChange fails. If the calling + /// thread is impersonating another user, it may not have sufficient permission to set context. + /// + /// + /// It is more efficient to call NotifyServiceStatusChange from a thread that performs a wait than to create an additional thread. + /// + /// + /// After the callback function is invoked, the caller must call NotifyServiceStatusChange to receive additional + /// notifications. Note that certain functions in the Windows API, including NotifyServiceStatusChange and other SCM + /// functions, use remote procedure calls (RPC); these functions might perform an alertable wait operation, so they are not safe to + /// call from within the callback function. Instead, the callback function should save the notification parameters and perform any + /// additional work outside the callback. + /// + /// + /// To cancel outstanding notifications, close the service handle using the CloseServiceHandle function. After + /// CloseServiceHandle succeeds, no more notification APCs will be queued. If the calling thread exits without closing the + /// service handle or waiting until the APC is generated, a memory leak can occur. + /// + /// + /// Important If the calling thread is in a DLL and the DLL is unloaded before the thread receives the notification or calls + /// CloseServiceHandle, the notification will cause unpredictable results and might cause the process to stop responding. + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-notifyservicestatuschangea DWORD NotifyServiceStatusChangeA( + // SC_HANDLE hService, DWORD dwNotifyMask, PSERVICE_NOTIFYA pNotifyBuffer ); + [DllImport(Lib.AdvApi32, SetLastError = false, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "e22b7f69-f096-486f-97fa-0465bef499cd")] + public static extern Win32Error NotifyServiceStatusChange(SC_HANDLE hService, SERVICE_NOTIFY_FLAGS dwNotifyMask, ref SERVICE_NOTIFY_2 pNotifyBuffer); + /// /// /// Establishes a connection to the service control manager on the specified computer and opens the specified service control manager database. @@ -2173,6 +2996,218 @@ namespace Vanara.PInvoke } } + /// Retrieves dynamic information related to the current service start. + /// A service status handle provided by RegisterServiceCtrlHandlerEx + /// Indicates the information level. + /// + /// A dynamic information buffer. If this parameter is valid, the callback function must free the buffer after use with the LocalFree function. + /// + /// + /// If the function succeeds, the return value is TRUE. + /// + /// If the function fails, the return value is FALSE. When this happens the GetLastError function should be called to retrieve the + /// error code. + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-queryservicedynamicinformation BOOL + // QueryServiceDynamicInformation( SERVICE_STATUS_HANDLE hServiceStatus, DWORD dwInfoLevel, PVOID *ppDynamicInfo ); + [DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("winsvc.h", MSDNShortId = "499b63fd-e77b-4b90-9ee7-ff4b7b12c431")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool QueryServiceDynamicInformation(SERVICE_STATUS_HANDLE hServiceStatus, uint dwInfoLevel, out SafeLocalHandle ppDynamicInfo); + + /// + /// [This function has no effect as of Windows Vista.] + /// Retrieves the lock status of the specified service control manager database. + /// + /// + /// A handle to the service control manager database. The OpenSCManager function returns this handle, which must have the + /// SC_MANAGER_QUERY_LOCK_STATUS access right. For more information, see Service Security and Access Rights. + /// + /// + /// A pointer to a QUERY_SERVICE_LOCK_STATUS structure that receives the lock status of the specified database is returned, plus the + /// strings to which its members point. + /// + /// The size of the buffer pointed to by the lpLockStatus parameter, in bytes. + /// + /// A pointer to a variable that receives the number of bytes needed to return all the lock status information, if the function fails. + /// + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// The following error codes can be set by the service control manager. Other error codes can be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The handle does not have the SC_MANAGER_QUERY_LOCK_STATUS access right. + /// + /// + /// ERROR_INSUFFICIENT_BUFFER + /// + /// There is more lock status information than would fit into the lpLockStatus buffer. The number of bytes required to get all the + /// information is returned in the pcbBytesNeeded parameter. Nothing is written to lpLockStatus. + /// + /// + /// + /// ERROR_INVALID_HANDLE + /// The specified handle is invalid. + /// + /// + /// + /// + /// + /// The QueryServiceLockStatus function returns a QUERY_SERVICE_LOCK_STATUS structure that indicates whether the specified + /// database is locked. If the database is locked, the structure provides the account name of the user that owns the lock and the + /// length of time that the lock has been held. + /// + /// + /// A process calls the LockServiceDatabase function to acquire ownership of a service control manager database lock and the + /// UnlockServiceDatabase function to release the lock. + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-queryservicelockstatusa + // BOOL QueryServiceLockStatusA( SC_HANDLE hSCManager, LPQUERY_SERVICE_LOCK_STATUSA lpLockStatus, DWORD cbBufSize, LPDWORD pcbBytesNeeded ); + [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "5139d31b-65f1-41ba-852a-91eab1dc366e")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool QueryServiceLockStatus(SC_HANDLE hSCManager, IntPtr lpLockStatus, uint cbBufSize, out uint pcbBytesNeeded); + + /// + /// The QueryServiceObjectSecurity function retrieves a copy of the security descriptor associated with a service object. You + /// can also use the GetNamedSecurityInfo function to retrieve a security descriptor. + /// + /// + /// A handle to the service control manager or the service. Handles to the service control manager are returned by the OpenSCManager + /// function, and handles to a service are returned by either the OpenService or CreateService function. The handle must have the + /// READ_CONTROL access right. + /// + /// + /// A set of bit flags that indicate the type of security information to retrieve. This parameter can be a combination of the + /// SECURITY_INFORMATION bit flags, with the exception that this function does not support the LABEL_SECURITY_INFORMATION value. + /// + /// + /// A pointer to a buffer that receives a copy of the security descriptor of the specified service object. The calling process must + /// have the appropriate access to view the specified aspects of the security descriptor of the object. The SECURITY_DESCRIPTOR + /// structure is returned in self-relative format. + /// + /// + /// The size of the buffer pointed to by the lpSecurityDescriptor parameter, in bytes. The largest size allowed is 8 kilobytes. + /// + /// + /// A pointer to a variable that receives the number of bytes needed to return the requested security descriptor information, if the + /// function fails. + /// + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// The following error codes may be set by the service control manager. Other error codes may be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The specified handle was not opened with READ_CONTROL access, or the calling process is not the owner of the object. + /// + /// + /// ERROR_INVALID_HANDLE + /// The specified handle is not valid. + /// + /// + /// ERROR_INSUFFICIENT_BUFFER + /// + /// The security descriptor information is too large for the lpSecurityDescriptor buffer. The number of bytes required to get all the + /// information is returned in the pcbBytesNeeded parameter. Nothing is written to the lpSecurityDescriptor buffer. + /// + /// + /// + /// ERROR_INVALID_PARAMETER + /// The specified security information is not valid. + /// + /// + /// + /// + /// + /// When a service is created, the service control manager assigns a default security descriptor to the service object. To retrieve a + /// copy of the security descriptor for a service object, call the QueryServiceObjectSecurity function. To change the security + /// descriptor, call the SetServiceObjectSecurity function. For a description of the default security descriptor for a service + /// object, see Service Security and Access Rights. + /// + /// + /// To read the owner, group, or DACL from the security descriptor of the service object, the calling process must have been granted + /// READ_CONTROL access when the handle was opened. To get READ_CONTROL access, the caller must be the owner of the object or the + /// DACL of the object must grant the access. + /// + /// + /// To read the SACL from the security descriptor, the calling process must have been granted ACCESS_SYSTEM_SECURITY access when the + /// handle was opened. The correct way to get this access is to enable the SE_SECURITY_NAME privilege in the caller's current token, + /// open the handle for ACCESS_SYSTEM_SECURITY access, and then disable the privilege. + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-queryserviceobjectsecurity BOOL QueryServiceObjectSecurity( + // SC_HANDLE hService, SECURITY_INFORMATION dwSecurityInformation, PSECURITY_DESCRIPTOR lpSecurityDescriptor, DWORD cbBufSize, + // LPDWORD pcbBytesNeeded ); + [DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("winsvc.h", MSDNShortId = "5d95945f-f11b-42af-b302-8d924917b9ab")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool QueryServiceObjectSecurity(SC_HANDLE hService, SECURITY_INFORMATION dwSecurityInformation, PSECURITY_DESCRIPTOR lpSecurityDescriptor, uint cbBufSize, out uint pcbBytesNeeded); + + /// + /// Retrieves the current status of the specified service. + /// + /// This function has been superseded by the QueryServiceStatusEx function. QueryServiceStatusEx returns the same information + /// QueryServiceStatus returns, with the addition of the process identifier and additional information for the service. + /// + /// + /// + /// A handle to the service. This handle is returned by the OpenService or the CreateService function, and it must have the + /// SERVICE_QUERY_STATUS access right. For more information, see Service Security and Access Rights. + /// + /// A pointer to a SERVICE_STATUS structure that receives the status information. + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// The following error codes can be set by the service control manager. Other error codes can be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The handle does not have the SERVICE_QUERY_STATUS access right. + /// + /// + /// ERROR_INVALID_HANDLE + /// The handle is invalid. + /// + /// + /// + /// + /// The QueryServiceStatus function returns the most recent service status information reported to the service control + /// manager. If the service just changed its status, it may not have updated the service control manager yet. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-queryservicestatus BOOL QueryServiceStatus( SC_HANDLE + // hService, LPSERVICE_STATUS lpServiceStatus ); + [DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("winsvc.h", MSDNShortId = "dcd2d8a1-10ef-4229-b873-b4fc3ec9293f")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool QueryServiceStatus(SC_HANDLE hService, out SERVICE_STATUS lpServiceStatus); + /// Retrieves the current status of the specified service based on the specified information level. /// /// A handle to the service. This handle is returned by the CreateService or OpenService function, and it must have the @@ -2274,7 +3309,7 @@ namespace Vanara.PInvoke /// A variable that receives the service status information. The format of this data depends on the value of the dwInfoLevel parameter. /// /// Type mismatch - T - public static T QueryServiceStatusEx(SC_HANDLE hService, SC_STATUS_TYPE InfoLevel) where T : struct + public static T QueryServiceStatusEx(SC_HANDLE hService, SC_STATUS_TYPE InfoLevel = SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO) where T : struct { if (!CorrespondingTypeAttribute.CanGet(InfoLevel, typeof(T))) throw new ArgumentException("Type mismatch", nameof(T)); var b = QueryServiceStatusEx(hService, InfoLevel, IntPtr.Zero, 0, out var size); @@ -2354,7 +3389,7 @@ namespace Vanara.PInvoke // RegisterServiceCtrlHandlerA( LPCSTR lpServiceName, LPHANDLER_FUNCTION lpHandlerProc ); [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("winsvc.h", MSDNShortId = "31ec28fe-8774-48fc-91ba-6fa43108e2cc")] - public static extern SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(string lpServiceName, LphandlerFunction lpHandlerProc); + public static extern SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(string lpServiceName, Handler lpHandlerProc); /// /// Registers a function to handle extended service control requests. @@ -2420,7 +3455,153 @@ namespace Vanara.PInvoke // RegisterServiceCtrlHandlerExA( LPCSTR lpServiceName, LPHANDLER_FUNCTION_EX lpHandlerProc, LPVOID lpContext ); [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] [PInvokeData("winsvc.h", MSDNShortId = "23eea346-9899-4214-88f4-9b7eb7ce1332")] - public static extern SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerEx(string lpServiceName, LphandlerFunction lpHandlerProc, IntPtr lpContext); + public static extern SERVICE_STATUS_HANDLE RegisterServiceCtrlHandlerEx(string lpServiceName, Handler lpHandlerProc, IntPtr lpContext); + + /// + /// Registers a service type with the service control manager and the Server service. The Server service can then announce the + /// registered service type as one it currently supports. The NetServerGetInfo and NetServerEnum functions obtain a specified + /// machine's supported service types. + /// + /// + /// A handle to the status information structure for the service. A service obtains the handle by calling the + /// RegisterServiceCtrlHandlerEx function. + /// + /// + /// The service type. + /// + /// Certain bit flags (0xC00F3F7B) are reserved for use by Microsoft. The SetServiceBits function fails with the error + /// ERROR_INVALID_DATA if any of these bit flags are set in dwServiceBits. The following bit flags are reserved for use by Microsoft. + /// + /// SV_TYPE_WORKSTATION (0x00000001) + /// SV_TYPE_SERVER (0x00000002) + /// SV_TYPE_DOMAIN_CTRL (0x00000008) + /// SV_TYPE_DOMAIN_BAKCTRL (0x00000010) + /// SV_TYPE_TIME_SOURCE (0x00000020) + /// SV_TYPE_AFP (0x00000040) + /// SV_TYPE_DOMAIN_MEMBER (0x00000100) + /// SV_TYPE_PRINTQ_SERVER (0x00000200) + /// SV_TYPE_DIALIN_SERVER (0x00000400) + /// SV_TYPE_XENIX_SERVER (0x00000800) + /// SV_TYPE_SERVER_UNIX (0x00000800) + /// SV_TYPE_NT (0x00001000) + /// SV_TYPE_WFW (0x00002000) + /// SV_TYPE_POTENTIAL_BROWSER (0x00010000) + /// SV_TYPE_BACKUP_BROWSER (0x00020000) + /// SV_TYPE_MASTER_BROWSER (0x00040000) + /// SV_TYPE_DOMAIN_MASTER (0x00080000) + /// SV_TYPE_LOCAL_LIST_ONLY (0x40000000) + /// SV_TYPE_DOMAIN_ENUM (0x80000000) + /// + /// Certain bit flags (0x00300084) are defined by Microsoft, but are not specifically reserved for systems software. The following + /// are these bit flags. + /// + /// SV_TYPE_SV_TYPE_SQLSERVER (0x00000004) + /// SV_TYPE_NOVELL (0x00000080) + /// SV_TYPE_DOMAIN_CTRL (0x00100000) + /// SV_TYPE_DOMAIN_BAKCTRL (0x00200000) + /// + /// Certain bit flags (0x3FC0C000) are not defined by Microsoft, and their use is not coordinated by Microsoft. Developers of + /// applications that use these bits should be aware that other applications can also use them, thus creating a conflict. The + /// following are these bit flags. + /// + /// 0x00004000 + /// 0x00008000 + /// 0x00400000 + /// 0x00800000 + /// 0x01000000 + /// 0x02000000 + /// 0x04000000 + /// 0x08000000 + /// 0x10000000 + /// 0x20000000 + /// + /// + /// If this value is TRUE, the bits in dwServiceBit are to be set. If this value is FALSE, the bits are to be cleared. + /// + /// + /// If this value is TRUE, the Server service is to perform an immediate update. If this value is FALSE, the update is not be + /// performed immediately. + /// + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/lmserver/nf-lmserver-setservicebits BOOL NET_API_FUNCTION SetServiceBits( IN + // SERVICE_STATUS_HANDLE hServiceStatus, IN DWORD dwServiceBits, IN BOOL bSetBitsOn, IN BOOL bUpdateImmediately ); + [DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("lmserver.h", MSDNShortId = "91a985d4-d1af-4161-ae67-a8a9d6740838")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetServiceBits([In] SERVICE_STATUS_HANDLE hServiceStatus, uint dwServiceBits, [MarshalAs(UnmanagedType.Bool)] bool bSetBitsOn, [MarshalAs(UnmanagedType.Bool)] bool bUpdateImmediately); + + /// + /// + /// [ SetServiceObjectSecurity is available for use in the operating systems specified in the Requirements section. It may be + /// altered or unavailable in subsequent versions. Instead, use the SetNamedSecurityInfo function.] + /// + /// The SetServiceObjectSecurity function sets the security descriptor of a service object. + /// + /// + /// A handle to the service. This handle is returned by the OpenService or CreateService function. The access required for this + /// handle depends on the security information specified in the dwSecurityInformation parameter. + /// + /// + /// Specifies the components of the security descriptor to set. This parameter can be a combination of the following values. Note + /// that flags not handled by SetServiceObjectSecurity will be silently ignored. + /// + /// A pointer to a SECURITY_DESCRIPTOR structure that contains the new security information. + /// + /// If the function succeeds, the function returns nonzero. + /// If the function fails, it returns zero. To get extended error information, call GetLastError. + /// + /// The following error codes can be set by the service control manager. Other error codes can be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_ACCESS_DENIED + /// The specified handle was not opened with the required access, or the calling process is not the owner of the object. + /// + /// + /// ERROR_INVALID_HANDLE + /// The specified handle is not valid. + /// + /// + /// ERROR_INVALID_PARAMETER + /// The specified security information or security descriptor is not valid. + /// + /// + /// ERROR_SERVICE_MARKED_FOR_DELETE + /// The specified service has been marked for deletion. + /// + /// + /// + /// + /// + /// The SetServiceObjectSecurity function sets the specified portions of the security descriptor of the service object based + /// on the information specified in the lpSecurityDescriptor buffer. This function replaces any or all of the security information + /// associated with the service object, according to the flags set in the dwSecurityInformation parameter and subject to the access + /// rights of the calling process. + /// + /// + /// When a service is created, the service control manager assigns a default security descriptor to the service object. To retrieve a + /// copy of the security descriptor for a service object, call the QueryServiceObjectSecurity function. For a description of the + /// default security descriptor for a service object, see Service Security and Access Rights. + /// + /// + /// Note that granting certain access to untrusted users (such as SERVICE_CHANGE_CONFIG or SERVICE_STOP) can allow them to interfere + /// with the execution of your service and possibly allow them to run applications under the LocalSystem account. + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-setserviceobjectsecurity BOOL SetServiceObjectSecurity( + // SC_HANDLE hService, SECURITY_INFORMATION dwSecurityInformation, PSECURITY_DESCRIPTOR lpSecurityDescriptor ); + [DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("winsvc.h", MSDNShortId = "39481d9a-79d5-4bbf-8480-4095a34dddb6")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetServiceObjectSecurity(SC_HANDLE hService, SECURITY_INFORMATION dwSecurityInformation, PSECURITY_DESCRIPTOR lpSecurityDescriptor); /// /// Updates the service control manager's status information for the calling service. @@ -2656,6 +3837,81 @@ namespace Vanara.PInvoke [return: MarshalAs(UnmanagedType.Bool)] public static extern bool StartService(SC_HANDLE hService, int dwNumServiceArgs = 0, string[] lpServiceArgVectors = null); + /// + /// Connects the main thread of a service process to the service control manager, which causes the thread to be the service control + /// dispatcher thread for the calling process. + /// + /// + /// A pointer to an array of SERVICE_TABLE_ENTRY structures containing one entry for each service that can execute in the calling + /// process. The members of the last entry in the table must have NULL values to designate the end of the table. + /// + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// The following error code can be set by the service control manager. Other error codes can be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_FAILED_SERVICE_CONTROLLER_CONNECT + /// + /// This error is returned if the program is being run as a console application rather than as a service. If the program will be run + /// as a console application for debugging purposes, structure it such that service-specific code is not called when this error is returned. + /// + /// + /// + /// ERROR_INVALID_DATA + /// The specified dispatch table contains entries that are not in the proper format. + /// + /// + /// ERROR_SERVICE_ALREADY_RUNNING + /// The process has already called StartServiceCtrlDispatcher. Each process can call StartServiceCtrlDispatcher only one time. + /// + /// + /// + /// + /// + /// When the service control manager starts a service process, it waits for the process to call the StartServiceCtrlDispatcher + /// function. The main thread of a service process should make this call as soon as possible after it starts up (within 30 seconds). + /// If StartServiceCtrlDispatcher succeeds, it connects the calling thread to the service control manager and does not return + /// until all running services in the process have entered the SERVICE_STOPPED state. The service control manager uses this + /// connection to send control and service start requests to the main thread of the service process. The main thread acts as a + /// dispatcher by invoking the appropriate HandlerEx function to handle control requests, or by creating a new thread to execute the + /// appropriate ServiceMain function when a new service is started. + /// + /// + /// The lpServiceTable parameter contains an entry for each service that can run in the calling process. Each entry specifies the + /// ServiceMain function for that service. For SERVICE_WIN32_SHARE_PROCESS services, each entry must contain the name of a service. + /// This name is the service name that was specified by the CreateService function when the service was installed. For + /// SERVICE_WIN32_OWN_PROCESS services, the service name in the table entry is ignored. + /// + /// + /// If a service runs in its own process, the main thread of the service process should immediately call + /// StartServiceCtrlDispatcher. All initialization tasks are done in the service's ServiceMain function when the service is started. + /// + /// + /// If multiple services share a process and some common process-wide initialization needs to be done before any ServiceMain function + /// is called, the main thread can do the work before calling StartServiceCtrlDispatcher, as long as it takes less than 30 + /// seconds. Otherwise, another thread must be created to do the process-wide initialization, while the main thread calls + /// StartServiceCtrlDispatcher and becomes the service control dispatcher. Any service-specific initialization should still be + /// done in the individual service main functions. + /// + /// Services should not attempt to display a user interface directly. For more information, see Interactive Services. + /// Examples + /// For an example, see Writing a Service Program's Main Function. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-startservicectrldispatchera BOOL + // StartServiceCtrlDispatcherA( const SERVICE_TABLE_ENTRYA *lpServiceStartTable ); + [DllImport(Lib.AdvApi32, SetLastError = true, CharSet = CharSet.Auto)] + [PInvokeData("winsvc.h", MSDNShortId = "8e275eb7-a8af-4bd7-bb39-0eac4f3735ad")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool StartServiceCtrlDispatcher([In, MarshalAs(UnmanagedType.LPArray)] SERVICE_TABLE_ENTRY[] lpServiceStartTable); + /// Stops a service using with /// /// A handle to the service. This handle is returned by the or function. The @@ -2679,6 +3935,97 @@ namespace Vanara.PInvoke public static bool StopService(SC_HANDLE hService, in SERVICE_CONTROL_STATUS_REASON_PARAMS reason) => ControlServiceEx(hService, ServiceControl.SERVICE_CONTROL_STOP, ServiceInfoLevels.SERVICE_CONTROL_STATUS_REASON_INFO, reason); + /// + /// [This function has no effect as of Windows Vista.] + /// Unlocks a service control manager database by releasing the specified lock. + /// + /// The lock, which is obtained from a previous call to the LockServiceDatabase function. + /// + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// The following error codes can be set by the service control manager. Other error codes can be set by the registry functions that + /// are called by the service control manager. + /// + /// + /// + /// Return code + /// Description + /// + /// + /// ERROR_INVALID_SERVICE_LOCK + /// The specified lock is invalid. + /// + /// + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/nf-winsvc-unlockservicedatabase BOOL UnlockServiceDatabase( SC_LOCK + // ScLock ); + [DllImport(Lib.AdvApi32, SetLastError = true, ExactSpelling = true)] + [PInvokeData("winsvc.h", MSDNShortId = "3277d175-ab0b-43ce-965f-f8087d0124e4")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool UnlockServiceDatabase(SC_LOCK ScLock); + + /// + /// Contains the name of a service in a service control manager database and information about that service. It is used by the + /// EnumDependentServices and EnumServicesStatus functions. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_enum_service_statusa typedef struct _ENUM_SERVICE_STATUSA { + // LPSTR lpServiceName; LPSTR lpDisplayName; SERVICE_STATUS ServiceStatus; } ENUM_SERVICE_STATUSA, *LPENUM_SERVICE_STATUSA; + [PInvokeData("winsvc.h", MSDNShortId = "b088bd94-5d25-44a7-93c0-80ce6588b811")] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct ENUM_SERVICE_STATUS + { + /// + /// The name of a service in the service control manager database. The maximum string length is 256 characters. The service + /// control manager database preserves the case of the characters, but service name comparisons are always case insensitive. A + /// slash (/), backslash (), comma, and space are invalid service name characters. + /// + [MarshalAs(UnmanagedType.LPTStr)] + public string lpServiceName; + + /// + /// A display name that can be used by service control programs, such as Services in Control Panel, to identify the service. This + /// string has a maximum length of 256 characters. The name is case-preserved in the service control manager. Display name + /// comparisons are always case-insensitive. + /// + [MarshalAs(UnmanagedType.LPTStr)] + public string lpDisplayName; + + /// A SERVICE_STATUS structure that contains status information for the lpServiceName service. + public SERVICE_STATUS ServiceStatus; + } + + /// + /// Contains the name of a service in a service control manager database and information about the service. It is used by the + /// EnumServicesStatusEx function. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_enum_service_status_processa typedef struct + // _ENUM_SERVICE_STATUS_PROCESSA { LPSTR lpServiceName; LPSTR lpDisplayName; SERVICE_STATUS_PROCESS ServiceStatusProcess; } + // ENUM_SERVICE_STATUS_PROCESSA, *LPENUM_SERVICE_STATUS_PROCESSA; + [PInvokeData("winsvc.h", MSDNShortId = "6a683cc8-c2ac-4093-aed7-33e6bdd02d79")] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct ENUM_SERVICE_STATUS_PROCESS + { + /// + /// The name of a service in the service control manager database. The maximum string length is 256 characters. The service + /// control manager database preserves the case of the characters, but service name comparisons are always case insensitive. A + /// slash (/), backslash (), comma, and space are invalid service name characters. + /// + [MarshalAs(UnmanagedType.LPTStr)] + public string lpServiceName; + + /// + /// A display name that can be used by service control programs, such as Services in Control Panel, to identify the service. This + /// string has a maximum length of 256 characters. The case is preserved in the service control manager. Display name comparisons + /// are always case-insensitive. + /// + [MarshalAs(UnmanagedType.LPTStr)] + public string lpDisplayName; + + /// A SERVICE_STATUS_PROCESS structure that contains status information for the lpServiceName service. + public SERVICE_STATUS_PROCESS ServiceStatusProcess; + } + /// Contains configuration information for an installed service. It is used by the QueryServiceConfig function. [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] [PInvokeData("Winsvc.h", MSDNShortId = "ms684950")] @@ -2761,6 +4108,29 @@ namespace Vanara.PInvoke public IEnumerable Dependencies => lpDependencies.ToStringEnum(); } + /// + /// Contains information about the lock status of a service control manager database. It is used by the QueryServiceLockStatus function. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-query_service_lock_statusw typedef struct + // _QUERY_SERVICE_LOCK_STATUSW { DWORD fIsLocked; LPWSTR lpLockOwner; DWORD dwLockDuration; } QUERY_SERVICE_LOCK_STATUSW, *LPQUERY_SERVICE_LOCK_STATUSW; + [PInvokeData("winsvc.h", MSDNShortId = "de9797b7-02b0-43cb-bed3-50b7e8676f36")] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct QUERY_SERVICE_LOCK_STATUS + { + /// + /// The lock status of the database. If this member is nonzero, the database is locked. If it is zero, the database is unlocked. + /// + [MarshalAs(UnmanagedType.Bool)] + public bool fIsLocked; + + /// The name of the user who acquired the lock. + [MarshalAs(UnmanagedType.LPTStr)] + public string lpLockOwner; + + /// The time since the lock was first acquired, in seconds. + public uint dwLockDuration; + } + /// /// Represents an action that the service control manager can perform. /// @@ -2859,6 +4229,54 @@ namespace Vanara.PInvoke public IntPtr DangerousGetHandle() => handle; } + /// Provides a handle to a service lock. + [StructLayout(LayoutKind.Sequential)] + public struct SC_LOCK : IHandle + { + private IntPtr handle; + + /// Initializes a new instance of the struct. + /// An object that represents the pre-existing handle to use. + public SC_LOCK(IntPtr preexistingHandle) => handle = preexistingHandle; + + /// Returns an invalid handle by instantiating a object with . + public static SC_LOCK NULL => new SC_LOCK(IntPtr.Zero); + + /// Gets a value indicating whether this instance is a null handle. + public bool IsNull => handle == IntPtr.Zero; + + /// Performs an explicit conversion from to . + /// The handle. + /// The result of the conversion. + public static explicit operator IntPtr(SC_LOCK h) => h.handle; + + /// Performs an implicit conversion from to . + /// The pointer to a handle. + /// The result of the conversion. + public static implicit operator SC_LOCK(IntPtr h) => new SC_LOCK(h); + + /// Implements the operator !=. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator !=(SC_LOCK h1, SC_LOCK h2) => !(h1 == h2); + + /// Implements the operator ==. + /// The first handle. + /// The second handle. + /// The result of the operator. + public static bool operator ==(SC_LOCK h1, SC_LOCK h2) => h1.Equals(h2); + + /// + public override bool Equals(object obj) => obj is SC_LOCK h ? handle == h.handle : false; + + /// + public override int GetHashCode() => handle.GetHashCode(); + + /// + public IntPtr DangerousGetHandle() => handle; + } + /// Contains service control parameters. // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-service_control_status_reason_paramsa typedef struct // _SERVICE_CONTROL_STATUS_REASON_PARAMSA { DWORD dwReason; LPSTR pszComment; SERVICE_STATUS_PROCESS ServiceStatus; } @@ -3281,6 +4699,59 @@ namespace Vanara.PInvoke public uint dwLaunchProtected; } + /// Represents service status notification information. It is used by the NotifyServiceStatusChange function. + /// + /// The callback function is declared as follows: + /// The callback function receives a pointer to the SERVICE_NOTIFY structure provided by the caller. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_notify_2a typedef struct _SERVICE_NOTIFY_2A { DWORD + // dwVersion; PFN_SC_NOTIFY_CALLBACK pfnNotifyCallback; PVOID pContext; DWORD dwNotificationStatus; SERVICE_STATUS_PROCESS + // ServiceStatus; DWORD dwNotificationTriggered; LPSTR pszServiceNames; } SERVICE_NOTIFY_2A, *PSERVICE_NOTIFY_2A; + [PInvokeData("winsvc.h", MSDNShortId = "52ede72e-eb50-48e2-b5c1-125816f6fe57")] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct SERVICE_NOTIFY_2 + { + /// The structure version. This member must be SERVICE_NOTIFY_STATUS_CHANGE (2). + public uint dwVersion; + + /// A pointer to the callback function. For more information, see Remarks. + [MarshalAs(UnmanagedType.FunctionPtr)] + public PFN_SC_NOTIFY_CALLBACK pfnNotifyCallback; + + /// Any user-defined data to be passed to the callback function. + public IntPtr pContext; + + /// + /// A value that indicates the notification status. If this member is ERROR_SUCCESS, the notification has succeeded and + /// the ServiceStatus member contains valid information. If this member is ERROR_SERVICE_MARKED_FOR_DELETE, the + /// service has been marked for deletion and the service handle used by NotifyServiceStatusChange must be closed. + /// + public Win32Error dwNotificationStatus; + + /// + /// A SERVICE_STATUS_PROCESS structure that contains the service status information. This member is only valid if + /// dwNotificationStatus is ERROR_SUCCESS. + /// + public SERVICE_STATUS_PROCESS ServiceStatus; + + /// + /// If dwNotificationStatus is ERROR_SUCCESS, this member contains a bitmask of the notifications that triggered + /// this call to the callback function. + /// + public SERVICE_NOTIFY_FLAGS dwNotificationTriggered; + + /// + /// + /// If dwNotificationStatus is ERROR_SUCCESS and the notification is SERVICE_NOTIFY_CREATED or + /// SERVICE_NOTIFY_DELETED, this member is valid and it is a MULTI_SZ string that contains one or more service + /// names. The names of the created services will have a '/' prefix so you can distinguish them from the names of the deleted services. + /// + /// If this member is valid, the notification callback function must free the string using the LocalFree function. + /// + [MarshalAs(UnmanagedType.LPTStr)] + public string pszServiceNames; + } + /// /// Represents the preferred node on which to run a service. /// @@ -3653,7 +5124,7 @@ namespace Vanara.PInvoke /// is running and on normal termination. /// /// - public uint dwWin32ExitCode; + public Win32Error dwWin32ExitCode; /// /// @@ -3661,7 +5132,7 @@ namespace Vanara.PInvoke /// value is ignored unless the dwWin32ExitCode member is set to ERROR_SERVICE_SPECIFIC_ERROR. /// /// - public uint dwServiceSpecificExitCode; + public Win32Error dwServiceSpecificExitCode; /// /// @@ -4007,6 +5478,35 @@ namespace Vanara.PInvoke public uint dwServiceFlags; } + /// + /// Specifies the ServiceMain function for a service that can run in the calling process. It is used by the + /// StartServiceCtrlDispatcher function. + /// + // https://docs.microsoft.com/en-us/windows/desktop/api/winsvc/ns-winsvc-_service_table_entrya typedef struct _SERVICE_TABLE_ENTRYA { + // LPSTR lpServiceName; LPSERVICE_MAIN_FUNCTIONA lpServiceProc; } SERVICE_TABLE_ENTRYA, *LPSERVICE_TABLE_ENTRYA; + [PInvokeData("winsvc.h", MSDNShortId = "dd40c4f0-cbbe-429f-91c0-3ba141dab702")] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + public struct SERVICE_TABLE_ENTRY + { + /// + /// The name of a service to be run in this service process. + /// + /// If the service is installed with the SERVICE_WIN32_OWN_PROCESS service type, this member is ignored, but cannot be NULL. This + /// member can be an empty string (""). + /// + /// + /// If the service is installed with the SERVICE_WIN32_SHARE_PROCESS service type, this member specifies the name of the service + /// that uses the ServiceMain function pointed to by the lpServiceProc member. + /// + /// + [MarshalAs(UnmanagedType.LPTStr)] + public string lpServiceName; + + /// A pointer to a ServiceMain function. + [MarshalAs(UnmanagedType.FunctionPtr)] + public ServiceMain lpServiceProc; + } + /// /// Represents a service trigger event. This structure is used by the SERVICE_TRIGGER_INFO structure. /// @@ -4398,37 +5898,5 @@ namespace Vanara.PInvoke /// protected override bool InternalReleaseHandle() => CloseServiceHandle(this); } - - /*LPHANDLER_FUNCTION_EX callback - LPSERVICE_MAIN_FUNCTIONA callback - LPSERVICE_MAIN_FUNCTIONW callback - - ControlService - ControlServiceEx - DeleteService - EnumDependentServices - EnumServicesStatusEx - EnumServicesStatus - GetServiceDisplayName - GetServiceKeyName - LockServiceDatabase - NotifyBootConfigStatus - NotifyServiceStatusChange - QueryServiceDynamicInformation - QueryServiceLockStatus - QueryServiceObjectSecurity - QueryServiceStatus - SetServiceObjectSecurity - StartServiceCtrlDispatcher - StartService - UnlockServiceDatabase - - ENUM_SERVICE_STATUS_PROCESS - ENUM_SERVICE_STATUS - QUERY_SERVICE_LOCK_STATUS - SERVICE_NOTIFY_2 - SERVICE_TABLE_ENTRY - SERVICE_TIMECHANGE_INFO - SERVICE_TRIGGER_SPECIFIC_DATA_ITEM */ } } \ No newline at end of file diff --git a/System/Extensions/ServiceControllerExtension.cs b/System/Extensions/ServiceControllerExtension.cs index 1a4bf00b..f48ed07f 100644 --- a/System/Extensions/ServiceControllerExtension.cs +++ b/System/Extensions/ServiceControllerExtension.cs @@ -1,7 +1,12 @@ #if (NET20 || NET35 || NET40 || NET45) + +using System; using System.ComponentModel; using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; using System.ServiceProcess; +using Vanara.PInvoke; using static Vanara.PInvoke.AdvApi32; namespace Vanara.Extensions @@ -9,6 +14,38 @@ namespace Vanara.Extensions /// Extension methods for . public static partial class ServiceControllerExtension { + /// + /// Gets a object that encapsulates the specified type of access + /// control list (ACL) entries for the service described by the current object. + /// + /// The object from which to get access control. + /// + /// One of the values that specifies which group of access control entries to retrieve. + /// + /// + /// A object that encapsulates the access control rules for the + /// current service. + /// + public static Security.AccessControl.ServiceControllerSecurity GetAccessControl(this ServiceController svc, AccessControlSections includeSections = AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group) => new Security.AccessControl.ServiceControllerSecurity(svc.ServiceHandle, includeSections); + + /// + /// Applies access control list (ACL) entries described by a object to + /// the service described by the current object. + /// + /// The object on which to set access control. + /// + /// A object that describes an access control list (ACL) entry to + /// apply to the current service. + /// + public static void SetAccessControl(this ServiceController svc, Security.AccessControl.ServiceControllerSecurity serviceSecurity) + { + if (serviceSecurity is null) throw new ArgumentNullException(nameof(serviceSecurity)); + serviceSecurity.Persist(svc.ServiceHandle); + } + + /// Changes the start mode of a service. + /// The instance. + /// The new start mode. public static void SetStartType(this ServiceController svc, ServiceStartMode mode) { using (var serviceHandle = svc.ServiceHandle) @@ -19,4 +56,275 @@ namespace Vanara.Extensions } } } + +namespace Vanara.Security.AccessControl +{ + /// Defines the access rights to use when creating access and audit rules. + [Flags] + public enum ServiceControllerAccessRights : uint + { + /// Includes STANDARD_RIGHTS_REQUIRED in addition to all access rights in this table. + FullControl = ServiceAccessRights.SERVICE_ALL_ACCESS | AccessSystemSecurity | ACCESS_MASK.STANDARD_RIGHTS_ALL, + + /// + /// Required to call the ChangeServiceConfig or ChangeServiceConfig2 function to change the service configuration. Because this + /// grants the caller the right to change the executable file that the system runs, it should be granted only to administrators. + /// + ChangeConfig = ServiceAccessRights.SERVICE_CHANGE_CONFIG, + + /// Required to call the EnumDependentServices function to enumerate all the services dependent on the service. + EnumerateDependents = ServiceAccessRights.SERVICE_ENUMERATE_DEPENDENTS, + + /// Required to call the ControlService function to ask the service to report its status immediately. + Interrogate = ServiceAccessRights.SERVICE_INTERROGATE, + + /// Required to call the ControlService function to pause or continue the service. + Continue = ServiceAccessRights.SERVICE_PAUSE_CONTINUE, + + /// Required to call the QueryServiceConfig and QueryServiceConfig2 functions to query the service configuration. + QueryConfig = ServiceAccessRights.SERVICE_QUERY_CONFIG, + + /// + /// Required to call the QueryServiceStatus or QueryServiceStatusEx function to ask the service control manager about the status of + /// the service. + /// Required to call the NotifyServiceStatusChange function to receive notification when a service changes status. + /// + QueryStatus = ServiceAccessRights.SERVICE_QUERY_STATUS, + + /// Required to call the StartService function to start the service. + Start = ServiceAccessRights.SERVICE_START, + + /// Required to call the ControlService function to stop the service. + Stop = ServiceAccessRights.SERVICE_STOP, + + /// Required to call the ControlService function to specify a user-defined control code. + UserDefinedControl = ServiceAccessRights.SERVICE_USER_DEFINED_CONTROL, + + /// + /// Required to call the QueryServiceObjectSecurity or SetServiceObjectSecurity function to access the SACL. The proper way to obtain + /// this access is to enable the SE_SECURITY_NAMEprivilege in the caller's current access token, open the handle for + /// ACCESS_SYSTEM_SECURITY access, and then disable the privilege. + /// + AccessSystemSecurity = ACCESS_MASK.ACCESS_SYSTEM_SECURITY, + + /// Required to call the DeleteService function to delete the service. + Delete = ACCESS_MASK.DELETE, + + /// Required to call the QueryServiceObjectSecurity function to query the security descriptor of the service object. + ReadPermissions = ACCESS_MASK.READ_CONTROL, + + /// + /// Required to call the SetServiceObjectSecurity function to modify the Dacl member of the service object's security descriptor. + /// + ChangePermissions = ACCESS_MASK.WRITE_DAC, + + /// + /// Required to call the SetServiceObjectSecurity function to modify the Owner and Group members of the service object's security descriptor. + /// + TakeOwnership = ACCESS_MASK.WRITE_OWNER, + + /// + /// Specifies the right to open and copy folders or files as read-only. This right includes the ReadData right, + /// ReadExtendedAttributes right, ReadAttributes right, and ReadPermissions right. + /// + Read = ACCESS_MASK.STANDARD_RIGHTS_READ | ServiceAccessRights.SERVICE_QUERY_CONFIG | ServiceAccessRights.SERVICE_QUERY_STATUS | ServiceAccessRights.SERVICE_INTERROGATE | ServiceAccessRights.SERVICE_ENUMERATE_DEPENDENTS, + + /// + /// Specifies the right to create folders and files, and to add or remove data from files. This right includes the WriteData right, + /// AppendData right, WriteExtendedAttributes right, and WriteAttributes right. + /// + Write = ACCESS_MASK.STANDARD_RIGHTS_WRITE | ServiceAccessRights.SERVICE_CHANGE_CONFIG, + + /// Specifies the right to run an application file. + Execute = ACCESS_MASK.STANDARD_RIGHTS_EXECUTE | ServiceAccessRights.SERVICE_START | ServiceAccessRights.SERVICE_STOP | ServiceAccessRights.SERVICE_PAUSE_CONTINUE | ServiceAccessRights.SERVICE_USER_DEFINED_CONTROL, + } + + /// Represents an abstraction of an access control entry (ACE) that defines an access rule for a service. + /// + public sealed class ServiceControllerAccessRule : AccessRule + { + /// + /// Initializes a new instance of the class with the specified identity, access rights, and + /// access control type.. + /// + /// The name of the user account. + /// + /// One of the values that specifies the type of operation associated with the access rule. + /// + /// One of the values that specifies whether to allow or deny the operation. + public ServiceControllerAccessRule(string identity, ServiceControllerAccessRights rights, AccessControlType type) + : this(new NTAccount(identity), AccessMaskFromRights(rights), false, type) + { + } + + /// + /// Initializes a new instance of the class with the specified identity, access rights, and + /// access control type.. + /// + /// An object that encapsulates a reference to a user account. + /// + /// One of the values that specifies the type of operation associated with the access rule. + /// + /// One of the values that specifies whether to allow or deny the operation. + public ServiceControllerAccessRule(IdentityReference identity, ServiceControllerAccessRights rights, AccessControlType type) + : this(identity, AccessMaskFromRights(rights), false, type) + { + } + + internal ServiceControllerAccessRule(IdentityReference identity, int accessMask, bool isInherited, AccessControlType type) + : base(identity, accessMask, isInherited, InheritanceFlags.None, PropagationFlags.None, type) + { + } + + /// + /// Gets the flags that are associated with the current + /// object. + /// + /// A bitwise combination of the values. + public ServiceControllerAccessRights AccessRights => (ServiceControllerAccessRights)AccessMask; + + internal static int AccessMaskFromRights(ServiceControllerAccessRights rights) => + rights >= 0 && rights <= ServiceControllerAccessRights.FullControl ? + (int)rights : throw new ArgumentOutOfRangeException(nameof(rights)); + } + + /// Represents an abstraction of an access control entry (ACE) that defines an audit rule for a service. + /// + public sealed class ServiceControllerAuditRule : AuditRule + { + /// + /// Initializes a new instance of the class for a user account specified in a + /// object. + /// + /// An object that encapsulates a reference to a user account. + /// + /// One of the values that specifies the type of operation associated with the access rule. + /// + /// One of the values that specifies when to perform auditing. + public ServiceControllerAuditRule(IdentityReference identity, ServiceControllerAccessRights rights, AuditFlags flags) + : this(identity, ServiceControllerAccessRule.AccessMaskFromRights(rights), false, flags) + { + } + + /// + /// Initializes a new instance of the class for a user account specified in a + /// object. + /// + /// The name of the user account. + /// + /// One of the values that specifies the type of operation associated with the access rule. + /// + /// One of the values that specifies when to perform auditing. + public ServiceControllerAuditRule(string identity, ServiceControllerAccessRights rights, AuditFlags flags) + : this(new NTAccount(identity), ServiceControllerAccessRule.AccessMaskFromRights(rights), false, flags) + { + } + + internal ServiceControllerAuditRule(IdentityReference identity, int accessMask, bool isInherited, AuditFlags flags) + : base(identity, accessMask, isInherited, InheritanceFlags.None, PropagationFlags.None, flags) + { + } + } + + /// Represents the access control and audit security for a service. + /// + public class ServiceControllerSecurity : NativeObjectSecurity + { + /// Initializes a new instance of the class. + public ServiceControllerSecurity() : base(false, System.Security.AccessControl.ResourceType.Service) { } + + internal ServiceControllerSecurity(SafeHandle handle, AccessControlSections includeSections) : + base(false, System.Security.AccessControl.ResourceType.Service, handle, includeSections) + { + } + + /// + /// Gets the of the securable object associated with this + /// object. + /// + public override Type AccessRightType => typeof(ServiceControllerAccessRights); + + /// + /// Gets the of the object associated with the access rules of this + /// object. The object must be an object + /// that can be cast as a object. + /// + public override Type AccessRuleType => typeof(ServiceControllerAccessRule); + + /// + /// Gets the object associated with the audit rules of this + /// object. The object must be an object + /// that can be cast as a object. + /// + public override Type AuditRuleType => typeof(ServiceControllerAuditRule); + + /// + /// Initializes a new instance of the class with the specified values. + /// + /// The identity to which the access rule applies. It must be an object that can be cast as a . + /// + /// The access mask of this rule. The access mask is a 32-bit collection of anonymous bits, the meaning of which is defined by the + /// individual integrators. + /// + /// true if this rule is inherited from a parent container. + /// Specifies the inheritance properties of the access rule. + /// + /// Specifies whether inherited access rules are automatically propagated. The propagation flags are ignored if + /// is set to . + /// + /// Specifies the valid access control type. + /// The object that this method creates. + public override AccessRule AccessRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) => new ServiceControllerAccessRule(identityReference, (ServiceControllerAccessRights)accessMask, type); + + /// + /// Initializes a new instance of the class with the specified values. + /// + /// The identity to which the audit rule applies. It must be an object that can be cast as a . + /// + /// The access mask of this rule. The access mask is a 32-bit collection of anonymous bits, the meaning of which is defined by the + /// individual integrators. + /// + /// true if this rule is inherited from a parent container. + /// Specifies the inheritance properties of the audit rule. + /// + /// Specifies whether inherited audit rules are automatically propagated. The propagation flags are ignored if + /// is set to . + /// + /// Specifies the conditions for which the rule is audited. + /// The object that this method creates. + public override AuditRule AuditRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) => new ServiceControllerAuditRule(identityReference, (ServiceControllerAccessRights)accessMask, flags); + + internal void Persist(SafeHandle handle) + { + WriteLock(); + try + { + var persistRules = GetAccessControlSectionsFromChanges(); + if (persistRules == AccessControlSections.None) + return; + Persist(handle, persistRules); + OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false; + } + finally + { + WriteUnlock(); + } + } + + private AccessControlSections GetAccessControlSectionsFromChanges() + { + var persistRules = AccessControlSections.None; + if (AccessRulesModified) + persistRules = AccessControlSections.Access; + if (AuditRulesModified) + persistRules |= AccessControlSections.Audit; + if (OwnerModified) + persistRules |= AccessControlSections.Owner; + if (GroupModified) + persistRules |= AccessControlSections.Group; + return persistRules; + } + } +} + #endif \ No newline at end of file diff --git a/UnitTests/PInvoke/Security/AdvApi32/AdvApi32Tests.cs b/UnitTests/PInvoke/Security/AdvApi32/AdvApi32Tests.cs index e6da91e1..7a7b9597 100644 --- a/UnitTests/PInvoke/Security/AdvApi32/AdvApi32Tests.cs +++ b/UnitTests/PInvoke/Security/AdvApi32/AdvApi32Tests.cs @@ -378,30 +378,6 @@ namespace Vanara.PInvoke.Tests Assert.That(LookupPrivilegeName(null, luid, sb, ref chSz), Is.False); } - [Test()] - public void OpenCloseSCManager() - { - using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT)) - { - AssertHandleIsValid(scm); - } - } - - [Test()] - public void OpenCloseService() - { - using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT)) - { - AssertHandleIsValid(scm); - - //opens task scheduler service - using (var service = AdvApi32.OpenService(scm, "Schedule", ServiceAccessTypes.SERVICE_QUERY_STATUS)) - { - AssertHandleIsValid(service); - } - } - } - [Test()] public void PrivilegeCheckTest() { @@ -439,27 +415,6 @@ namespace Vanara.PInvoke.Tests } } - [Test()] - public void QueryServiceStatus() - { - using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT)) - { - AssertHandleIsValid(scm); - - //opens task scheduler service - using (var service = AdvApi32.OpenService(scm, "Schedule", ServiceAccessTypes.SERVICE_QUERY_STATUS)) - { - AssertHandleIsValid(service); - - //query service status - var status = AdvApi32.QueryServiceStatusEx(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO); - - Assert.That(status.dwServiceType, Is.EqualTo(ServiceTypes.SERVICE_WIN32).Or.EqualTo(ServiceTypes.SERVICE_WIN32_SHARE_PROCESS)); - Assert.That(status.dwServiceFlags, Is.EqualTo(0)); - } - } - } - [Test()] public void RegNotifyChangeKeyValueTest() { @@ -498,48 +453,6 @@ namespace Vanara.PInvoke.Tests } } - [Test()] - [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")] - public void StartStopService() - { - using (var scm = AdvApi32.OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT)) - { - AssertHandleIsValid(scm); - - var access = ServiceAccessTypes.SERVICE_START | ServiceAccessTypes.SERVICE_STOP | ServiceAccessTypes.SERVICE_QUERY_STATUS; - - //opens print spooler service - using (var service = AdvApi32.OpenService(scm, "Spooler", access)) - { - AssertHandleIsValid(service); - - //query service status - var status = AdvApi32.QueryServiceStatusEx(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO); - - if (status.dwCurrentState == ServiceState.SERVICE_RUNNING) - { - var ret4 = AdvApi32.StopService(service, out var _); - if (!ret4) Win32Error.ThrowLastError(); - - WaitForServiceStatus(service, ServiceState.SERVICE_STOPPED); - - var ret6 = AdvApi32.StartService(service); - if (!ret6) Win32Error.ThrowLastError(); - } - else - { - var ret4 = AdvApi32.StartService(service); - if (!ret4) Win32Error.ThrowLastError(); - - WaitForServiceStatus(service, ServiceState.SERVICE_RUNNING); - - var ret6 = AdvApi32.StopService(service, out var _); - if (!ret6) Win32Error.ThrowLastError(); - } - } - } - } - [Test] public void UserTest() { @@ -557,6 +470,7 @@ namespace Vanara.PInvoke.Tests TestContext.WriteLine($"Ace{i}: {ace.GetHeader().AceType}={domain}\\{account}; {ace.GetMask()}"); } } + internal static SafeSecurityDescriptor GetSD(string filename, SECURITY_INFORMATION si = SECURITY_INFORMATION.DACL_SECURITY_INFORMATION | SECURITY_INFORMATION.OWNER_SECURITY_INFORMATION) { var err = GetNamedSecurityInfo(filename, SE_OBJECT_TYPE.SE_FILE_OBJECT, si, out _, out _, out _, out _, out var pSD); @@ -564,34 +478,5 @@ namespace Vanara.PInvoke.Tests Assert.That(!pSD.IsInvalid); return pSD; } - - private static void AssertHandleIsValid(SafeSC_HANDLE handle) - { - if (handle.IsInvalid) - Win32Error.ThrowLastError(); - - Assert.That(handle.IsNull, Is.False); - Assert.That(handle.IsClosed, Is.False); - Assert.That(handle.IsInvalid, Is.False); - } - - private static void WaitForServiceStatus(SafeSC_HANDLE service, ServiceState status) - { - //query service status again to check that it changed - var tests = 0; - - while (tests < 40) - { - var status2 = AdvApi32.QueryServiceStatusEx(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO); - if (status2.dwCurrentState == status) - break; - - Thread.Sleep(500); - tests++; - } - - if (tests >= 40) - throw new TimeoutException($"Timed-out waiting for service status {status}"); - } } } \ No newline at end of file diff --git a/UnitTests/PInvoke/Security/AdvApi32/ServiceTests.cs b/UnitTests/PInvoke/Security/AdvApi32/ServiceTests.cs new file mode 100644 index 00000000..e9cfe9a9 --- /dev/null +++ b/UnitTests/PInvoke/Security/AdvApi32/ServiceTests.cs @@ -0,0 +1,232 @@ +using NUnit.Framework; +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using static Vanara.PInvoke.AdvApi32; + +namespace Vanara.PInvoke.Tests +{ + [TestFixture()] + public class ServiceTests + { + private const string svcKey = "Windows Management Instrumentation"; + private const string svcName = "Winmgmt"; + private SafeSC_HANDLE hSvc; + private SafeSC_HANDLE hSvcMgr; + + [OneTimeSetUp] + public void _Setup() + { + hSvcMgr = OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_ALL_ACCESS); + AssertHandleIsValid(hSvcMgr); + hSvc = OpenService(hSvcMgr, svcName, ServiceAccessTypes.SERVICE_ALL_ACCESS); + AssertHandleIsValid(hSvc); + } + + [OneTimeTearDown] + public void _TearDown() + { + hSvc.Dispose(); + hSvcMgr.Dispose(); + } + + [Test] + public void ControlServiceTest() + { + Assert.That(ControlService(hSvc, ServiceControl.SERVICE_CONTROL_PAUSE, out var status), Is.True); + Thread.Sleep((int)status.dwWaitHint); + Assert.That(GetState(hSvc), Is.EqualTo(ServiceState.SERVICE_PAUSED).Or.EqualTo(ServiceState.SERVICE_PAUSE_PENDING)); + Assert.That(ControlService(hSvc, ServiceControl.SERVICE_CONTROL_CONTINUE, out status), Is.True); + Thread.Sleep((int)status.dwWaitHint); + Assert.That(GetState(hSvc), Is.EqualTo(ServiceState.SERVICE_RUNNING)); + } + + [Test] + public void EnumDependentServicesTest() + { + var l = EnumDependentServices(hSvc); + TestContext.WriteLine(string.Join("; ", l.Select(i => i.lpDisplayName))); + Assert.That(l, Is.Not.Empty); + } + + [Test] + public void EnumServicesStatusExTest() + { + var l = EnumServicesStatusEx(hSvcMgr, ServiceTypes.SERVICE_DRIVER, SERVICE_STATE.SERVICE_ACTIVE); + TestContext.WriteLine(string.Join("; ", l.Select(i => i.lpDisplayName))); + Assert.That(l, Is.Not.Empty); + } + + [Test] + public void EnumServicesStatusTest() + { + var l = EnumServicesStatus(hSvcMgr); + TestContext.WriteLine(string.Join("; ", l.Select(i => i.lpDisplayName))); + Assert.That(l, Is.Not.Empty); + } + + [Test] + public void GetServiceDisplayNameTest() + { + var sb = new StringBuilder(1024, 1024); + var sz = (uint)sb.Capacity; + var ret = GetServiceDisplayName(hSvcMgr, svcName, sb, ref sz); + TestContext.WriteLine(ret ? sb.ToString() : $"Error: {Win32Error.GetLastError()}"); + Assert.That(ret, Is.True); + Assert.That(sb.ToString(), Is.EqualTo(svcKey)); + } + + [Test] + public void GetServiceKeyNameTest() + { + var sb = new StringBuilder(1024, 1024); + var sz = (uint)sb.Capacity; + var ret = GetServiceKeyName(hSvcMgr, svcKey, sb, ref sz); + TestContext.WriteLine(ret ? sb.ToString() : $"Error: {Win32Error.GetLastError()}"); + Assert.That(ret, Is.True); + Assert.That(sb.ToString(), Is.EqualTo(svcName)); + } + + [Test] + public void NotifyServiceStatusChangeTest() + { + var svcNotify = new SERVICE_NOTIFY_2 + { + dwVersion = 2, + pfnNotifyCallback = ChangeDelegate + }; + Thread.BeginThreadAffinity(); + var ret = NotifyServiceStatusChange(hSvc, SERVICE_NOTIFY_FLAGS.SERVICE_NOTIFY_PAUSED | SERVICE_NOTIFY_FLAGS.SERVICE_NOTIFY_PAUSE_PENDING | SERVICE_NOTIFY_FLAGS.SERVICE_NOTIFY_CONTINUE_PENDING, ref svcNotify); + if (ret.Failed) TestContext.WriteLine(ret); + Assert.That(ret.Succeeded, Is.True); + new Thread(ThreadExec).Start(); + Kernel32.SleepEx(10000, true); + Thread.EndThreadAffinity(); + + void ChangeDelegate(ref SERVICE_NOTIFY_2 pParameter) + { + TestContext.WriteLine(pParameter.ServiceStatus.dwCurrentState); + } + + void ThreadExec() + { + using (var mgr = OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_ALL_ACCESS)) + { + if (!mgr.IsInvalid) + { + using (var svc = OpenService(hSvcMgr, svcName, ServiceAccessTypes.SERVICE_ALL_ACCESS)) + { + if (!svc.IsInvalid) + { + TestContext.WriteLine("Pausing..."); + ControlService(svc, ServiceControl.SERVICE_CONTROL_PAUSE, out _); + Thread.Sleep(3000); + TestContext.WriteLine("Continuing..."); + ControlService(svc, ServiceControl.SERVICE_CONTROL_CONTINUE, out _); + Thread.Sleep(3000); + } + } + } + } + } + } + + [Test] + public void OpenCloseSCManagerTest() + { + using (var scm = OpenSCManager(null, null, ScManagerAccessTypes.SC_MANAGER_CONNECT)) + { + AssertHandleIsValid(scm); + } + } + + [Test] + public void OpenCloseServiceTest() + { + //opens task scheduler service + using (var service = OpenService(hSvcMgr, "Schedule", ServiceAccessTypes.SERVICE_QUERY_STATUS)) + { + AssertHandleIsValid(service); + } + } + + [Test] + public void QueryServiceStatusTest() + { + //query service status + var ret = QueryServiceStatus(hSvc, out var i); + TestContext.WriteLine(ret ? i.dwCurrentState.ToString() : $"Error: {Win32Error.GetLastError()}"); + Assert.That(ret, Is.True); + } + + [Test] + public void QueryServiceStatusExTest() + { + //query service status + var status = QueryServiceStatusEx(hSvc, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO); + + Assert.That(status.dwServiceType, Is.EqualTo(ServiceTypes.SERVICE_WIN32_OWN_PROCESS | ServiceTypes.SERVICE_INTERACTIVE_PROCESS)); + Assert.That(status.dwServiceFlags, Is.EqualTo(0)); + } + + [Test] + public void StartStopServiceTest() + { + //query service status + var status = QueryServiceStatusEx(hSvc, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO); + + if (status.dwCurrentState == ServiceState.SERVICE_RUNNING) + { + var ret4 = StopService(hSvc, out var _); + if (!ret4) Win32Error.ThrowLastError(); + + WaitForServiceStatus(hSvc, ServiceState.SERVICE_STOPPED); + + var ret6 = StartService(hSvc); + if (!ret6) Win32Error.ThrowLastError(); + } + else + { + var ret4 = StartService(hSvc); + if (!ret4) Win32Error.ThrowLastError(); + + WaitForServiceStatus(hSvc, ServiceState.SERVICE_RUNNING); + + var ret6 = StopService(hSvc, out var _); + if (!ret6) Win32Error.ThrowLastError(); + } + } + + private static void AssertHandleIsValid(SafeSC_HANDLE handle) + { + if (handle.IsInvalid) + Win32Error.ThrowLastError(); + + Assert.That(handle.IsNull, Is.False); + Assert.That(handle.IsClosed, Is.False); + Assert.That(handle.IsInvalid, Is.False); + } + + private static ServiceState GetState(SC_HANDLE handle) => QueryServiceStatus(handle, out var i) ? i.dwCurrentState : throw Win32Error.GetLastError().GetException(); + + private static void WaitForServiceStatus(SafeSC_HANDLE service, ServiceState status) + { + //query service status again to check that it changed + var tests = 0; + + while (tests < 40) + { + if (GetState(service) == status) + break; + + Thread.Sleep(500); + tests++; + } + + if (tests >= 40) + throw new TimeoutException($"Timed-out waiting for service status {status}"); + } + } +} \ No newline at end of file diff --git a/UnitTests/PInvoke/Security/Security.csproj b/UnitTests/PInvoke/Security/Security.csproj index 0da33c8a..590d6763 100644 --- a/UnitTests/PInvoke/Security/Security.csproj +++ b/UnitTests/PInvoke/Security/Security.csproj @@ -40,6 +40,7 @@ +